40 Lines / Blitz grades and averages

+ another design changes
This commit is contained in:
dan63047 2024-03-05 01:05:59 +03:00
parent 49c5dfdf5a
commit 0648ca9a5d
7 changed files with 174 additions and 16 deletions

View File

@ -117,6 +117,48 @@ const Map<String, Color> rankColors = { // thanks osk for const rankColors at ht
'z': Color(0xFF375433) 'z': Color(0xFF375433)
}; };
const Map<String, Duration> 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<String, int> 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){ String getStatNameByEnum(Stats stat){
return t[stat.name]; return t[stat.name];
} }

View File

@ -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. /// Retrieves and returns 100 latest news entries from Tetra Channel api for given [userID]. Throws an exception if fails to retrieve.
Future<List<News>> fetchNews(String userID) async{ Future<List<News>> fetchNews(String userID) async{
try{ try{

View File

@ -30,6 +30,7 @@ import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
Future<List> me = Future.delayed(const Duration(seconds: 60), () => [null, null, null, null, null, null]); // I love lists shut up Future<List> 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 _searchFor = "6098518e3d5155e6ec429cdc"; // who we looking for
String _titleNickname = "dan63047"; String _titleNickname = "dan63047";
final TetrioService teto = TetrioService(); // thing, that manadge our local DB final TetrioService teto = TetrioService(); // thing, that manadge our local DB
@ -38,8 +39,8 @@ var chartsData = <DropdownMenuItem<List<FlSpot>>>[];
int _chartsIndex = 0; 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"]; 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; late ScrollController _scrollController;
final NumberFormat _timeInSec = NumberFormat("#,###.###s."); final NumberFormat _timeInSec = NumberFormat("#,###.###s.", LocaleSettings.currentLocale.languageCode);
final NumberFormat secs = NumberFormat("00.###"); final NumberFormat secs = NumberFormat("00.###", LocaleSettings.currentLocale.languageCode);
final NumberFormat _f2 = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 2); final NumberFormat _f2 = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 2);
final NumberFormat _f4 = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 4); final NumberFormat _f4 = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 4);
final DateFormat _dateFormat = DateFormat.yMMMd(LocaleSettings.currentLocale.languageCode).add_Hms(); 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); 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<MainView> with TickerProviderStateMixin { class _MainState extends State<MainView> with TickerProviderStateMixin {
final bodyGlobalKey = GlobalKey(); final bodyGlobalKey = GlobalKey();
bool _showSearchBar = false; bool _showSearchBar = false;
@ -153,6 +168,9 @@ class _MainState extends State<MainView> with TickerProviderStateMixin {
news = requests[2] as List<News>; news = requests[2] as List<News>;
topTR = requests.elementAtOrNull(3) as double?; // No TR - no Top TR 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 // Making list of Tetra League matches
List<TetraLeagueAlphaRecord> tlMatches = []; List<TetraLeagueAlphaRecord> tlMatches = [];
bool isTracking = await teto.isPlayerTracking(me.userId); bool isTracking = await teto.isPlayerTracking(me.userId);
@ -379,8 +397,8 @@ class _MainState extends State<MainView> 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"), 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]), _TLRecords(userID: snapshot.data![0].userId, data: snapshot.data![3]),
_History(states: snapshot.data![2], update: _justUpdate), _History(states: snapshot.data![2], update: _justUpdate),
_RecordThingy(record: snapshot.data![1]['sprint']), _RecordThingy(record: snapshot.data![1]['sprint'], rank: snapshot.data![0].tlSeason1.percentileRank),
_RecordThingy(record: snapshot.data![1]['blitz']), _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],) _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 { class _RecordThingy extends StatelessWidget {
final RecordSingle? record; final RecordSingle? record;
final String? rank;
/// Widget that displays data from [record] /// Widget that displays data from [record]
const _RecordThingy({required this.record}); const _RecordThingy({required this.record, this.rank});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (record == null) return Center(child: Text(t.noRecord, textAlign: TextAlign.center, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 28))); 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) { return LayoutBuilder(builder: (context, constraints) {
bool bigScreen = constraints.maxWidth > 768; bool bigScreen = constraints.maxWidth > 768;
return ListView.builder( 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)), else if (record!.stream.contains("blitz")) Text(t.blitz, style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)),
// show main metric // show main metric
if (record!.stream.contains("40l")) Text(get40lTime(record!.endContext!.finalTime.inMicroseconds), style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)) Wrap(
else if (record!.stream.contains("blitz")) Text(NumberFormat.decimalPattern().format(record!.endContext!.score), style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)), 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 // Show rank if presented
if (record!.rank != null) StatCellNum(playerStat: record!.rank!, playerStatLabel: "Leaderboard Placement", isScreenBig: bigScreen, higherIsBetter: false), if (record!.rank != null) StatCellNum(playerStat: record!.rank!, playerStatLabel: "Leaderboard Placement", isScreenBig: bigScreen, higherIsBetter: false),

View File

@ -26,6 +26,7 @@ class SettingsState extends State<SettingsView> {
late SharedPreferences prefs; late SharedPreferences prefs;
final TetrioService teto = TetrioService(); final TetrioService teto = TetrioService();
String defaultNickname = "Checking..."; String defaultNickname = "Checking...";
late bool loadLeaderboard;
final TextEditingController _playertext = TextEditingController(); final TextEditingController _playertext = TextEditingController();
@override @override
@ -46,6 +47,11 @@ class SettingsState extends State<SettingsView> {
Future<void> _getPreferences() async { Future<void> _getPreferences() async {
prefs = await SharedPreferences.getInstance(); prefs = await SharedPreferences.getInstance();
if (prefs.getBool("loadLeaderboard") != null) {
loadLeaderboard = prefs.getBool("loadLeaderboard")!;
} else {
loadLeaderboard = false;
}
_setDefaultNickname(prefs.getString("player")); _setDefaultNickname(prefs.getString("player"));
} }
@ -260,6 +266,14 @@ class SettingsState extends State<SettingsView> {
onTap: () { onTap: () {
Navigator.pushNamed(context, "/customization"); 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(), const Divider(),
ListTile( ListTile(
onTap: (){ onTap: (){

View File

@ -15,6 +15,7 @@ class TrailingStats extends StatelessWidget{
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final NumberFormat f2 = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 2); final NumberFormat f2 = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 2);
const TextStyle style = TextStyle(height: 1.1, fontWeight: FontWeight.w100);
return Table( return Table(
defaultColumnWidth: const IntrinsicColumnWidth(), defaultColumnWidth: const IntrinsicColumnWidth(),
defaultVerticalAlignment: TableCellVerticalAlignment.baseline, defaultVerticalAlignment: TableCellVerticalAlignment.baseline,
@ -24,9 +25,9 @@ class TrailingStats extends StatelessWidget{
2: FixedColumnWidth(42), 2: FixedColumnWidth(42),
}, },
children: [ 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(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: 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(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: 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(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)]),
], ],
); );
} }

View File

@ -50,8 +50,8 @@ class StatCellNum extends StatelessWidget {
), ),
if (oldPlayerStat != null) Text(comparef.format(playerStat - oldPlayerStat!), style: TextStyle( if (oldPlayerStat != null) Text(comparef.format(playerStat - oldPlayerStat!), style: TextStyle(
color: higherIsBetter ? color: higherIsBetter ?
oldPlayerStat! > playerStat ? Colors.red : Colors.green : oldPlayerStat! > playerStat ? Colors.redAccent : Colors.greenAccent :
oldPlayerStat! < playerStat ? Colors.red : Colors.green oldPlayerStat! < playerStat ? Colors.redAccent : Colors.greenAccent
),), ),),
alertWidgets == null alertWidgets == null
? Text( ? Text(

View File

@ -241,8 +241,8 @@ class _TLThingyState extends State<TLThingy> {
},), verticalAlignment: GaugeAlignment.far, positionFactor: 0.05,), },), 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( 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 ? color: currentTl.nerdStats!.app - oldTl!.nerdStats!.app < 0 ?
Colors.red : Colors.redAccent :
Colors.green Colors.greenAccent
),), positionFactor: 0.05,)], ),), positionFactor: 0.05,)],
)],), )],),
), ),
@ -303,8 +303,8 @@ class _TLThingyState extends State<TLThingy> {
},), verticalAlignment: GaugeAlignment.far, positionFactor: 0.05), },), 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( 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 ? color: currentTl.nerdStats!.vsapm - oldTl!.nerdStats!.vsapm < 0 ?
Colors.red : Colors.redAccent :
Colors.green Colors.greenAccent
),), positionFactor: 0.05,)], ),), positionFactor: 0.05,)],
)],), )],),
),]), ),]),