From 0648ca9a5dcfba05cd0ba6265ca6c26c7dbf1981 Mon Sep 17 00:00:00 2001 From: dan63047 Date: Tue, 5 Mar 2024 01:05:59 +0300 Subject: [PATCH] 40 Lines / Blitz grades and averages + another design changes --- lib/data_objects/tetrio.dart | 42 +++++++++ lib/services/tetrio_crud.dart | 6 ++ lib/views/main_view.dart | 109 ++++++++++++++++++++-- lib/views/settings_view.dart | 14 +++ lib/widgets/list_tile_trailing_stats.dart | 7 +- lib/widgets/stat_sell_num.dart | 4 +- lib/widgets/tl_thingy.dart | 8 +- 7 files changed, 174 insertions(+), 16 deletions(-) diff --git a/lib/data_objects/tetrio.dart b/lib/data_objects/tetrio.dart index 6df9f83..cc7607e 100644 --- a/lib/data_objects/tetrio.dart +++ b/lib/data_objects/tetrio.dart @@ -117,6 +117,48 @@ const Map rankColors = { // thanks osk for const rankColors at ht 'z': Color(0xFF375433) }; +const Map sprintAverages = { // based on https://discord.com/channels/673303546107658242/917098364787650590/1214231970259673098 + 'x': Duration(seconds: 25, milliseconds: 413), + 'u': Duration(seconds: 34, milliseconds: 549), + 'ss': Duration(seconds: 43, milliseconds: 373), + 's+': Duration(seconds: 54, milliseconds: 027), + 's': Duration(seconds: 60, milliseconds: 412), + 's-': Duration(seconds: 67, milliseconds: 381), + 'a+': Duration(seconds: 73, milliseconds: 694), + 'a': Duration(seconds: 81, milliseconds: 166), + 'a-': Duration(seconds: 88, milliseconds: 334), + 'b+': Duration(seconds: 93, milliseconds: 741), + 'b': Duration(seconds: 98, milliseconds: 354), + 'b-': Duration(seconds: 109, milliseconds: 610), + 'c+': Duration(seconds: 124, milliseconds: 641), + 'c': Duration(seconds: 126, milliseconds: 104), + 'c-': Duration(seconds: 145, milliseconds: 865), + 'd+': Duration(seconds: 154, milliseconds: 338), + 'd': Duration(seconds: 162, milliseconds: 063), + //'z': Duration(seconds: 66, milliseconds: 802) +}; + +const Map blitzAverages = { + 'x': 626494, + 'u': 406059, + 'ss': 243166, + 's+': 168636, + 's': 121594, + 's-': 107845, + 'a+': 87142, + 'a': 73413, + 'a-': 60799, + 'b+': 55417, + 'b': 47608, + 'b-': 40534, + 'c+': 34200, + 'c': 32535, + 'c-': 25808, + 'd+': 23345, + 'd': 23063, + //'z': 72084 +}; + String getStatNameByEnum(Stats stat){ return t[stat.name]; } diff --git a/lib/services/tetrio_crud.dart b/lib/services/tetrio_crud.dart index b5bf668..073a7fa 100644 --- a/lib/services/tetrio_crud.dart +++ b/lib/services/tetrio_crud.dart @@ -535,6 +535,12 @@ class TetrioService extends DB { } } + TetrioPlayersLeaderboard? getCachedLeaderboard(){ + return _leaderboardsCache.entries.firstOrNull?.value; + // That function will break if i decide to recive other leaderboards + // TODO: Think about better solution + } + /// Retrieves and returns 100 latest news entries from Tetra Channel api for given [userID]. Throws an exception if fails to retrieve. Future> fetchNews(String userID) async{ try{ diff --git a/lib/views/main_view.dart b/lib/views/main_view.dart index 3e23981..3c630e8 100644 --- a/lib/views/main_view.dart +++ b/lib/views/main_view.dart @@ -30,6 +30,7 @@ import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:go_router/go_router.dart'; Future me = Future.delayed(const Duration(seconds: 60), () => [null, null, null, null, null, null]); // I love lists shut up +TetrioPlayersLeaderboard? everyone; String _searchFor = "6098518e3d5155e6ec429cdc"; // who we looking for String _titleNickname = "dan63047"; final TetrioService teto = TetrioService(); // thing, that manadge our local DB @@ -38,8 +39,8 @@ var chartsData = >>[]; int _chartsIndex = 0; List _historyShortTitles = ["TR", "Glicko", "RD", "APM", "PPS", "VS", "APP", "DS/S", "DS/P", "APP + DS/P", "VS/APM", "Cheese", "GbE", "wAPP", "Area", "eTR", "±eTR", "Opener", "Plonk", "Inf. DS", "Stride"]; late ScrollController _scrollController; -final NumberFormat _timeInSec = NumberFormat("#,###.###s."); -final NumberFormat secs = NumberFormat("00.###"); +final NumberFormat _timeInSec = NumberFormat("#,###.###s.", LocaleSettings.currentLocale.languageCode); +final NumberFormat secs = NumberFormat("00.###", LocaleSettings.currentLocale.languageCode); final NumberFormat _f2 = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 2); final NumberFormat _f4 = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 4); final DateFormat _dateFormat = DateFormat.yMMMd(LocaleSettings.currentLocale.languageCode).add_Hms(); @@ -67,6 +68,20 @@ String get40lTime(int microseconds){ return microseconds > 60000000 ? "${(microseconds/1000000/60).floor()}:${(secs.format(microseconds /1000000 % 60))}" : _timeInSec.format(microseconds / 1000000); } +/// Readable [a] - [b], without sign +String readableTimeDifference(Duration a, Duration b){ + Duration result = a - b; + + return "${NumberFormat("0.000s;0.000s", LocaleSettings.currentLocale.languageCode).format(result.inMilliseconds/1000)}"; +} + +/// Readable [a] - [b], without sign +String readableIntDifference(int a, int b){ + int result = a - b; + + return "${NumberFormat("#,###;#,###", LocaleSettings.currentLocale.languageCode).format(result)}"; +} + class _MainState extends State with TickerProviderStateMixin { final bodyGlobalKey = GlobalKey(); bool _showSearchBar = false; @@ -153,6 +168,9 @@ class _MainState extends State with TickerProviderStateMixin { news = requests[2] as List; topTR = requests.elementAtOrNull(3) as double?; // No TR - no Top TR + // Get tetra League leaderboard if needed + // if(prefs.getBool("loadLeaderboard") == true) everyone = await teto.fetchTLLeaderboard(); + // Making list of Tetra League matches List tlMatches = []; bool isTracking = await teto.isPlayerTracking(me.userId); @@ -379,8 +397,8 @@ class _MainState extends State with TickerProviderStateMixin { TLThingy(tl: snapshot.data![0].tlSeason1, userID: snapshot.data![0].userId, states: snapshot.data![2], topTR: snapshot.data![7], bot: snapshot.data![0].role == "bot", guest: snapshot.data![0].role == "anon"), _TLRecords(userID: snapshot.data![0].userId, data: snapshot.data![3]), _History(states: snapshot.data![2], update: _justUpdate), - _RecordThingy(record: snapshot.data![1]['sprint']), - _RecordThingy(record: snapshot.data![1]['blitz']), + _RecordThingy(record: snapshot.data![1]['sprint'], rank: snapshot.data![0].tlSeason1.percentileRank), + _RecordThingy(record: snapshot.data![1]['blitz'], rank: snapshot.data![0].tlSeason1.percentileRank), _OtherThingy(zen: snapshot.data![1]['zen'], bio: snapshot.data![0].bio, distinguishment: snapshot.data![0].distinguishment, newsletter: snapshot.data![6],) ], ), @@ -906,13 +924,27 @@ class _HistoryChartThigyState extends State<_HistoryChartThigy> { class _RecordThingy extends StatelessWidget { final RecordSingle? record; + final String? rank; /// Widget that displays data from [record] - const _RecordThingy({required this.record}); + const _RecordThingy({required this.record, this.rank}); @override Widget build(BuildContext context) { if (record == null) return Center(child: Text(t.noRecord, textAlign: TextAlign.center, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 28))); + late MapEntry closestAverageBlitz; + late bool blitzBetterThanClosestAverage; + bool? blitzBetterThanRankAverage = (rank != null && rank != "z") ? record!.endContext!.score > blitzAverages[rank]! : null; + late MapEntry closestAverageSprint; + late bool sprintBetterThanClosestAverage; + bool? sprintBetterThanRankAverage = (rank != null && rank != "z") ? record!.endContext!.finalTime < sprintAverages[rank]! : null; + if (record!.stream.contains("40l")) { + closestAverageSprint = sprintAverages.entries.singleWhere((element) => element.value == sprintAverages.values.reduce((a, b) => (a-record!.endContext!.finalTime).abs() < (b -record!.endContext!.finalTime).abs() ? a : b)); + sprintBetterThanClosestAverage = record!.endContext!.finalTime < closestAverageSprint.value; + }else if (record!.stream.contains("blitz")){ + closestAverageBlitz = blitzAverages.entries.singleWhere((element) => element.value == blitzAverages.values.reduce((a, b) => (a-record!.endContext!.score).abs() < (b -record!.endContext!.score).abs() ? a : b)); + blitzBetterThanClosestAverage = record!.endContext!.score > closestAverageBlitz.value; + } return LayoutBuilder(builder: (context, constraints) { bool bigScreen = constraints.maxWidth > 768; return ListView.builder( @@ -926,8 +958,71 @@ class _RecordThingy extends StatelessWidget { else if (record!.stream.contains("blitz")) Text(t.blitz, style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)), // show main metric - if (record!.stream.contains("40l")) Text(get40lTime(record!.endContext!.finalTime.inMicroseconds), style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)) - else if (record!.stream.contains("blitz")) Text(NumberFormat.decimalPattern().format(record!.endContext!.score), style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)), + Wrap( + direction: Axis.horizontal, + alignment: WrapAlignment.spaceAround, + crossAxisAlignment: WrapCrossAlignment.center, + clipBehavior: Clip.hardEdge, + children: [ + // Show grade based on closest rank average + if (record!.stream.contains("40l")) Image.asset("res/tetrio_tl_alpha_ranks/${closestAverageSprint.key}.png", height: 96) + else if (record!.stream.contains("blitz")) Image.asset("res/tetrio_tl_alpha_ranks/${closestAverageBlitz.key}.png", height: 96), + + // TODO: I'm not sure abour that element. Maybe, it could be done differenly + Column( + children: [ + // Show result + if (record!.stream.contains("40l")) Text(get40lTime(record!.endContext!.finalTime.inMicroseconds), style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)) + else if (record!.stream.contains("blitz")) Text(NumberFormat.decimalPattern().format(record!.endContext!.score), style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)), + + // Show difference between rank average + if (record!.stream.contains("40l") && (rank != null && rank != "z")) Text( + "${readableTimeDifference(record!.endContext!.finalTime, sprintAverages[rank]!)} ${sprintBetterThanRankAverage??false ? "better" : "worse"} than ${rank!.toUpperCase()} rank average", + textAlign: TextAlign.center, + style: TextStyle( + color: sprintBetterThanRankAverage??false ? + Colors.greenAccent : + Colors.redAccent + ) + ) + else if (record!.stream.contains("40l") && (rank == null || rank == "z")) Text( + "${readableTimeDifference(record!.endContext!.finalTime, closestAverageSprint.value)} ${sprintBetterThanClosestAverage ? "better" : "worse"} than ${closestAverageSprint.key!.toUpperCase()} rank average", + textAlign: TextAlign.center, + style: TextStyle( + color: sprintBetterThanClosestAverage ? + Colors.greenAccent : + Colors.redAccent + ) + ) + else if (record!.stream.contains("blitz") && (rank != null && rank != "z")) Text( + "${readableIntDifference(record!.endContext!.score, blitzAverages[rank]!)} ${blitzBetterThanRankAverage??false ? "better" : "worse"} than ${rank!.toUpperCase()} rank average", + textAlign: TextAlign.center, + style: TextStyle( + color: blitzBetterThanRankAverage??false ? + Colors.greenAccent : + Colors.redAccent + ) + ) + else if (record!.stream.contains("blitz") && (rank == null || rank == "z")) Text( + "${readableIntDifference(record!.endContext!.score, closestAverageBlitz.value)} ${blitzBetterThanClosestAverage ? "better" : "worse"} than ${closestAverageBlitz.key!.toUpperCase()} rank average", + textAlign: TextAlign.center, + style: TextStyle( + color: blitzBetterThanClosestAverage ? + Colors.greenAccent : + Colors.redAccent + ) + ), + ], + ), + ], + ), + // if (record!.stream.contains("40l")) Text(get40lTime(record!.endContext!.finalTime.inMicroseconds), style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)) + // else if (record!.stream.contains("blitz")) Text(NumberFormat.decimalPattern().format(record!.endContext!.score), style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)), + + // // Compare with averages + // if (record!.stream.contains("40l") && rank != null) RichText(text: TextSpan(text: "${readableTimeDifference(record!.endContext!.finalTime, sprintAverages[rank]!)} ${sprintBetterThanRankAverage??false ? "better" : "worse"} than ${rank!.toUpperCase()} rank average", style: TextStyle(fontFamily: "Eurostile Round", color: sprintBetterThanRankAverage??false ? Colors.green : Colors.red))) + // //Text("${record!.endContext!.finalTime - sprintAverages[rank]!}; ${sprintAverages[rank]}; ${get40lTime((record!.endContext!.finalTime - sprintAverages[rank]!).inMicroseconds)}") + // else if (record!.stream.contains("blitz")) Text("${closestAverageBlitz}; ${blitzAverages[rank]}"), // Show rank if presented if (record!.rank != null) StatCellNum(playerStat: record!.rank!, playerStatLabel: "Leaderboard Placement", isScreenBig: bigScreen, higherIsBetter: false), diff --git a/lib/views/settings_view.dart b/lib/views/settings_view.dart index 6edb8b9..b4d88ac 100644 --- a/lib/views/settings_view.dart +++ b/lib/views/settings_view.dart @@ -26,6 +26,7 @@ class SettingsState extends State { late SharedPreferences prefs; final TetrioService teto = TetrioService(); String defaultNickname = "Checking..."; + late bool loadLeaderboard; final TextEditingController _playertext = TextEditingController(); @override @@ -46,6 +47,11 @@ class SettingsState extends State { Future _getPreferences() async { prefs = await SharedPreferences.getInstance(); + if (prefs.getBool("loadLeaderboard") != null) { + loadLeaderboard = prefs.getBool("loadLeaderboard")!; + } else { + loadLeaderboard = false; + } _setDefaultNickname(prefs.getString("player")); } @@ -260,6 +266,14 @@ class SettingsState extends State { onTap: () { Navigator.pushNamed(context, "/customization"); },), + ListTile(title: Text("Load leaderboard on startup"), + subtitle: Text("That will allow app to show additional stats, like..."), + trailing: Switch(value: loadLeaderboard, onChanged: (bool value){ + prefs.setBool("loadLeaderboard", value); + setState(() { + loadLeaderboard = value; + }); + }),), const Divider(), ListTile( onTap: (){ diff --git a/lib/widgets/list_tile_trailing_stats.dart b/lib/widgets/list_tile_trailing_stats.dart index 52db4b9..c5b223b 100644 --- a/lib/widgets/list_tile_trailing_stats.dart +++ b/lib/widgets/list_tile_trailing_stats.dart @@ -15,6 +15,7 @@ class TrailingStats extends StatelessWidget{ @override Widget build(BuildContext context) { final NumberFormat f2 = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 2); + const TextStyle style = TextStyle(height: 1.1, fontWeight: FontWeight.w100); return Table( defaultColumnWidth: const IntrinsicColumnWidth(), defaultVerticalAlignment: TableCellVerticalAlignment.baseline, @@ -24,9 +25,9 @@ class TrailingStats extends StatelessWidget{ 2: FixedColumnWidth(42), }, children: [ - TableRow(children: [Text(f2.format(yourAPM), textAlign: TextAlign.right, style: const TextStyle(height: 1.1)), const Text(" :", style: TextStyle(height: 1.1)), Text(f2.format(notyourAPM), textAlign: TextAlign.right, style: const TextStyle(height: 1.1)), const Text(" APM", textAlign: TextAlign.right, style: TextStyle(height: 1.1))]), - TableRow(children: [Text(f2.format(yourPPS), textAlign: TextAlign.right, style: const TextStyle(height: 1.1)), const Text(" :", style: TextStyle(height: 1.1)), Text(f2.format(notyourPPS), textAlign: TextAlign.right, style: const TextStyle(height: 1.1)), const Text(" PPS", textAlign: TextAlign.right, style: TextStyle(height: 1.1))]), - TableRow(children: [Text(f2.format(yourVS), textAlign: TextAlign.right, style: const TextStyle(height: 1.1)), const Text(" :", style: TextStyle(height: 1.1)), Text(f2.format(notyourVS), textAlign: TextAlign.right, style: const TextStyle(height: 1.1)), const Text(" VS", textAlign: TextAlign.right, style: TextStyle(height: 1.1))]), + TableRow(children: [Text(f2.format(yourAPM), textAlign: TextAlign.right, style: style), const Text(" :", style: style), Text(f2.format(notyourAPM), textAlign: TextAlign.right, style: style), const Text(" APM", textAlign: TextAlign.right, style: style)]), + TableRow(children: [Text(f2.format(yourPPS), textAlign: TextAlign.right, style: style), const Text(" :", style: style), Text(f2.format(notyourPPS), textAlign: TextAlign.right, style: style), const Text(" PPS", textAlign: TextAlign.right, style: style)]), + TableRow(children: [Text(f2.format(yourVS), textAlign: TextAlign.right, style: style), const Text(" :", style: style), Text(f2.format(notyourVS), textAlign: TextAlign.right, style: style), const Text(" VS", textAlign: TextAlign.right, style: style)]), ], ); } diff --git a/lib/widgets/stat_sell_num.dart b/lib/widgets/stat_sell_num.dart index 32be43a..6a11a89 100644 --- a/lib/widgets/stat_sell_num.dart +++ b/lib/widgets/stat_sell_num.dart @@ -50,8 +50,8 @@ class StatCellNum extends StatelessWidget { ), if (oldPlayerStat != null) Text(comparef.format(playerStat - oldPlayerStat!), style: TextStyle( color: higherIsBetter ? - oldPlayerStat! > playerStat ? Colors.red : Colors.green : - oldPlayerStat! < playerStat ? Colors.red : Colors.green + oldPlayerStat! > playerStat ? Colors.redAccent : Colors.greenAccent : + oldPlayerStat! < playerStat ? Colors.redAccent : Colors.greenAccent ),), alertWidgets == null ? Text( diff --git a/lib/widgets/tl_thingy.dart b/lib/widgets/tl_thingy.dart index 2207abe..175a93a 100644 --- a/lib/widgets/tl_thingy.dart +++ b/lib/widgets/tl_thingy.dart @@ -241,8 +241,8 @@ class _TLThingyState extends State { },), verticalAlignment: GaugeAlignment.far, positionFactor: 0.05,), if (oldTl != null && oldTl!.gamesPlayed > 0) GaugeAnnotation(widget: Text(fDiff.format(currentTl.nerdStats!.app - oldTl!.nerdStats!.app), style: TextStyle( color: currentTl.nerdStats!.app - oldTl!.nerdStats!.app < 0 ? - Colors.red : - Colors.green + Colors.redAccent : + Colors.greenAccent ),), positionFactor: 0.05,)], )],), ), @@ -303,8 +303,8 @@ class _TLThingyState extends State { },), verticalAlignment: GaugeAlignment.far, positionFactor: 0.05), if (oldTl != null && oldTl!.gamesPlayed > 0) GaugeAnnotation(widget: Text(fDiff.format(currentTl.nerdStats!.vsapm - oldTl!.nerdStats!.vsapm), style: TextStyle( color: currentTl.nerdStats!.vsapm - oldTl!.nerdStats!.vsapm < 0 ? - Colors.red : - Colors.green + Colors.redAccent : + Colors.greenAccent ),), positionFactor: 0.05,)], )],), ),]),