From c0d395235b318c701edc70b245addd5e069e19c0 Mon Sep 17 00:00:00 2001 From: dan63047 Date: Mon, 19 Aug 2024 20:59:25 +0300 Subject: [PATCH] Full leaderboard - full capabilities --- lib/data_objects/tetrio.dart | 54 +++++- lib/services/tetrio_crud.dart | 56 +++--- lib/views/main_view.dart | 10 +- lib/views/rank_averages_view.dart | 8 +- lib/views/tl_leaderboard_view.dart | 291 +++++++++++++++-------------- pubspec.yaml | 2 +- 6 files changed, 234 insertions(+), 187 deletions(-) diff --git a/lib/data_objects/tetrio.dart b/lib/data_objects/tetrio.dart index ba79b51..df4dfbe 100644 --- a/lib/data_objects/tetrio.dart +++ b/lib/data_objects/tetrio.dart @@ -66,6 +66,8 @@ const Map rankCutoffs = { enum Stats { tr, glicko, + gxe, + s1tr, rd, gp, gw, @@ -95,6 +97,8 @@ enum Stats { const Map chartsShortTitles = { Stats.tr: "TR", + Stats.gxe: "Glixare", + Stats.s1tr: "S1 TR", Stats.glicko: "Glicko", Stats.rd: "RD", Stats.gp: "GP", @@ -351,6 +355,10 @@ class TetrioPlayer { return tlSeason1?.tr; case Stats.glicko: return tlSeason1?.glicko; + case Stats.gxe: + return tlSeason1?.gxe; + case Stats.s1tr: + return tlSeason1?.s1tr; case Stats.rd: return tlSeason1?.rd; case Stats.gp: @@ -1414,6 +1422,7 @@ class TetraLeague { } double get winrate => gamesWon / gamesPlayed; + double get s1tr => gxe * 250; TetraLeague.fromJson(Map json, ts) { timestamp = ts; @@ -1451,7 +1460,7 @@ class TetraLeague { TetrioPlayerFromLeaderboard convertToPlayerFromLeaderboard(String id) => TetrioPlayerFromLeaderboard( id, "", "user", -1, null, timestamp, gamesPlayed, gamesWon, - tr, glicko??0, rd??noTrRd, rank, bestRank, apm??0, pps??0, vs??0, decaying); + tr, gxe, glicko??0, rd??noTrRd, rank, bestRank, apm??0, pps??0, vs??0, decaying); Map toJson() { final Map data = {}; @@ -1757,6 +1766,7 @@ class TetrioPlayersLeaderboard { avgPPS = 0, avgVS = 0, avgTR = 0, + avgGlixare = 0, avgGlicko = 0, avgRD = 0, avgAPP = 0, @@ -1775,6 +1785,7 @@ class TetrioPlayersLeaderboard { avgStride = 0, avgInfDS = 0, lowestTR = 25000, + lowestGlixare = double.infinity, lowestGlicko = double.infinity, lowestRD = double.infinity, lowestWinrate = double.infinity, @@ -1797,6 +1808,7 @@ class TetrioPlayersLeaderboard { lowestStride = double.infinity, lowestInfDS = double.infinity, highestTR = double.negativeInfinity, + highestGlixare = double.negativeInfinity, highestGlicko = double.negativeInfinity, highestRD = double.negativeInfinity, highestWinrate = double.negativeInfinity, @@ -1827,6 +1839,7 @@ class TetrioPlayersLeaderboard { highestGamesPlayed = 0, highestGamesWon = 0; String lowestTRid = "", lowestTRnick = "", + lowestGlixareID = "", lowestGlixareNick = "", lowestGlickoID = "", lowestGlickoNick = "", lowestRdID = "", lowestRdNick = "", lowestGamesPlayedID = "", lowestGamesPlayedNick = "", @@ -1851,6 +1864,7 @@ class TetrioPlayersLeaderboard { lowestStrideID = "", lowestStrideNick = "", lowestInfDSid = "", lowestInfDSnick = "", highestTRid = "", highestTRnick = "", + highestGlixareID = "", highestGlixareNick = "", highestGlickoID = "", highestGlickoNick = "", highestRdID = "", highestRdNick = "", highestGamesPlayedID = "", highestGamesPlayedNick = "", @@ -1879,6 +1893,7 @@ class TetrioPlayersLeaderboard { avgPPS += entry.pps; avgVS += entry.vs; avgTR += entry.tr; + avgGlixare += entry.gxe; if (entry.glicko != null) avgGlicko += entry.glicko!; if (entry.rd != null) avgRD += entry.rd!; avgAPP += entry.nerdStats.app; @@ -1903,6 +1918,11 @@ class TetrioPlayersLeaderboard { lowestTRid = entry.userId; lowestTRnick = entry.username; } + if (entry.gxe < lowestGlixare){ + lowestGlixare = entry.gxe; + lowestGlixareID = entry.userId; + lowestGlixareNick = entry.username; + } if (entry.glicko != null && entry.glicko! < lowestGlicko){ lowestGlicko = entry.glicko!; lowestGlickoID = entry.userId; @@ -2023,6 +2043,11 @@ class TetrioPlayersLeaderboard { highestTRid = entry.userId; highestTRnick = entry.username; } + if (entry.gxe > highestGlixare){ + highestGlixare = entry.gxe; + highestGlixareID = entry.userId; + highestGlixareNick = entry.username; + } if (entry.glicko != null && entry.glicko! > highestGlicko){ highestGlicko = entry.glicko!; highestGlickoID = entry.userId; @@ -2143,6 +2168,7 @@ class TetrioPlayersLeaderboard { avgPPS /= filtredLeaderboard.length; avgVS /= filtredLeaderboard.length; avgTR /= filtredLeaderboard.length; + avgGlixare /= filtredLeaderboard.length; avgGlicko /= filtredLeaderboard.length; avgRD /= filtredLeaderboard.length; avgAPP /= filtredLeaderboard.length; @@ -2162,7 +2188,7 @@ class TetrioPlayersLeaderboard { avgInfDS /= filtredLeaderboard.length; avgGamesPlayed = (totalGamesPlayed / filtredLeaderboard.length).floor(); avgGamesWon = (totalGamesWon / filtredLeaderboard.length).floor(); - return [TetraLeague(timestamp: DateTime.now(), apm: avgAPM, pps: avgPPS, vs: avgVS, glicko: avgGlicko, rd: avgRD, gamesPlayed: avgGamesPlayed, gamesWon: avgGamesWon, bestRank: rank, gxe: -1, decaying: false, tr: avgTR, rank: rank == "" ? "z" : rank, percentileRank: rank, percentile: rankCutoffs[rank]!, standing: -1, standingLocal: -1, nextAt: -1, prevAt: -1), + return [TetraLeague(timestamp: DateTime.now(), apm: avgAPM, pps: avgPPS, vs: avgVS, gxe: avgGlixare, glicko: avgGlicko, rd: avgRD, gamesPlayed: avgGamesPlayed, gamesWon: avgGamesWon, bestRank: rank, decaying: false, tr: avgTR, rank: rank == "" ? "z" : rank, percentileRank: rank, percentile: rankCutoffs[rank]!, standing: -1, standingLocal: -1, nextAt: -1, prevAt: -1), { "everyone": rank == "", "totalGamesPlayed": totalGamesPlayed, @@ -2171,6 +2197,12 @@ class TetrioPlayersLeaderboard { "lowestTR": lowestTR, "lowestTRid": lowestTRid, "lowestTRnick": lowestTRnick, + "lowestGlixare": lowestGlixare, + "lowestGlixareID": lowestGlixareID, + "lowestGlixareNick": lowestGlixareNick, + "lowestS1tr": lowestGlixare * 250, + "lowestS1trID": lowestGlixareID, + "lowestS1trNick": lowestGlixareNick, "lowestGlicko": lowestGlicko, "lowestGlickoID": lowestGlickoID, "lowestGlickoNick": lowestGlickoNick, @@ -2243,6 +2275,12 @@ class TetrioPlayersLeaderboard { "highestTR": highestTR, "highestTRid": highestTRid, "highestTRnick": highestTRnick, + "highestGlixare": highestGlixare, + "highestGlixareID": highestGlixareID, + "highestGlixareNick": highestGlixareNick, + "highestS1tr": highestGlixare * 250, + "highestS1trID": highestGlixareID, + "highestS1trNick": highestGlixareNick, "highestGlicko": highestGlicko, "highestGlickoID": highestGlickoID, "highestGlickoNick": highestGlickoNick, @@ -2327,8 +2365,8 @@ class TetrioPlayersLeaderboard { "avgPlonk": avgPlonk, "avgStride": avgStride, "avgInfDS": avgInfDS, - "toEnterTR": rank.toLowerCase() != "z" ? leaderboard[(leaderboard.length * rankCutoffs[rank]!).floor()].tr : lowestTR, - "toEnterGlicko": rank.toLowerCase() != "z" ? leaderboard[(leaderboard.length * rankCutoffs[rank]!).floor()].glicko : 0, + "toEnterTR": rank.toLowerCase() != "z" ? leaderboard[(leaderboard.length * rankCutoffs[rank]!).floor()-1].tr : lowestTR, + "toEnterGlicko": rank.toLowerCase() != "z" ? leaderboard[(leaderboard.length * rankCutoffs[rank]!).floor()-1].glicko : 0, "entries": filtredLeaderboard }]; }else{ @@ -2447,6 +2485,7 @@ class TetrioPlayerFromLeaderboard { late int gamesPlayed; late int gamesWon; late double tr; + late double gxe; late double? glicko; late double? rd; late String rank; @@ -2469,6 +2508,7 @@ class TetrioPlayerFromLeaderboard { this.gamesPlayed, this.gamesWon, this.tr, + this.gxe, this.glicko, this.rd, this.rank, @@ -2484,6 +2524,7 @@ class TetrioPlayerFromLeaderboard { double get winrate => gamesWon / gamesPlayed; double get esttracc => estTr.esttr - tr; + double get s1tr => gxe * 250; TetrioPlayerFromLeaderboard.fromJson(Map json, DateTime ts) { userId = json['_id']; @@ -2495,6 +2536,7 @@ class TetrioPlayerFromLeaderboard { gamesPlayed = json['league']['gamesplayed'] as int; gamesWon = json['league']['gameswon'] as int; tr = json['league']['tr'] != null ? json['league']['tr'].toDouble() : 0; + gxe = json['league']['gxe']??-1; glicko = json['league']['glicko']?.toDouble(); rd = json['league']['rd']?.toDouble(); rank = json['league']['rank']; @@ -2514,6 +2556,10 @@ class TetrioPlayerFromLeaderboard { return tr; case Stats.glicko: return glicko??-1; + case Stats.gxe: + return gxe; + case Stats.s1tr: + return s1tr; case Stats.rd: return rd??-1; case Stats.gp: diff --git a/lib/services/tetrio_crud.dart b/lib/services/tetrio_crud.dart index d27a2e6..4e8da1c 100644 --- a/lib/services/tetrio_crud.dart +++ b/lib/services/tetrio_crud.dart @@ -636,18 +636,12 @@ class TetrioService extends DB { } /// Retrieves full Tetra League leaderboard from Tetra Channel api. Returns a leaderboard object. Throws an exception if fails to retrieve. - Future fetchTLLeaderboard({double? after}) async { - TetrioPlayersLeaderboard? cached = _cache.get("league${after != null ? after.toString() : ""}", TetrioPlayersLeaderboard); + Future fetchTLLeaderboard() async { + TetrioPlayersLeaderboard? cached = _cache.get("league", TetrioPlayersLeaderboard); if (cached != null) return cached; - Uri url; - if (kIsWeb) { - url = Uri.https('ts.dan63.by', 'oskware_bridge.php', {"endpoint": "TLLeaderboard"}); - } else { - url = Uri.https('ch.tetr.io', 'api/users/by/league', { - "limit": "100", - if (after != null) "after": "$after:0:0" - }); - } + + Uri url = Uri.https('ts.dan63.by', 'beanserver_blaster/leaderboard.json'); + try{ final response = await client.get(url); @@ -655,16 +649,10 @@ class TetrioService extends DB { case 200: _lbPositions.clear(); var rawJson = jsonDecode(response.body); - if (rawJson['success']) { // if api confirmed that everything ok - TetrioPlayersLeaderboard leaderboard = TetrioPlayersLeaderboard.fromJson(rawJson['data']['entries'], "league", DateTime.fromMillisecondsSinceEpoch(rawJson['cache']['cached_at'])); - developer.log("fetchTLLeaderboard: Leaderboard retrieved and cached", name: "services/tetrio_crud"); - //_leaderboardsCache[rawJson['cache']['cached_until'].toString()] = leaderboard; - _cache.store(leaderboard, rawJson['cache']['cached_until']); - return leaderboard; - } else { // idk how to hit that one - developer.log("fetchTLLeaderboard: Bruh", name: "services/tetrio_crud", error: rawJson); - throw Exception("Failed to get leaderboard (problems on the tetr.io side)"); // will it be on tetr.io side? - } + TetrioPlayersLeaderboard leaderboard = TetrioPlayersLeaderboard.fromJson(rawJson['data'], "league", DateTime.fromMillisecondsSinceEpoch(rawJson['created'])); + developer.log("fetchTLLeaderboard: Leaderboard retrieved and cached", name: "services/tetrio_crud"); + _cache.store(leaderboard, rawJson['cache_until']); + return leaderboard; case 403: throw TetrioForbidden(); case 429: @@ -686,19 +674,19 @@ class TetrioService extends DB { } } - Stream fetchFullLeaderboard() async* { - late double after; - int lbLength = 100; - TetrioPlayersLeaderboard leaderboard = await fetchTLLeaderboard(); - after = leaderboard.leaderboard.last.tr; - while (lbLength == 100){ - TetrioPlayersLeaderboard pseudoLb = await fetchTLLeaderboard(after: after); - leaderboard.addPlayers(pseudoLb.leaderboard); - lbLength = pseudoLb.leaderboard.length; - after = pseudoLb.leaderboard.last.tr; - yield leaderboard; - } - } + // Stream fetchFullLeaderboard() async* { + // late double after; + // int lbLength = 100; + // TetrioPlayersLeaderboard leaderboard = await fetchTLLeaderboard(); + // after = leaderboard.leaderboard.last.tr; + // while (lbLength == 100){ + // TetrioPlayersLeaderboard pseudoLb = await fetchTLLeaderboard(after: after); + // leaderboard.addPlayers(pseudoLb.leaderboard); + // lbLength = pseudoLb.leaderboard.length; + // after = pseudoLb.leaderboard.last.tr; + // yield leaderboard; + // } + // } // i want to know progress, so i trying to figure out this thing: // Stream fetchTLLeaderboardAsStream() async { diff --git a/lib/views/main_view.dart b/lib/views/main_view.dart index b160e99..df6fe00 100644 --- a/lib/views/main_view.dart +++ b/lib/views/main_view.dart @@ -213,7 +213,7 @@ class _MainState extends State with TickerProviderStateMixin { nextRankGlickoCutoff = (summaries.league.rank != "z" ? summaries.league.rank == "x+" : summaries.league.percentileRank == "x+") ? topOne?.glicko??double.infinity : cutoffsGlicko?[ranks.elementAtOrNull(ranks.indexOf(summaries.league.rank != "z" ? summaries.league.rank : summaries.league.percentileRank)+1)]; } - // if (everyone != null && summaries.league.gamesPlayed > 9) rankAverages = everyone?.averages[summaries.league.percentileRank]?[0]; + if (everyone != null && summaries.league.gamesPlayed > 9) rankAverages = everyone?.averages[summaries.league.percentileRank]?[0]; // Making list of Tetra League matches //bool isTracking = await teto.isPlayerTracking(me.userId); @@ -482,8 +482,8 @@ class _MainState extends State with TickerProviderStateMixin { 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, - //averages: rankAverages, - //lbPositions: meAmongEveryone + averages: rankAverages, + lbPositions: meAmongEveryone ), ), SizedBox( @@ -523,8 +523,8 @@ class _MainState extends State with TickerProviderStateMixin { 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, - //averages: rankAverages, - //lbPositions: meAmongEveryone + averages: rankAverages, + lbPositions: meAmongEveryone ), _TLRecords(userID: snapshot.data![0].userId, changePlayer: changePlayer, data: snapshot.data![3].records, wasActiveInTL: true, oldMathcesHere: _TLHistoryWasFetched, separateScrollController: true), _History(chartsData: chartsData, changePlayer: changePlayer, userID: _searchFor, update: _justUpdate, wasActiveInTL: snapshot.data![1].league.gamesPlayed > 0), diff --git a/lib/views/rank_averages_view.dart b/lib/views/rank_averages_view.dart index 4ff0446..557bda3 100644 --- a/lib/views/rank_averages_view.dart +++ b/lib/views/rank_averages_view.dart @@ -379,6 +379,8 @@ class RankState extends State with SingleTickerProviderStateMixin { child: ListView( children: [ _ListEntry(value: widget.rank[1]["lowestTR"], label: t.statCellNum.tr.replaceAll(RegExp(r'\n'), " "), id: widget.rank[1]["lowestTRid"], username: widget.rank[1]["lowestTRnick"], approximate: false, fractionDigits: 2), + _ListEntry(value: widget.rank[1]["lowestGlixare"], label: "Glixare", id: widget.rank[1]["lowestGlixareID"], username: widget.rank[1]["lowestGlixareNick"], approximate: false, fractionDigits: 3), + _ListEntry(value: widget.rank[1]["lowestS1tr"], label: "S1 ${t.statCellNum.tr.replaceAll(RegExp(r'\n'), " ")}", id: widget.rank[1]["lowestS1trID"], username: widget.rank[1]["lowestS1trNick"], approximate: false, fractionDigits: 2), _ListEntry(value: widget.rank[1]["lowestGlicko"], label: "Glicko", id: widget.rank[1]["lowestGlickoID"], username: widget.rank[1]["lowestGlickoNick"], approximate: false, fractionDigits: 2), _ListEntry(value: widget.rank[1]["lowestRD"], label: t.statCellNum.rd.replaceAll(RegExp(r'\n'), " "), id: widget.rank[1]["lowestRdID"], username: widget.rank[1]["lowestRdNick"], approximate: false, fractionDigits: 3), _ListEntry(value: widget.rank[1]["lowestGamesPlayed"], label: t.statCellNum.gamesPlayed.replaceAll(RegExp(r'\n'), " "), id: widget.rank[1]["lowestGamesPlayedID"], username: widget.rank[1]["lowestGamesPlayedNick"], approximate: false), @@ -413,6 +415,8 @@ class RankState extends State with SingleTickerProviderStateMixin { Expanded( child: ListView(children: [ _ListEntry(value: widget.rank[0].tr, label: t.statCellNum.tr.replaceAll(RegExp(r'\n'), " "), id: "", username: "", approximate: true, fractionDigits: 2), + _ListEntry(value: widget.rank[0].gxe, label: "Glixare", id: "", username: "", approximate: false, fractionDigits: 3), + _ListEntry(value: widget.rank[0].s1tr, label: "S1 ${t.statCellNum.tr.replaceAll(RegExp(r'\n'), " ")}", id: "", username: "", approximate: false, fractionDigits: 2), _ListEntry(value: widget.rank[0].glicko, label: "Glicko", id: "", username: "", approximate: true, fractionDigits: 2), _ListEntry(value: widget.rank[0].rd, label: t.statCellNum.rd.replaceAll(RegExp(r'\n'), " "), id: "", username: "", approximate: true, fractionDigits: 3), _ListEntry(value: widget.rank[0].gamesPlayed, label: t.statCellNum.gamesPlayed.replaceAll(RegExp(r'\n'), " "), id: "", username: "", approximate: true, fractionDigits: 0), @@ -446,6 +450,8 @@ class RankState extends State with SingleTickerProviderStateMixin { child: ListView( children: [ _ListEntry(value: widget.rank[1]["highestTR"], label: t.statCellNum.tr.replaceAll(RegExp(r'\n'), " "), id: widget.rank[1]["highestTRid"], username: widget.rank[1]["highestTRnick"], approximate: false, fractionDigits: 2), + _ListEntry(value: widget.rank[1]["highestGlixare"], label: "Glixare", id: widget.rank[1]["highestGlixareID"], username: widget.rank[1]["highestGlixareNick"], approximate: false, fractionDigits: 3), + _ListEntry(value: widget.rank[1]["highestS1tr"], label: "S1 ${t.statCellNum.tr.replaceAll(RegExp(r'\n'), " ")}", id: widget.rank[1]["highestS1trID"], username: widget.rank[1]["highestS1trNick"], approximate: false, fractionDigits: 2), _ListEntry(value: widget.rank[1]["highestGlicko"], label: "Glicko", id: widget.rank[1]["highestGlickoID"], username: widget.rank[1]["highestGlickoNick"], approximate: false, fractionDigits: 2), _ListEntry(value: widget.rank[1]["highestRD"], label: t.statCellNum.rd.replaceAll(RegExp(r'\n'), " "), id: widget.rank[1]["highestRdID"], username: widget.rank[1]["highestRdNick"], approximate: false, fractionDigits: 3), _ListEntry(value: widget.rank[1]["highestGamesPlayed"], label: t.statCellNum.gamesPlayed.replaceAll(RegExp(r'\n'), " "), id: widget.rank[1]["highestGamesPlayedID"], username: widget.rank[1]["highestGamesPlayedNick"], approximate: false), @@ -517,7 +523,7 @@ class _ListEntry extends StatelessWidget { children: [ Text(f.format(value), style: const TextStyle(fontSize: 22, height: 0.9)), - if (id.isNotEmpty) Text(t.forPlayer(username: username)) + if (id.isNotEmpty) Text(t.forPlayer(username: username), style: TextStyle(color: Colors.grey, fontWeight: FontWeight.w100),) ], ), onTap: id.isNotEmpty diff --git a/lib/views/tl_leaderboard_view.dart b/lib/views/tl_leaderboard_view.dart index b3c181d..0a3d87e 100644 --- a/lib/views/tl_leaderboard_view.dart +++ b/lib/views/tl_leaderboard_view.dart @@ -4,13 +4,13 @@ import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:tetra_stats/data_objects/tetrio.dart'; import 'package:tetra_stats/gen/strings.g.dart'; -import 'package:tetra_stats/services/tetrio_crud.dart'; +import 'package:tetra_stats/main.dart'; import 'package:tetra_stats/views/main_view.dart'; import 'package:tetra_stats/views/rank_averages_view.dart'; import 'package:tetra_stats/views/ranks_averages_view.dart'; import 'package:window_manager/window_manager.dart'; +import 'package:tetra_stats/widgets/text_timestamp.dart'; -final TetrioService _teto = TetrioService(); List _itemStats = [for (MapEntry e in chartsShortTitles.entries) DropdownMenuItem(value: e.key, child: Text(e.value))]; Stats _sortBy = Stats.tr; bool reversed = false; @@ -64,148 +64,155 @@ class TLLeaderboardState extends State { ), backgroundColor: Colors.black, body: SafeArea( - child: FutureBuilder( - future: _teto.fetchTLLeaderboard(), - builder: (context, snapshot) { - switch (snapshot.connectionState) { - case ConnectionState.none: - case ConnectionState.waiting: - case ConnectionState.active: - return const Center(child: CircularProgressIndicator()); - case ConnectionState.done: - final allPlayers = snapshot.data?.getStatRanking(snapshot.data!.leaderboard, _sortBy, reversed: reversed, country: _country); - if (!kIsWeb && !Platform.isAndroid && !Platform.isIOS) windowManager.setTitle("Tetra Stats: ${t.tlLeaderboard} - ${t.players(n: allPlayers != null ? allPlayers.length : 0)}"); - bool bigScreen = MediaQuery.of(context).size.width > 768; - return NestedScrollView( - headerSliverBuilder: (context, value) { - String howManyPlayers(int numberOfPlayers) => Intl.plural( - numberOfPlayers, - zero: t.lbViewZeroEntrys, - one: t.lbViewOneEntry, - other: t.lbViewManyEntrys(numberOfPlayers: t.players(n: numberOfPlayers)), - name: 'howManyPeople', - args: [numberOfPlayers], - desc: 'Description of how many people are seen in a place.', - examples: const {'numberOfPeople': 3}, - ); - return [ - SliverToBoxAdapter( - child: Padding( - padding: const EdgeInsets.only(left: 16), - child: Wrap( - direction: Axis.horizontal, - alignment: WrapAlignment.spaceBetween, - children: [ - Text( - howManyPlayers(allPlayers.length), - style: const TextStyle(color: Colors.white, fontSize: 25), - ), - TextButton(onPressed: (){ - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => RankView(rank: snapshot.data!.getAverageOfRank("")), - ), - ); - }, child: Text(t.everyoneAverages, - style: const TextStyle(fontSize: 25))) - ],) - )), - SliverToBoxAdapter(child: Padding( - padding: const EdgeInsets.only(left: 16), - child: Wrap( - direction: Axis.horizontal, - alignment: WrapAlignment.start, - crossAxisAlignment: WrapCrossAlignment.center, - spacing: 16, - children: [ - Row( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.baseline, - textBaseline: TextBaseline.alphabetic, - children: [ - Text("${t.sortBy}: ", - style: const TextStyle(color: Colors.white, fontSize: 25)), - DropdownButton(items: _itemStats, value: _sortBy, onChanged: ((value) { - _sortBy = value; - setState(() {}); - }),), - ], - ), - Row( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.baseline, - textBaseline: TextBaseline.alphabetic, - children: [ - Text("${t.reversed}: ", - style: const TextStyle(color: Colors.white, fontSize: 25)), - Padding( - padding: const EdgeInsets.fromLTRB(0, 5.5, 0, 7.5), - child: Checkbox(value: reversed, - checkColor: Colors.black, - onChanged: ((value) { - reversed = value!; - setState(() {}); - }),), - ), - ], - ), - Row( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.baseline, - textBaseline: TextBaseline.alphabetic, - children: [ - Text("${t.country}: ", - style: const TextStyle(color: Colors.white, fontSize: 25)), - DropdownButton(items: _itemCountries, value: _country, onChanged: ((value) { - _country = value; - setState(() {}); - }),), - ], - ), - ], - ), - ),), - const SliverToBoxAdapter(child: Divider()) - ]; - }, - body: ListView.builder( - itemCount: allPlayers!.length, - prototypeItem: ListTile( - leading: Text("0", style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 28 : 24, height: 0.9)), - title: Text("ehhh...", style: TextStyle(fontFamily: bigScreen ? "Eurostile Round Extended" : "Eurostile Round", height: 0.9)), - trailing: SizedBox(height: bigScreen ? 48 : 36, width: 1,), - subtitle: const Text("eh..."), + child: FutureBuilder( + future: teto.fetchTLLeaderboard(), + 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){ + final allPlayers = snapshot.data?.getStatRanking(snapshot.data!.leaderboard, _sortBy, reversed: reversed, country: _country); + if (!kIsWeb && !Platform.isAndroid && !Platform.isIOS) windowManager.setTitle("Tetra Stats: ${t.tlLeaderboard} - ${t.players(n: allPlayers != null ? allPlayers.length : 0)}"); + bool bigScreen = MediaQuery.of(context).size.width > 768; + return NestedScrollView( + headerSliverBuilder: (context, value) { + return [ + SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.only(left: 16), + child: Wrap( + direction: Axis.horizontal, + alignment: WrapAlignment.spaceBetween, + children: [ + Text( + "${t.players(n: allPlayers.length)} • ${t.sprintAndBlitsRelevance(date: timestamp(snapshot.data!.timestamp))}", + style: const TextStyle(color: Colors.white, fontSize: 25), + ), + TextButton(onPressed: (){ + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => RankView(rank: snapshot.data!.getAverageOfRank("")), ), - itemBuilder: (context, index) { - return ListTile( - leading: Text( - (index+1).toString(), - style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 28 : 24, height: 0.9) + ); + }, child: Text(t.everyoneAverages, + style: const TextStyle(fontSize: 25))) + ],) + )), + SliverToBoxAdapter(child: Padding( + padding: const EdgeInsets.only(left: 16), + child: Wrap( + direction: Axis.horizontal, + alignment: WrapAlignment.start, + crossAxisAlignment: WrapCrossAlignment.center, + spacing: 16, + children: [ + Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.baseline, + textBaseline: TextBaseline.alphabetic, + children: [ + Text("${t.sortBy}: ", + style: const TextStyle(color: Colors.white, fontSize: 25)), + DropdownButton(items: _itemStats, value: _sortBy, onChanged: ((value) { + _sortBy = value; + setState(() {}); + }),), + ], + ), + Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.baseline, + textBaseline: TextBaseline.alphabetic, + children: [ + Text("${t.reversed}: ", + style: const TextStyle(color: Colors.white, fontSize: 25)), + Padding( + padding: const EdgeInsets.fromLTRB(0, 5.5, 0, 7.5), + child: Checkbox(value: reversed, + checkColor: Colors.black, + onChanged: ((value) { + reversed = value!; + setState(() {}); + }),), ), - title: Text(allPlayers[index].username, style: TextStyle(fontFamily: bigScreen ? "Eurostile Round Extended" : "Eurostile Round", height: 0.9)), - subtitle: (bigScreen || _sortBy != Stats.tr) ? Text(_sortBy == Stats.tr ? "${f2.format(allPlayers[index].apm)} APM, ${f2.format(allPlayers[index].pps)} PPS, ${f2.format(allPlayers[index].vs)} VS, ${f2.format(allPlayers[index].nerdStats.app)} APP, ${f2.format(allPlayers[index].nerdStats.vsapm)} VS/APM" : "${_f4.format(allPlayers[index].getStatByEnum(_sortBy))} ${chartsShortTitles[_sortBy]}", - style: TextStyle(fontFamily: "Eurostile Round Condensed", fontSize: bigScreen ? null : 13, color: _sortBy == Stats.tr ? Colors.grey : null)) : null, - trailing: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Text("${f2.format(allPlayers[index].tr)} TR", style: const TextStyle(fontSize: 28)), - Image.asset("res/tetrio_tl_alpha_ranks/${allPlayers[index].rank}.png", height: bigScreen ? 48 : 36), - ], - ), - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => MainView(player: allPlayers[index].userId), - maintainState: false, - ), - ); - }, - ); - })); - } - })), + ], + ), + Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.baseline, + textBaseline: TextBaseline.alphabetic, + children: [ + Text("${t.country}: ", + style: const TextStyle(color: Colors.white, fontSize: 25)), + DropdownButton(items: _itemCountries, value: _country, onChanged: ((value) { + _country = value; + setState(() {}); + }),), + ], + ), + ], + ), + ),), + const SliverToBoxAdapter(child: Divider()) + ]; + }, + body: ListView.builder( + itemCount: allPlayers!.length, + prototypeItem: ListTile( + leading: Text("0", style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 28 : 24, height: 0.9)), + title: Text("ehhh...", style: TextStyle(fontFamily: bigScreen ? "Eurostile Round Extended" : "Eurostile Round", height: 0.9)), + trailing: SizedBox(height: bigScreen ? 48 : 36, width: 1,), + subtitle: const Text("eh..."), + ), + itemBuilder: (context, index) { + return ListTile( + leading: Text( + (index+1).toString(), + style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 28 : 24, height: 0.9) + ), + title: Text(allPlayers[index].username, style: TextStyle(fontFamily: bigScreen ? "Eurostile Round Extended" : "Eurostile Round", height: 0.9)), + subtitle: (bigScreen || _sortBy != Stats.tr) ? Text(_sortBy == Stats.tr ? "${f2.format(allPlayers[index].apm)} APM, ${f2.format(allPlayers[index].pps)} PPS, ${f2.format(allPlayers[index].vs)} VS, ${f2.format(allPlayers[index].nerdStats.app)} APP, ${f2.format(allPlayers[index].nerdStats.vsapm)} VS/APM" : "${_f4.format(allPlayers[index].getStatByEnum(_sortBy))} ${chartsShortTitles[_sortBy]}", + style: TextStyle(fontFamily: "Eurostile Round Condensed", fontSize: bigScreen ? null : 13, color: _sortBy == Stats.tr ? Colors.grey : null)) : null, + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text("${f2.format(allPlayers[index].tr)} TR", style: const TextStyle(fontSize: 28)), + Image.asset("res/tetrio_tl_alpha_ranks/${allPlayers[index].rank}.png", height: bigScreen ? 48 : 36), + ], + ), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => MainView(player: allPlayers[index].userId), + maintainState: false, + ), + ); + }, + ); + })); + } + if (snapshot.hasError){ + return Center(child: + Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text(snapshot.error.toString(), style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 42, fontWeight: FontWeight.bold), textAlign: TextAlign.center), + if (snapshot.stackTrace != null) Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Text(snapshot.stackTrace.toString(), style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 18), textAlign: TextAlign.center), + ), + ], + ) + ); + } + return Text("end of FutureBuilder"); + } + })), ); } } diff --git a/pubspec.yaml b/pubspec.yaml index ea53554..bf491d7 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.5+31 +version: 1.6.6+32 environment: sdk: '>=3.0.0'