From 518f2db7eaa72f719add0badd92c6af6bbc0f61d Mon Sep 17 00:00:00 2001 From: dan63047 Date: Sat, 29 Jul 2023 21:01:49 +0300 Subject: [PATCH] detailed stats for rank (not finished) + bug fix --- lib/data_objects/tetrio.dart | 234 +++++++++++++++-- lib/services/tetrio_crud.dart | 45 +++- lib/views/main_view.dart | 7 +- lib/views/rank_averages_view.dart | 397 +++++++++++++++++++++++++++++ lib/views/ranks_averages_view.dart | 12 +- lib/widgets/tl_thingy.dart | 2 +- 6 files changed, 657 insertions(+), 40 deletions(-) create mode 100644 lib/views/rank_averages_view.dart diff --git a/lib/data_objects/tetrio.dart b/lib/data_objects/tetrio.dart index 0f14b9e..89d2331 100644 --- a/lib/data_objects/tetrio.dart +++ b/lib/data_objects/tetrio.dart @@ -894,32 +894,214 @@ class TetrioPlayersLeaderboard { TetrioPlayersLeaderboard(this.type, this.leaderboard); List getAverageOfRank(String rank){ + if (rank.isNotEmpty && !rankCutoffs.keys.contains(rank)) throw Exception("Invalid rank"); List filtredLeaderboard = List.from(leaderboard); - filtredLeaderboard.removeWhere((element) => element.rank != rank); - if (filtredLeaderboard.isEmpty) throw Exception("Invalid rank"); - double avgAPM = 0, avgPPS = 0, avgVS = 0, avgTR = 0, avgGlicko = 0, avgRD = 0, lowestTR = 25000; - int avgGamesPlayed = 0, avgGamesWon = 0, totalGamesPlayed = 0, totalGamesWon = 0; - for (var entry in filtredLeaderboard){ - avgAPM += entry.apm; - avgPPS += entry.pps; - avgVS += entry.vs; - avgTR += entry.rating; - avgGlicko += entry.glicko; - avgRD += entry.rd; - totalGamesPlayed += entry.gamesPlayed; - totalGamesWon += entry.gamesWon; - if (entry.rating < lowestTR) lowestTR = entry.rating; + if (rank.isNotEmpty) { + filtredLeaderboard.removeWhere((element) => element.rank != rank); + } else { + rank = "z"; + } + if (filtredLeaderboard.isNotEmpty){ + double avgAPM = 0, + avgPPS = 0, + avgVS = 0, + avgTR = 0, + avgGlicko = 0, + avgRD = 0, + lowestTR = 25000, + lowestGlicko = double.infinity, + lowestWinrate = double.infinity, + lowestAPM = double.infinity, + lowestPPS = double.infinity, + lowestVS = double.infinity, + highestTR = 0, + highestGlicko = 0, + highestWinrate = 0, + highestAPM = 0, + highestPPS = 0, + highestVS = 0; + int avgGamesPlayed = 0, + avgGamesWon = 0, + totalGamesPlayed = 0, + totalGamesWon = 0, + lowestGamesPlayed = pow(2, 53) as int, + lowestGamesWon = pow(2, 53) as int, + highestGamesPlayed = 0, + highestGamesWon = 0; + String lowestTRid = "", lowestTRnick = "", + lowestGlickoID = "", lowestGlickoNick = "", + lowestGamesPlayedID = "", lowestGamesPlayedNick = "", + lowestGamesWonID = "", lowestGamesWonNick = "", + lowestWinrateID = "", lowestWinrateNick = "", + lowestAPMid = "", lowestAPMnick = "", + lowestPPSid = "", lowestPPSnick = "", + lowestVSid = "", lowestVSnick = "", + highestTRid = "", highestTRnick = "", + highestGlickoID = "", highestGlickoNick = "", + highestGamesPlayedID = "", highestGamesPlayedNick = "", + highestGamesWonID = "", highestGamesWonNick = "", + highestWinrateID = "", highestWinrateNick = "", + highestAPMid = "", highestAPMnick = "", + highestPPSid = "", highestPPSnick = "", + highestVSid = "", highestVSnick = ""; + for (var entry in filtredLeaderboard){ + avgAPM += entry.apm; + avgPPS += entry.pps; + avgVS += entry.vs; + avgTR += entry.rating; + avgGlicko += entry.glicko; + avgRD += entry.rd; + totalGamesPlayed += entry.gamesPlayed; + totalGamesWon += entry.gamesWon; + if (entry.rating < lowestTR){ + lowestTR = entry.rating; + lowestTRid = entry.userId; + lowestTRnick = entry.username; + } + if (entry.glicko < lowestGlicko){ + lowestGlicko = entry.glicko; + lowestGlickoID = entry.userId; + lowestGlickoNick = entry.username; + } + if (entry.gamesPlayed < lowestGamesPlayed){ + lowestGamesPlayed = entry.gamesPlayed; + lowestGamesPlayedID = entry.userId; + lowestGamesPlayedNick = entry.username; + } + if (entry.gamesWon < lowestGamesWon){ + lowestGamesWon = entry.gamesWon; + lowestGamesWonID = entry.userId; + lowestGamesWonNick = entry.username; + } + if (entry.winrate < lowestWinrate){ + lowestWinrate = entry.winrate; + lowestWinrateID = entry.userId; + lowestWinrateNick = entry.username; + } + if (entry.apm < lowestAPM){ + lowestAPM = entry.apm; + lowestAPMid = entry.userId; + lowestAPMnick = entry.username; + } + if (entry.pps < lowestPPS){ + lowestPPS = entry.pps; + lowestPPSid = entry.userId; + lowestPPSnick = entry.username; + } + if (entry.vs < lowestVS){ + lowestVS = entry.vs; + lowestVSid = entry.userId; + lowestVSnick = entry.username; + } + if (entry.rating > highestTR){ + highestTR = entry.rating; + highestTRid = entry.userId; + highestTRnick = entry.username; + } + if (entry.glicko > highestGlicko){ + highestGlicko = entry.glicko; + highestGlickoID = entry.userId; + highestGlickoNick = entry.username; + } + if (entry.gamesPlayed > highestGamesPlayed){ + highestGamesPlayed = entry.gamesPlayed; + highestGamesPlayedID = entry.userId; + highestGamesPlayedNick = entry.username; + } + if (entry.gamesWon > highestGamesWon){ + highestGamesWon = entry.gamesWon; + highestGamesWonID = entry.userId; + highestGamesWonNick = entry.username; + } + if (entry.winrate > highestWinrate){ + highestWinrate = entry.winrate; + highestWinrateID = entry.userId; + highestWinrateNick = entry.username; + } + if (entry.apm > highestAPM){ + highestAPM = entry.apm; + highestAPMid = entry.userId; + highestAPMnick = entry.username; + } + if (entry.pps > highestPPS){ + highestPPS = entry.pps; + highestPPSid = entry.userId; + highestPPSnick = entry.username; + } + if (entry.vs > highestVS){ + highestVS = entry.vs; + highestVSid = entry.userId; + highestVSnick = entry.username; + } + } + avgAPM /= filtredLeaderboard.length; + avgPPS /= filtredLeaderboard.length; + avgVS /= filtredLeaderboard.length; + avgTR /= filtredLeaderboard.length; + avgGlicko /= filtredLeaderboard.length; + avgRD /= filtredLeaderboard.length; + avgGamesPlayed = (totalGamesPlayed / filtredLeaderboard.length).floor(); + avgGamesWon = (totalGamesWon / filtredLeaderboard.length).floor(); + return [TetraLeagueAlpha(timestamp: DateTime.now(), apm: avgAPM, pps: avgPPS, vs: avgVS, glicko: avgGlicko, rd: avgRD, gamesPlayed: avgGamesPlayed, gamesWon: avgGamesWon, bestRank: rank, decaying: false, rating: avgTR, rank: rank, percentileRank: rank, percentile: rankCutoffs[rank]!, standing: -1, standingLocal: -1, nextAt: -1, prevAt: -1), + { + "totalGamesPlayed": totalGamesPlayed, + "totalGamesWon": totalGamesWon, + "players": filtredLeaderboard.length, + "lowestTR": lowestTR, + "lowestTRid": lowestTRid, + "lowestTRnick": lowestTRnick, + "lowestGlicko": lowestGlicko, + "lowestGlickoID": lowestGlickoID, + "lowestGlickoNick": lowestGlickoNick, + "lowestGamesPlayed": lowestGamesPlayed, + "lowestGamesPlayedID": lowestGamesPlayedID, + "lowestGamesPlayedNick": lowestGamesPlayedNick, + "lowestGamesWon": lowestGamesWon, + "lowestGamesWonID": lowestGamesWonID, + "lowestGamesWonNick": lowestGamesWonNick, + "lowestWinrate": lowestWinrate, + "lowestWinrateID": lowestWinrateID, + "lowestWinrateNick": lowestWinrateNick, + "lowestAPM": lowestAPM, + "lowestAPMid": lowestAPMid, + "lowestAPMnick": lowestAPMnick, + "lowestPPS": lowestPPS, + "lowestPPSid": lowestPPSid, + "lowestPPSnick": lowestPPSnick, + "lowestVS": lowestVS, + "lowestVSid": lowestVSid, + "lowestVSnick": lowestVSnick, + "highestTR": highestTR, + "highestTRid": highestTRid, + "highestTRnick": highestTRnick, + "highestGlicko": highestGlicko, + "highestGlickoID": highestGlickoID, + "highestGlickoNick": highestGlickoNick, + "highestGamesPlayed": highestGamesPlayed, + "highestGamesPlayedID": highestGamesPlayedID, + "highestGamesPlayedNick": highestGamesPlayedNick, + "highestGamesWon": highestGamesWon, + "highestGamesWonID": highestGamesWonID, + "highestGamesWonNick": highestGamesWonNick, + "highestWinrate": highestWinrate, + "highestWinrateID": highestWinrateID, + "highestWinrateNick": highestWinrateNick, + "highestAPM": highestAPM, + "highestAPMid": highestAPMid, + "highestAPMnick": highestAPMnick, + "highestPPS": highestPPS, + "highestPPSid": highestPPSid, + "highestPPSnick": highestPPSnick, + "highestVS": highestVS, + "highestVSid": highestVSid, + "highestVSnick": highestVSnick, + "toEnterTR": rank.toLowerCase() != "z" ? leaderboard[(leaderboard.length * rankCutoffs[rank]!).floor()-1].rating : lowestTR, + "entries": filtredLeaderboard + }]; + }else{ + return [TetraLeagueAlpha(timestamp: DateTime.now(), apm: 0, pps: 0, vs: 0, glicko: 0, rd: noTrRd, gamesPlayed: 0, gamesWon: 0, bestRank: rank, decaying: false, rating: 0, rank: rank, percentileRank: rank, percentile: rankCutoffs[rank]!, standing: -1, standingLocal: -1, nextAt: -1, prevAt: -1), + {"players": filtredLeaderboard.length, "lowestTR": 0, "toEnterTR": 0}]; } - avgAPM /= filtredLeaderboard.length; - avgPPS /= filtredLeaderboard.length; - avgVS /= filtredLeaderboard.length; - avgTR /= filtredLeaderboard.length; - avgGlicko /= filtredLeaderboard.length; - avgRD /= filtredLeaderboard.length; - avgGamesPlayed = (totalGamesPlayed / filtredLeaderboard.length).floor(); - avgGamesWon = (totalGamesWon / filtredLeaderboard.length).floor(); - return [TetraLeagueAlpha(timestamp: DateTime.now(), apm: avgAPM, pps: avgPPS, vs: avgVS, glicko: avgGlicko, rd: avgRD, gamesPlayed: avgGamesPlayed, gamesWon: avgGamesWon, bestRank: rank, decaying: false, rating: avgTR, rank: rank, percentileRank: rank, percentile: 0, standing: -1, standingLocal: -1, nextAt: -1, prevAt: -1), - {"totalGamesPlayed": totalGamesPlayed, "totalGamesWon": totalGamesWon, "players": filtredLeaderboard.length, "lowestTR": lowestTR, "toEnterTR": leaderboard[(leaderboard.length * rankCutoffs[rank]!).floor()-1].rating}]; } Map> get averages => { @@ -939,7 +1121,8 @@ class TetrioPlayersLeaderboard { 'c': getAverageOfRank("c"), 'c-': getAverageOfRank("c-"), 'd+': getAverageOfRank("d+"), - 'd': getAverageOfRank("d") + 'd': getAverageOfRank("d"), + 'z': getAverageOfRank("z") }; TetrioPlayersLeaderboard.fromJson(List json, String t, DateTime ts) { @@ -994,6 +1177,7 @@ class TetrioPlayerFromLeaderboard { this.vs, this.decaying); + double get winrate => gamesWon / gamesPlayed; get app => apm / (pps * 60); get vsapm => vs / apm; diff --git a/lib/services/tetrio_crud.dart b/lib/services/tetrio_crud.dart index 6264975..873b624 100644 --- a/lib/services/tetrio_crud.dart +++ b/lib/services/tetrio_crud.dart @@ -135,7 +135,7 @@ class TetrioService extends DB { ); history.add(state); } - ensureDbIsOpen(); + await ensureDbIsOpen(); final db = getDatabaseOrThrow(); late List states; try{ @@ -237,7 +237,7 @@ class TetrioService extends DB { } Future saveTLMatchesFromStream(TetraLeagueAlphaStream stream) async { - ensureDbIsOpen(); + await ensureDbIsOpen(); final db = getDatabaseOrThrow(); for (TetraLeagueAlphaRecord match in stream.records) { final results = await db.query(tetraLeagueMatchesTable, where: '$idCol = ?', whereArgs: [match.ownId]); @@ -247,7 +247,7 @@ class TetrioService extends DB { } Future> getTLMatchesbyPlayerID(String playerID) async { - ensureDbIsOpen(); + await ensureDbIsOpen(); final db = getDatabaseOrThrow(); List matches = []; final results = await db.query(tetraLeagueMatchesTable, where: '($player1id = ?) OR ($player2id = ?)', whereArgs: [playerID, playerID]); @@ -304,7 +304,7 @@ class TetrioService extends DB { } Future createPlayer(TetrioPlayer tetrioPlayer) async { - ensureDbIsOpen(); + await ensureDbIsOpen(); final db = getDatabaseOrThrow(); final results = await db.query(tetrioUsersTable, limit: 1, where: '$idCol = ?', whereArgs: [tetrioPlayer.userId.toLowerCase()]); if (results.isNotEmpty) { @@ -319,7 +319,7 @@ class TetrioService extends DB { } Future addPlayerToTrack(TetrioPlayer tetrioPlayer) async { - ensureDbIsOpen(); + await ensureDbIsOpen(); final db = getDatabaseOrThrow(); final results = await db.query(tetrioUsersToTrackTable, where: '$idCol = ?', whereArgs: [tetrioPlayer.userId.toLowerCase()]); if (results.isNotEmpty) { @@ -329,7 +329,7 @@ class TetrioService extends DB { } Future isPlayerTracking(String id) async { - ensureDbIsOpen(); + await ensureDbIsOpen(); final db = getDatabaseOrThrow(); final results = await db.query(tetrioUsersToTrackTable, where: '$idCol = ?', whereArgs: [id.toLowerCase()]); if (results.isEmpty) { @@ -360,12 +360,11 @@ class TetrioService extends DB { } Future storeState(TetrioPlayer tetrioPlayer) async { - ensureDbIsOpen(); + await ensureDbIsOpen(); final db = getDatabaseOrThrow(); late List states; try { states = _players[tetrioPlayer.userId]!; - //states = await getPlayer(tetrioPlayer.userId); } catch (e) { await createPlayer(tetrioPlayer); states = await getPlayer(tetrioPlayer.userId); @@ -383,7 +382,7 @@ class TetrioService extends DB { } Future deleteState(TetrioPlayer tetrioPlayer) async { - ensureDbIsOpen(); + await ensureDbIsOpen(); final db = getDatabaseOrThrow(); late List states; states = await getPlayer(tetrioPlayer.userId); @@ -400,7 +399,7 @@ class TetrioService extends DB { } Future> getPlayer(String id) async { - ensureDbIsOpen(); + await ensureDbIsOpen(); final db = getDatabaseOrThrow(); List states = []; final results = await db.query(tetrioUsersTable, limit: 1, where: '$idCol = ?', whereArgs: [id.toLowerCase()]); @@ -417,7 +416,7 @@ class TetrioService extends DB { } } - Future fetchPlayer(String user) async { + Future fetchPlayer(String user, {bool isItDiscordID = false}) async { try{ var cached = _playersCache.entries.firstWhere((element) => element.value.userId == user || element.value.username == user); if (DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true).isAfter(DateTime.now())){ @@ -430,7 +429,29 @@ class TetrioService extends DB { }catch(e){ developer.log("fetchPlayer: Trying to retrieve $user", name: "services/tetrio_crud"); } - + + if (isItDiscordID){ + Uri dUrl; + if (kIsWeb) { + dUrl = Uri.https('ts.dan63.by', 'oskware_bridge.php', {"endpoint": "tetrioUserByDiscordID", "user": user.toLowerCase().trim()}); + } else { + dUrl = Uri.https('ch.tetr.io', 'api/users/search/${user.toLowerCase().trim()}'); + } + final response = await http.get(dUrl); + if (response.statusCode == 200) { + var json = jsonDecode(response.body); + if (json['success'] && json['data'] != null) { + user = json['data']['user']['_id']; + } else { + developer.log("fetchPlayer User dosen't exist", name: "services/tetrio_crud", error: response.body); + throw TetrioPlayerNotExist(); + } + } else { + developer.log("fetchPlayer Failed to fetch player", name: "services/tetrio_crud", error: response.statusCode); + throw ConnectionIssue(response.statusCode, response.reasonPhrase??"No reason"); + } + } + Uri url; if (kIsWeb) { url = Uri.https('ts.dan63.by', 'oskware_bridge.php', {"endpoint": "tetrioUser", "user": user.toLowerCase().trim()}); diff --git a/lib/views/main_view.dart b/lib/views/main_view.dart index 3eb4a25..5728b81 100644 --- a/lib/views/main_view.dart +++ b/lib/views/main_view.dart @@ -116,7 +116,12 @@ class _MainState extends State with SingleTickerProviderStateMixin { } Future fetch(String nickOrID, {bool fetchHistory = false}) async { - TetrioPlayer me = await teto.fetchPlayer(nickOrID); + TetrioPlayer me; + if (nickOrID.startsWith("ds:")){ + me = await teto.fetchPlayer(nickOrID.substring(3), isItDiscordID: true); + }else{ + me = await teto.fetchPlayer(nickOrID); + } _searchFor = me.userId; setState((){_titleNickname = me.username;}); var tlStream = await teto.getTLStream(me.userId); diff --git a/lib/views/rank_averages_view.dart b/lib/views/rank_averages_view.dart new file mode 100644 index 0000000..b30aa6b --- /dev/null +++ b/lib/views/rank_averages_view.dart @@ -0,0 +1,397 @@ +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/views/main_view.dart'; +import 'package:tetra_stats/widgets/stat_sell_num.dart'; +//import 'package:tetra_stats/widgets/tl_thingy.dart'; + +final DateFormat dateFormat = + DateFormat.yMMMd(LocaleSettings.currentLocale.languageCode).add_Hms(); +double pfpHeight = 128; + +class RankView extends StatefulWidget { + final List rank; + const RankView({Key? key, required this.rank}) : super(key: key); + + @override + State createState() => RankState(); +} + +class RankState extends State with SingleTickerProviderStateMixin { + late ScrollController _scrollController; + late TabController _tabController; + + @override + void initState() { + _scrollController = ScrollController(); + _tabController = TabController(length: 6, vsync: this); + super.initState(); + } + + @override + void dispose() { + _tabController.dispose(); + _scrollController.dispose(); + super.dispose(); + } + + void _justUpdate() { + setState(() {}); + } + + @override + Widget build(BuildContext context) { + final t = Translations.of(context); + bool bigScreen = MediaQuery.of(context).size.width > 768; + return Scaffold( + appBar: AppBar( + title: Text(widget.rank[0].rank.toUpperCase()), + ), + backgroundColor: Colors.black, + body: SafeArea( + child: NestedScrollView( + controller: _scrollController, + headerSliverBuilder: (context, value) { + return [ + SliverToBoxAdapter( + child: Column( + children: [ + Flex( + direction: Axis.vertical, + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Stack( + alignment: Alignment.topCenter, + children: [ + Container( + padding: + EdgeInsets.fromLTRB(0, pfpHeight, 0, 0), + child: Image.asset( + "res/tetrio_tl_alpha_ranks/${widget.rank[0].rank}.png", + fit: BoxFit.fitHeight, + height: 128), + ), + ], + ), + Flexible( + child: Column( + children: [ + Text( + "Values for ${widget.rank[0].rank.toUpperCase()} rank", + style: TextStyle( + fontFamily: "Eurostile Round Extended", + fontSize: bigScreen ? 42 : 28)), + Text( + "${widget.rank[1]["entries"].length} players", + style: TextStyle( + fontFamily: "Eurostile Round Extended", + fontSize: bigScreen ? 42 : 28)), + ], + )), + ], + ), + Wrap( + direction: Axis.horizontal, + alignment: WrapAlignment.center, + spacing: 25, + crossAxisAlignment: WrapCrossAlignment.start, + clipBehavior: Clip.hardEdge, // hard WHAT??? + children: [ + StatCellNum( + playerStat: widget.rank[1]["totalGamesPlayed"], + playerStatLabel: "Total games\nplayed", + isScreenBig: bigScreen, + higherIsBetter: true, + ), + StatCellNum( + playerStat: widget.rank[1]["totalGamesWon"], + playerStatLabel: "Total games\nwon", + isScreenBig: bigScreen, + higherIsBetter: true, + ), + StatCellNum( + playerStat: (widget.rank[1]["totalGamesWon"] / + widget.rank[1]["totalGamesPlayed"]) * + 100, + playerStatLabel: t.statCellNum.winrate, + fractionDigits: 3, + isScreenBig: bigScreen, + higherIsBetter: true) + ], + ), + ], + )), + SliverToBoxAdapter( + child: TabBar( + controller: _tabController, + isScrollable: true, + tabs: const [ + Tab(text: "Chart"), + Tab(text: "Entries"), + Tab(text: "Minimums"), + Tab(text: "Averages"), + Tab(text: "Maximums"), + Tab(text: "Other"), + ], + )), + ]; + }, + body: TabBarView( + controller: _tabController, + children: [ + Column( + children: [ + Text("Chart", + style: TextStyle( + fontFamily: "Eurostile Round Extended", + fontSize: bigScreen ? 42 : 28)), + ], + ), + Column( + children: [ + Text("Entries", + style: TextStyle( + fontFamily: "Eurostile Round Extended", + fontSize: bigScreen ? 42 : 28)), + Expanded( + child: ListView.builder( + itemCount: widget.rank[1]["entries"]!.length, + itemBuilder: (context, index) { + bool bigScreen = + MediaQuery.of(context).size.width > 768; + return ListTile( + title: Text( + widget.rank[1]["entries"][index].username, + style: const TextStyle( + fontFamily: + "Eurostile Round Extended")), + subtitle: Text( + "${f2.format(widget.rank[1]["entries"][index].apm)} APM, ${f2.format(widget.rank[1]["entries"][index].pps)} PPS, ${f2.format(widget.rank[1]["entries"][index].vs)} VS, ${f2.format(widget.rank[1]["entries"][index].app)} APP, ${f2.format(widget.rank[1]["entries"][index].vsapm)} VS/APM"), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "${f2.format(widget.rank[1]["entries"][index].rating)} TR", + style: bigScreen + ? const TextStyle(fontSize: 28) + : null), + Image.asset( + "res/tetrio_tl_alpha_ranks/${widget.rank[1]["entries"][index].rank}.png", + height: bigScreen ? 48 : 16), + ], + ), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => MainView( + player: widget + .rank[1]["entries"][index] + .userId), + maintainState: false, + ), + ); + }, + ); + }), + ) + ], + ), + Column( + children: [ + Text("Lowest Values", + style: TextStyle( + fontFamily: "Eurostile Round Extended", + fontSize: bigScreen ? 42 : 28)), + Expanded( + child: ListView( + children: [ + _ListEntry( + value: widget.rank[1]["lowestTR"], + label: "Tetra Rating", + id: widget.rank[1]["lowestTRid"], + username: widget.rank[1]["lowestTRnick"], + 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]["lowestGamesPlayed"], + label: "Games Played", + id: widget.rank[1]["lowestGamesPlayedID"], + username: widget.rank[1] + ["lowestGamesPlayedNick"], + approximate: false), + _ListEntry( + value: widget.rank[1]["lowestGamesWon"], + label: "Games Won", + id: widget.rank[1]["lowestGamesWonID"], + username: widget.rank[1] + ["lowestGamesWonNick"], + approximate: false), + _ListEntry( + value: widget.rank[1]["lowestWinrate"] * 100, + label: "Winrate Percentage", + id: widget.rank[1]["lowestWinrateID"], + username: widget.rank[1]["lowestWinrateNick"], + approximate: false, + fractionDigits: 2), + _ListEntry( + value: widget.rank[1]["lowestAPM"], + label: "Attack Per Minute", + id: widget.rank[1]["lowestAPMid"], + username: widget.rank[1]["lowestAPMnick"], + approximate: false, + fractionDigits: 2), + _ListEntry( + value: widget.rank[1]["lowestPPS"], + label: "Pieces Per Second", + id: widget.rank[1]["lowestPPSid"], + username: widget.rank[1]["lowestPPSnick"], + approximate: false, + fractionDigits: 2), + _ListEntry( + value: widget.rank[1]["lowestVS"], + label: "Versus Score", + id: widget.rank[1]["lowestVSid"], + username: widget.rank[1]["lowestVSnick"], + approximate: false, + fractionDigits: 2) + ], + ), + ), + ], + ), + Column( + children: [], + ), + Column( + children: [ + Text("Highest Values", + style: TextStyle( + fontFamily: "Eurostile Round Extended", + fontSize: bigScreen ? 42 : 28)), + Expanded( + child: ListView( + children: [ + _ListEntry( + value: widget.rank[1]["highestTR"], + label: "Tetra Rating", + id: widget.rank[1]["highestTRid"], + username: widget.rank[1]["highestTRnick"], + 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]["highestGamesPlayed"], + label: "Games Played", + id: widget.rank[1]["highestGamesPlayedID"], + username: widget.rank[1] + ["highestGamesPlayedNick"], + approximate: false), + _ListEntry( + value: widget.rank[1]["highestGamesWon"], + label: "Games Won", + id: widget.rank[1]["highestGamesWonID"], + username: widget.rank[1] + ["highestGamesWonNick"], + approximate: false), + _ListEntry( + value: widget.rank[1]["highestWinrate"] * 100, + label: "Winrate Percentage", + id: widget.rank[1]["highestWinrateID"], + username: widget.rank[1] + ["highestWinrateNick"], + approximate: false, + fractionDigits: 2), + _ListEntry( + value: widget.rank[1]["highestAPM"], + label: "Attack Per Minute", + id: widget.rank[1]["highestAPMid"], + username: widget.rank[1]["highestAPMnick"], + approximate: false, + fractionDigits: 2), + _ListEntry( + value: widget.rank[1]["highestPPS"], + label: "Pieces Per Second", + id: widget.rank[1]["highestPPSid"], + username: widget.rank[1]["highestPPSnick"], + approximate: false, + fractionDigits: 2), + _ListEntry( + value: widget.rank[1]["highestVS"], + label: "Versus Score", + id: widget.rank[1]["highestVSid"], + username: widget.rank[1]["highestVSnick"], + approximate: false, + fractionDigits: 2) + ], + ), + ) + ], + ), + Column( + children: [], + ), + ], + )))); + } +} + +class _ListEntry extends StatelessWidget { + final num value; + final String label; + final String id; + final String username; + final bool approximate; + final int? fractionDigits; + const _ListEntry( + {required this.value, + required this.label, + this.fractionDigits, + required this.id, + required this.username, + required this.approximate}); + + @override + Widget build(BuildContext context) { + NumberFormat f = NumberFormat.decimalPatternDigits( + locale: LocaleSettings.currentLocale.languageCode, + decimalDigits: fractionDigits ?? 0); + return ListTile( + title: Text(label), + trailing: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text(f.format(value), + style: const TextStyle(fontSize: 22, height: 0.9)), + if (id.isNotEmpty) Text('for player $username') + ], + ), + onTap: id.isNotEmpty + ? () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => MainView(player: id), + maintainState: false, + ), + ); + } + : null, + ); + } +} diff --git a/lib/views/ranks_averages_view.dart b/lib/views/ranks_averages_view.dart index 20a7edf..03d49ec 100644 --- a/lib/views/ranks_averages_view.dart +++ b/lib/views/ranks_averages_view.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:tetra_stats/gen/strings.g.dart'; +import 'package:tetra_stats/views/rank_averages_view.dart'; import 'package:tetra_stats/views/tl_leaderboard_view.dart'; class RankAveragesView extends StatefulWidget { @@ -41,7 +42,16 @@ class RanksAverages extends State { leading: Image.asset("res/tetrio_tl_alpha_ranks/${keys[index]}.png", height: 48), title: Text("${averages[keys[index]]?[1]["players"]} players", style: const TextStyle(fontFamily: "Eurostile Round Extended")), subtitle: Text("${f2.format(averages[keys[index]]?[0].apm)} APM, ${f2.format(averages[keys[index]]?[0].pps)} PPS, ${f2.format(averages[keys[index]]?[0].vs)} VS, ${f2.format(averages[keys[index]]?[0].nerdStats.app)} APP, ${f2.format(averages[keys[index]]?[0].nerdStats.vsapm)} VS/APM"), - trailing: Text("${f2.format(averages[keys[index]]?[1]["toEnterTR"])} TR", style: bigScreen ? const TextStyle(fontSize: 28) : null)); + trailing: Text("${f2.format(averages[keys[index]]?[1]["toEnterTR"])} TR", style: bigScreen ? const TextStyle(fontSize: 28) : null), + onTap: (){ + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => RankView(rank: averages[keys[index]]!), + ), + ); + }, + ); }) ), ); diff --git a/lib/widgets/tl_thingy.dart b/lib/widgets/tl_thingy.dart index 2675350..4a80995 100644 --- a/lib/widgets/tl_thingy.dart +++ b/lib/widgets/tl_thingy.dart @@ -67,7 +67,7 @@ class TLThingy extends StatelessWidget { minimum: tl.nextAt.toDouble(), maximum: tl.prevAt.toDouble(), interval: tl.prevAt.toDouble() - tl.nextAt.toDouble(), - ranges: [LinearGaugeRange(startValue: tl.standing.toDouble(), endValue: tl.prevAt.toDouble(), color: Colors.cyanAccent,)], + ranges: [LinearGaugeRange(startValue: tl.standing.toDouble() <= tl.prevAt.toDouble() ? tl.standing.toDouble() : tl.prevAt.toDouble(), endValue: tl.prevAt.toDouble(), color: Colors.cyanAccent,)], //barPointers: [LinearBarPointer(value: 80)], isAxisInversed: true, isMirrored: true,