Leaderboard sorting + some UI reworks

This commit is contained in:
dan63047 2023-09-03 01:48:50 +03:00
parent dbe875150f
commit 2b2b9ff7d5
10 changed files with 286 additions and 252 deletions

View File

@ -67,6 +67,35 @@ enum Stats {
openerMinusInfDS
}
const Map<Stats, String> chartsShortTitles = {
Stats.tr: "TR",
Stats.glicko: "Glicko",
Stats.rd: "RD",
Stats.gp: "GP",
Stats.gw: "GW",
Stats.wr: "WR%",
Stats.apm: "APM",
Stats.pps: "PPS",
Stats.vs: "VS",
Stats.app: "APP",
Stats.dss: "DS/S",
Stats.dsp: "DS/P",
Stats.appdsp: "APP + DS/P",
Stats.vsapm: "VS/APM",
Stats.cheese: "Cheese",
Stats.gbe: "GbE",
Stats.nyaapp: "wAPP",
Stats.area: "Area",
Stats.eTR: "eTR",
Stats.acceTR: "±eTR",
Stats.opener: "Opener",
Stats.plonk: "Plonk",
Stats.infDS: "Inf. DS",
Stats.stride: "Stride",
Stats.stridemMinusPlonk: "Stride - Plonk",
Stats.openerMinusInfDS: "Opener - Inf. DS"
};
const Map<String, Color> rankColors = { // thanks osk for const rankColors at https://ch.tetr.io/res/js/base.js:418
'x': Color(0xFFFF45FF),
'u': Color(0xFFFF3813),
@ -1009,10 +1038,21 @@ class TetrioPlayersLeaderboard {
TetrioPlayersLeaderboard(this.type, this.leaderboard);
List<num> getStatRanking(List<TetrioPlayerFromLeaderboard> leaderboard, Stats stat){
var lb = leaderboard.map((e) => e.getStatByEnum(stat)).toList();
lb.sort();
return lb.reversed.toList();
List<TetrioPlayerFromLeaderboard> getStatRanking(List<TetrioPlayerFromLeaderboard> leaderboard, Stats stat, {bool reversed = false, String country = ""}){
List<TetrioPlayerFromLeaderboard> lb = List.from(leaderboard);
if (country.isNotEmpty){
lb.removeWhere((element) => element.country != country);
}
lb.sort(((a, b) {
if (a.getStatByEnum(stat) > b.getStatByEnum(stat)){
return reversed ? 1 : -1;
}else if (a.getStatByEnum(stat) == b.getStatByEnum(stat)){
return 0;
}else{
return reversed ? -1 : 1;
}
}));
return lb;
}
List<dynamic> getAverageOfRank(String rank){ // i tried to refactor it and that's was terrible
@ -1689,7 +1729,7 @@ class TetrioPlayerFromLeaderboard {
username = json['username'];
role = json['role'];
xp = json['xp'].toDouble();
country = json['country '];
country = json['country'];
supporter = json['supporter'];
verified = json['verified'];
timestamp = ts;
@ -1722,7 +1762,7 @@ class TetrioPlayerFromLeaderboard {
case Stats.gw:
return gamesWon;
case Stats.wr:
return winrate;
return winrate*100;
case Stats.apm:
return apm;
case Stats.pps:

View File

@ -4,9 +4,9 @@
/// To regenerate, run: `dart run slang`
///
/// Locales: 2
/// Strings: 898 (449 per locale)
/// Strings: 906 (453 per locale)
///
/// Built on 2023-08-21 at 09:52 UTC
/// Built on 2023-09-02 at 21:37 UTC
// coverage:ignore-file
// ignore_for_file: type=lint
@ -251,10 +251,13 @@ class _StringsEn implements BaseTranslations<AppLocale, _StringsEn> {
String get calcViewNoValues => 'Enter values to calculate the stats';
String get rankAveragesViewTitle => 'Ranks cutoff and average stats';
String get averages => 'Averages';
String get lbViewZeroEntrys => 'Empty list. Looks like something is wrong...';
String get lbViewOneEntry => 'There is only one player... What?';
String lbViewManyEntrys({required Object numberOfPlayers}) => 'There are ${numberOfPlayers}.';
String get lbViewZeroEntrys => 'Empty list';
String get lbViewOneEntry => 'There is only one player';
String lbViewManyEntrys({required Object numberOfPlayers}) => 'There are ${numberOfPlayers}';
String get everyoneAverages => 'Values for leaderboard';
String get sortBy => 'Sort by';
String get reversed => 'Reversed';
String get country => 'Country';
String rankAverages({required Object rank}) => 'Values for ${rank} rank';
String players({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('en'))(n,
zero: '${n} players',
@ -290,6 +293,7 @@ class _StringsEn implements BaseTranslations<AppLocale, _StringsEn> {
late final _StringsPopupActionsEn popupActions = _StringsPopupActionsEn._(_root);
late final _StringsErrorsEn errors = _StringsErrorsEn._(_root);
Map<String, String> get countries => {
'': 'Not selected',
'AF': 'Afghanistan',
'AX': 'Åland Islands',
'AL': 'Albania',
@ -775,10 +779,13 @@ class _StringsRu implements _StringsEn {
@override String get calcViewNoValues => 'Введите значения, чтобы посчитать статистику';
@override String get rankAveragesViewTitle => 'Требования рангов и средние значения';
@override String get averages => 'Средние значения';
@override String get lbViewZeroEntrys => 'Рейтинговая таблица пуста. Похоже, что-то здесь не так...';
@override String get lbViewOneEntry => 'В рейтинговой таблице всего один игрок... Чего?';
@override String lbViewManyEntrys({required Object numberOfPlayers}) => 'В рейтинговой таблице находится ${numberOfPlayers}.';
@override String get lbViewZeroEntrys => 'Рейтинговая таблица пуста';
@override String get lbViewOneEntry => 'В рейтинговой таблице всего один игрок';
@override String lbViewManyEntrys({required Object numberOfPlayers}) => 'В рейтинговой таблице находится ${numberOfPlayers}';
@override String get everyoneAverages => 'Значения таблицы';
@override String get sortBy => 'Cортировать по';
@override String get reversed => 'Наоборот';
@override String get country => 'Страна';
@override String rankAverages({required Object rank}) => 'Значения для ${rank} ранга';
@override String players({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('ru'))(n,
zero: '${n} игроков',
@ -814,6 +821,7 @@ class _StringsRu implements _StringsEn {
@override late final _StringsPopupActionsRu popupActions = _StringsPopupActionsRu._(_root);
@override late final _StringsErrorsRu errors = _StringsErrorsRu._(_root);
@override Map<String, String> get countries => {
'': 'Не выбрана',
'AF': 'Афганистан',
'AX': 'Аландские острова',
'AL': 'Албания',
@ -1278,10 +1286,13 @@ extension on _StringsEn {
case 'calcViewNoValues': return 'Enter values to calculate the stats';
case 'rankAveragesViewTitle': return 'Ranks cutoff and average stats';
case 'averages': return 'Averages';
case 'lbViewZeroEntrys': return 'Empty list. Looks like something is wrong...';
case 'lbViewOneEntry': return 'There is only one player... What?';
case 'lbViewManyEntrys': return ({required Object numberOfPlayers}) => 'There are ${numberOfPlayers}.';
case 'lbViewZeroEntrys': return 'Empty list';
case 'lbViewOneEntry': return 'There is only one player';
case 'lbViewManyEntrys': return ({required Object numberOfPlayers}) => 'There are ${numberOfPlayers}';
case 'everyoneAverages': return 'Values for leaderboard';
case 'sortBy': return 'Sort by';
case 'reversed': return 'Reversed';
case 'country': return 'Country';
case 'rankAverages': return ({required Object rank}) => 'Values for ${rank} rank';
case 'players': return ({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('en'))(n,
zero: '${n} players',
@ -1372,6 +1383,7 @@ extension on _StringsEn {
case 'errors.connection': return ({required Object code, required Object message}) => 'Some issue with connection: ${code} ${message}';
case 'errors.noSuchUser': return 'No such user';
case 'errors.socketException': return ({required Object host, required Object message}) => 'Can\'t connect with ${host}: ${message}';
case 'countries.': return 'Not selected';
case 'countries.AF': return 'Afghanistan';
case 'countries.AX': return 'Åland Islands';
case 'countries.AL': return 'Albania';
@ -1737,10 +1749,13 @@ extension on _StringsRu {
case 'calcViewNoValues': return 'Введите значения, чтобы посчитать статистику';
case 'rankAveragesViewTitle': return 'Требования рангов и средние значения';
case 'averages': return 'Средние значения';
case 'lbViewZeroEntrys': return 'Рейтинговая таблица пуста. Похоже, что-то здесь не так...';
case 'lbViewOneEntry': return 'В рейтинговой таблице всего один игрок... Чего?';
case 'lbViewManyEntrys': return ({required Object numberOfPlayers}) => 'В рейтинговой таблице находится ${numberOfPlayers}.';
case 'lbViewZeroEntrys': return 'Рейтинговая таблица пуста';
case 'lbViewOneEntry': return 'В рейтинговой таблице всего один игрок';
case 'lbViewManyEntrys': return ({required Object numberOfPlayers}) => 'В рейтинговой таблице находится ${numberOfPlayers}';
case 'everyoneAverages': return 'Значения таблицы';
case 'sortBy': return 'Cортировать по';
case 'reversed': return 'Наоборот';
case 'country': return 'Страна';
case 'rankAverages': return ({required Object rank}) => 'Значения для ${rank} ранга';
case 'players': return ({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('ru'))(n,
zero: '${n} игроков',
@ -1831,6 +1846,7 @@ extension on _StringsRu {
case 'errors.connection': return ({required Object code, required Object message}) => 'Проблема с подключением: ${code} ${message}';
case 'errors.noSuchUser': return 'Нет такого пользователя';
case 'errors.socketException': return ({required Object host, required Object message}) => 'Невозможно подключиться к ${host}: ${message}';
case 'countries.': return 'Не выбрана';
case 'countries.AF': return 'Афганистан';
case 'countries.AX': return 'Аландские острова';
case 'countries.AL': return 'Албания';

View File

@ -10,6 +10,7 @@ double? vs;
NerdStats? nerdStats;
EstTr? estTr;
Playstyle? playstyle;
final NumberFormat f2 = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 2);
class CalcView extends StatefulWidget {
const CalcView({Key? key}) : super(key: key);
@ -129,10 +130,10 @@ class CalcState extends State<CalcView> {
clipBehavior: Clip.hardEdge,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(20, 0, 20, 48),
padding: const EdgeInsets.fromLTRB(18, 0, 18, 44),
child: SizedBox(
height: 300,
width: 300,
height: 310,
width: 310,
child: RadarChart(
RadarChartData(
radarShape: RadarShape.polygon,
@ -154,21 +155,21 @@ class CalcState extends State<CalcView> {
angle: angle,
);
case 2:
return RadarChartTitle(text: 'VS', angle: angle);
return RadarChartTitle(text: 'VS', angle: angle, positionPercentageOffset: 0.05);
case 3:
return RadarChartTitle(text: 'APP', angle: angle + 180);
return RadarChartTitle(text: 'APP', angle: angle + 180, positionPercentageOffset: 0.05);
case 4:
return RadarChartTitle(text: 'DS/S', angle: angle + 180);
return RadarChartTitle(text: 'DS/S', angle: angle + 180, positionPercentageOffset: 0.05);
case 5:
return RadarChartTitle(text: 'DS/P', angle: angle + 180);
return RadarChartTitle(text: 'DS/P', angle: angle + 180, positionPercentageOffset: 0.05);
case 6:
return RadarChartTitle(text: 'APP+DS/P', angle: angle + 180);
return RadarChartTitle(text: 'APP+DS/P', angle: angle + 180, positionPercentageOffset: 0.05);
case 7:
return RadarChartTitle(text: 'VS/APM', angle: angle + 180);
return RadarChartTitle(text: 'VS/APM', angle: angle + 180, positionPercentageOffset: 0.05);
case 8:
return RadarChartTitle(text: 'Cheese', angle: angle);
return RadarChartTitle(text: 'Cheese', angle: angle, positionPercentageOffset: 0.05);
case 9:
return RadarChartTitle(text: 'Gb Eff.', angle: angle);
return RadarChartTitle(text: 'Gb Eff.', angle: angle, positionPercentageOffset: 0.05);
default:
return const RadarChartTitle(text: '');
}
@ -212,34 +213,30 @@ class CalcState extends State<CalcView> {
),
),
Padding(
padding: const EdgeInsets.fromLTRB(20, 0, 20, 48),
padding: const EdgeInsets.fromLTRB(18, 0, 18, 44),
child: SizedBox(
height: 300,
width: 300,
height: 310,
width: 310,
child: RadarChart(
RadarChartData(
radarShape: RadarShape.polygon,
tickCount: 4,
ticksTextStyle: const TextStyle(color: Colors.transparent, fontSize: 10),
ticksTextStyle: const TextStyle(color: Colors.white24, fontSize: 10),
radarBorderData: const BorderSide(color: Colors.transparent, width: 1),
gridBorderData: const BorderSide(color: Colors.white24, width: 1),
tickBorderData: const BorderSide(color: Colors.transparent, width: 1),
titleTextStyle: const TextStyle(height: 1.1),
radarTouchData: RadarTouchData(),
getTitle: (index, angle) {
switch (index) {
case 0:
return RadarChartTitle(
text: 'Opener',
angle: angle,
);
return RadarChartTitle(text: 'Opener\n${f2.format(playstyle!.opener)}', angle: 0, positionPercentageOffset: 0.05);
case 1:
return RadarChartTitle(
text: 'Stride',
angle: angle,
);
return RadarChartTitle(text: 'Stride\n${f2.format(playstyle!.stride)}', angle: 0, positionPercentageOffset: 0.05);
case 2:
return RadarChartTitle(text: 'Inf Ds', angle: angle + 180);
return RadarChartTitle(text: 'Inf Ds\n${f2.format(playstyle!.infds)}', angle: angle + 180, positionPercentageOffset: 0.05);
case 3:
return RadarChartTitle(text: 'Plonk', angle: angle);
return RadarChartTitle(text: 'Plonk\n${f2.format(playstyle!.plonk)}', angle: 0, positionPercentageOffset: 0.05);
default:
return const RadarChartTitle(text: '');
}

View File

@ -620,46 +620,48 @@ class CompareState extends State<CompareView> {
clipBehavior: Clip.hardEdge,
children: [
Padding(
padding:
const EdgeInsets.fromLTRB(20, 20, 20, 20),
padding: const EdgeInsets.fromLTRB(18, 0, 18, 44),
child: SizedBox(
height: 300,
width: 300,
height: 310,
width: 310,
child: RadarChart(
RadarChartData(
radarShape: RadarShape.polygon,
tickCount: 4,
ticksTextStyle: const TextStyle(
color: Colors.transparent,
fontSize: 10),
radarBorderData: const BorderSide(
color: Colors.transparent, width: 1),
gridBorderData: const BorderSide(
color: Colors.white24, width: 1),
tickBorderData: const BorderSide(
color: Colors.transparent, width: 1),
ticksTextStyle: const TextStyle(color: Colors.transparent, fontSize: 10),
radarBorderData: const BorderSide(color: Colors.transparent, width: 1),
gridBorderData: const BorderSide(color: Colors.white24, width: 1),
tickBorderData: const BorderSide(color: Colors.transparent, width: 1),
getTitle: (index, angle) {
switch (index) {
case 0:
return RadarChartTitle(text: 'APM', angle: angle);
return RadarChartTitle(
text: 'APM',
angle: angle,
positionPercentageOffset: 0.05
);
case 1:
return RadarChartTitle(text: 'PPS', angle: angle);
return RadarChartTitle(
text: 'PPS',
angle: angle,
positionPercentageOffset: 0.05
);
case 2:
return RadarChartTitle(text: 'VS', angle: angle);
return RadarChartTitle(text: 'VS', angle: angle, positionPercentageOffset: 0.05);
case 3:
return RadarChartTitle(text: 'APP', angle: angle + 180);
return RadarChartTitle(text: 'APP', angle: angle + 180, positionPercentageOffset: 0.05);
case 4:
return RadarChartTitle(text: 'DS/S', angle: angle + 180);
return RadarChartTitle(text: 'DS/S', angle: angle + 180, positionPercentageOffset: 0.05);
case 5:
return RadarChartTitle(text: 'DS/P', angle: angle + 180);
return RadarChartTitle(text: 'DS/P', angle: angle + 180, positionPercentageOffset: 0.05);
case 6:
return RadarChartTitle(text: 'APP+DS/P', angle: angle + 180);
return RadarChartTitle(text: 'APP+DS/P', angle: angle + 180, positionPercentageOffset: 0.05);
case 7:
return RadarChartTitle(text: 'VS/APM', angle: angle + 180);
return RadarChartTitle(text: 'VS/APM', angle: angle + 180, positionPercentageOffset: 0.05);
case 8:
return RadarChartTitle(text: 'Cheese', angle: angle);
return RadarChartTitle(text: 'Cheese', angle: angle, positionPercentageOffset: 0.05);
case 9:
return RadarChartTitle(text: 'Gb Eff.', angle: angle);
return RadarChartTitle(text: 'Gb Eff.', angle: angle, positionPercentageOffset: 0.05);
default:
return const RadarChartTitle(text: '');
}
@ -721,34 +723,30 @@ class CompareState extends State<CompareView> {
),
),
Padding(
padding:
const EdgeInsets.fromLTRB(20, 20, 20, 20),
padding: const EdgeInsets.fromLTRB(18, 0, 18, 44),
child: SizedBox(
height: 300,
width: 300,
height: 310,
width: 310,
child: RadarChart(
RadarChartData(
radarShape: RadarShape.polygon,
tickCount: 4,
ticksTextStyle: const TextStyle(
color: Colors.transparent,
fontSize: 10),
radarBorderData: const BorderSide(
color: Colors.transparent, width: 1),
gridBorderData: const BorderSide(
color: Colors.white24, width: 1),
tickBorderData: const BorderSide(
color: Colors.transparent, width: 1),
ticksTextStyle: const TextStyle(color: Colors.white24, fontSize: 10),
radarBorderData: const BorderSide(color: Colors.transparent, width: 1),
gridBorderData: const BorderSide(color: Colors.white24, width: 1),
tickBorderData: const BorderSide(color: Colors.transparent, width: 1),
titleTextStyle: const TextStyle(height: 1.1),
radarTouchData: RadarTouchData(),
getTitle: (index, angle) {
switch (index) {
case 0:
return RadarChartTitle(text: 'Opener',angle: angle);
return RadarChartTitle(text: 'Opener',angle: angle, positionPercentageOffset: 0.05);
case 1:
return RadarChartTitle(text: 'Stride', angle: angle);
return RadarChartTitle(text: 'Stride', angle: angle, positionPercentageOffset: 0.05);
case 2:
return RadarChartTitle(text: 'Inf Ds', angle: angle + 180);
return RadarChartTitle(text: 'Inf Ds', angle: angle + 180, positionPercentageOffset: 0.05);
case 3:
return RadarChartTitle(text: 'Plonk', angle: angle);
return RadarChartTitle(text: 'Plonk', angle: angle, positionPercentageOffset: 0.05);
default:
return const RadarChartTitle(text: '');
}
@ -849,7 +847,7 @@ class CompareState extends State<CompareView> {
)
],
)
] : [Text(t.compareViewNoValues(avgR: "\$avdR"))], // This is so fucked up holy shit
] : [Text(t.compareViewNoValues(avgR: "\$avgR"))], // This is so fucked up holy shit
)
),
),

View File

@ -23,7 +23,7 @@ String _titleNickname = "dan63047";
final TetrioService teto = TetrioService();
late SharedPreferences prefs;
var chartsData = <DropdownMenuItem<List<FlSpot>>>[];
List chartsShortTitles = ["TR", "Glicko", "RD", "APM", "PPS", "VS", "APP", "DS/S", "DS/P", "APP + DS/P", "VS/APM", "Cheese", "GbE", "wAPP", "Area", "eTR", "±eTR"];
List historyShortTitles = ["TR", "Glicko", "RD", "APM", "PPS", "VS", "APP", "DS/S", "DS/P", "APP + DS/P", "VS/APM", "Cheese", "GbE", "wAPP", "Area", "eTR", "±eTR"];
int chartsIndex = 0;
const allowedHeightForPlayerIdInPixels = 40.0;
const allowedHeightForPlayerBioInPixels = 30.0;
@ -508,12 +508,18 @@ class _TLRecords extends StatelessWidget {
fontSize: 28,)),
title: Text("vs. ${value.endContext.firstWhere((element) => element.userId != userID).username}"),
subtitle: Text(dateFormat.format(value.timestamp)),
trailing: Column(mainAxisAlignment: MainAxisAlignment.center,
trailing: Table(defaultColumnWidth: IntrinsicColumnWidth(),
defaultVerticalAlignment: TableCellVerticalAlignment.baseline,
textBaseline: TextBaseline.alphabetic,
columnWidths: {
0: FixedColumnWidth(50),
2: FixedColumnWidth(50),
},
children: [
Text("${f2.format(value.endContext.firstWhere((element) => element.userId == userID).secondary)} : ${f2.format(value.endContext.firstWhere((element) => element.userId != userID).secondary)} APM", style: const TextStyle(height: 1.1)),
Text("${f2.format(value.endContext.firstWhere((element) => element.userId == userID).tertiary)} : ${f2.format(value.endContext.firstWhere((element) => element.userId != userID).tertiary)} PPS", style: const TextStyle(height: 1.1)),
Text("${f2.format(value.endContext.firstWhere((element) => element.userId == userID).extra)} : ${f2.format(value.endContext.firstWhere((element) => element.userId != userID).extra)} VS", style: const TextStyle(height: 1.1)),
]),
TableRow(children: [Text(f2.format(value.endContext.firstWhere((element) => element.userId == userID).secondary), textAlign: TextAlign.right, style: const TextStyle(height: 1.1)), const Text(" :", style: const TextStyle(height: 1.1)), Text(f2.format(value.endContext.firstWhere((element) => element.userId != userID).secondary), textAlign: TextAlign.right, style: const TextStyle(height: 1.1)), const Text(" APM", textAlign: TextAlign.right, style: const TextStyle(height: 1.1))]),
TableRow(children: [Text(f2.format(value.endContext.firstWhere((element) => element.userId == userID).tertiary), textAlign: TextAlign.right, style: const TextStyle(height: 1.1)), const Text(" :", style: const TextStyle(height: 1.1)), Text(f2.format(value.endContext.firstWhere((element) => element.userId != userID).tertiary), textAlign: TextAlign.right, style: const TextStyle(height: 1.1)), const Text(" PPS", textAlign: TextAlign.right, style: const TextStyle(height: 1.1))]),
TableRow(children: [Text(f2.format(value.endContext.firstWhere((element) => element.userId == userID).extra), textAlign: TextAlign.right, style: const TextStyle(height: 1.1)), const Text(" :", style: const TextStyle(height: 1.1)), Text(f2.format(value.endContext.firstWhere((element) => element.userId != userID).extra), textAlign: TextAlign.right, style: const TextStyle(height: 1.1)), const Text(" VS", textAlign: TextAlign.right, style: const TextStyle(height: 1.1))]),
],),
onTap: (){Navigator.push(
context,
MaterialPageRoute(
@ -546,7 +552,7 @@ class _History extends StatelessWidget{
update();
}
),
if(chartsData[chartsIndex].value!.length > 1) _HistoryChartThigy(data: chartsData[chartsIndex].value!, title: "ss", yAxisTitle: chartsShortTitles[chartsIndex], bigScreen: bigScreen, leftSpace: bigScreen? 80 : 45, yFormat: bigScreen? f2 : NumberFormat.compact(),)
if(chartsData[chartsIndex].value!.length > 1) _HistoryChartThigy(data: chartsData[chartsIndex].value!, title: "ss", yAxisTitle: historyShortTitles[chartsIndex], bigScreen: bigScreen, leftSpace: bigScreen? 80 : 45, yFormat: bigScreen? f2 : NumberFormat.compact(),)
else Center(child: Text(t.notEnoughData, style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 28)))
],
),

View File

@ -6,39 +6,9 @@ import 'package:tetra_stats/data_objects/tetrio.dart';
import 'package:tetra_stats/gen/strings.g.dart';
import 'package:tetra_stats/views/main_view.dart' show MainView, f4, f2;
const List chartsShortTitles = [
"TR",
"Glicko",
"RD",
"GP",
"GW",
"WR%",
"APM",
"PPS",
"VS",
"APP",
"DS/S",
"DS/P",
"APP + DS/P",
"VS/APM",
"Cheese",
"GbE",
"wAPP",
"Area",
"eTR",
"±eTR",
"Opener",
"Plonk",
"Inf. DS",
"Stride",
"Stride - Plonk",
"Opener - Inf. DS"
];
var chartsShortTitlesDropdowns = <DropdownMenuItem>[for (String e in chartsShortTitles) DropdownMenuItem(value: e,child: Text(e),)];
int chartsIndexX = 0;
int chartsIndexY = 6;
//final DateFormat dateFormat = DateFormat.yMMMd(LocaleSettings.currentLocale.languageCode).add_Hms();
double pfpHeight = 128;
var chartsShortTitlesDropdowns = <DropdownMenuItem>[for (MapEntry e in chartsShortTitles.entries) DropdownMenuItem(value: e.key, child: Text(e.value),)];
Stats chartsX = Stats.tr;
Stats chartsY = Stats.apm;
class RankView extends StatefulWidget {
final List rank;
@ -154,9 +124,9 @@ class RankState extends State<RankView> with SingleTickerProviderStateMixin {
child: Text(t.currentAxis(axis: "X"), style: const TextStyle(fontSize: 22))),
DropdownButton(
items: chartsShortTitlesDropdowns,
value: chartsShortTitlesDropdowns[chartsIndexX].value,
value: chartsX,
onChanged: (value) {
chartsIndexX = chartsShortTitlesDropdowns.indexWhere((element) => element.value == value);
chartsX = value;
_justUpdate();
}
),
@ -174,9 +144,9 @@ class RankState extends State<RankView> with SingleTickerProviderStateMixin {
),
DropdownButton(
items: chartsShortTitlesDropdowns,
value: chartsShortTitlesDropdowns[chartsIndexY].value,
value: chartsY,
onChanged: (value) {
chartsIndexY = chartsShortTitlesDropdowns.indexWhere((element) => element.value == value);
chartsY = value;
_justUpdate();
}
),
@ -195,11 +165,11 @@ class RankState extends State<RankView> with SingleTickerProviderStateMixin {
: const EdgeInsets.fromLTRB(0, 40, 16, 48),
child: ScatterChart(
ScatterChartData(
scatterSpots: [ for (TetrioPlayerFromLeaderboard entry in widget.rank[1]["entries"]) _MyScatterSpot(takeStat(entry, chartsShortTitles[chartsIndexX]), takeStat(entry, chartsShortTitles[chartsIndexY]), entry.userId, entry.username, color: rankColors[entry.rank])],
scatterSpots: [ for (TetrioPlayerFromLeaderboard entry in widget.rank[1]["entries"]) _MyScatterSpot(entry.getStatByEnum(chartsX) as double, entry.getStatByEnum(chartsY) as double, entry.userId, entry.username, color: rankColors[entry.rank])],
scatterTouchData: ScatterTouchData(touchTooltipData: ScatterTouchTooltipData(
fitInsideHorizontally: true, fitInsideVertically: true, getTooltipItems: (touchedSpot) {
touchedSpot as _MyScatterSpot;
return ScatterTooltipItem("${touchedSpot.nickname}\n", textStyle: const TextStyle(fontFamily: "Eurostile Round Extended"), children: [TextSpan(text: "${f4.format(touchedSpot.x)} ${chartsShortTitles[chartsIndexX]}\n${f4.format(touchedSpot.y)} ${chartsShortTitles[chartsIndexY]}", style: const TextStyle(fontFamily: "Eurostile Round"))]);
return ScatterTooltipItem("${touchedSpot.nickname}\n", textStyle: const TextStyle(fontFamily: "Eurostile Round Extended"), children: [TextSpan(text: "${f4.format(touchedSpot.x)} ${chartsShortTitles[chartsX]}\n${f4.format(touchedSpot.y)} ${chartsShortTitles[chartsY]}", style: const TextStyle(fontFamily: "Eurostile Round"))]);
}),
touchCallback:(event, response) {
if (event.runtimeType == FlTapDownEvent && response?.touchedSpot?.spot != null){
@ -956,66 +926,6 @@ class _ListEntry extends StatelessWidget {
);
}
}
double takeStat(TetrioPlayerFromLeaderboard entry, String stat) {
switch (stat) {
case "TR":
return entry.rating;
case "Glicko":
return entry.glicko;
case "RD":
return entry.rd;
case "GP":
return entry.gamesPlayed.toDouble();
case "GW":
return entry.gamesWon.toDouble();
case "WR%":
return entry.winrate*100;
case "APM":
return entry.apm;
case "PPS":
return entry.pps;
case "VS":
return entry.vs;
case "APP":
return entry.nerdStats.app;
case "DS/S":
return entry.nerdStats.dss;
case "DS/P":
return entry.nerdStats.dsp;
case "APP + DS/P":
return entry.nerdStats.appdsp;
case "VS/APM":
return entry.nerdStats.vsapm;
case "Cheese":
return entry.nerdStats.cheese;
case "GbE":
return entry.nerdStats.gbe;
case "wAPP":
return entry.nerdStats.nyaapp;
case "Area":
return entry.nerdStats.area;
case "eTR":
return entry.estTr.esttr;
case "±eTR":
return entry.esttracc;
case "Opener":
return entry.playstyle.opener;
case "Plonk":
return entry.playstyle.plonk;
case "Inf. DS":
return entry.playstyle.infds;
case "Stride":
return entry.playstyle.stride;
case "Stride - Plonk":
return entry.playstyle.stride - entry.playstyle.plonk;
case "Opener - Inf. DS":
return entry.playstyle.opener - entry.playstyle.infds;
default:
throw ArgumentError.value(stat, "Incorrect stat", "We don't have that stat");
}
}
class _MyScatterSpot extends ScatterSpot{
String id;
String nickname;

View File

@ -1,5 +1,6 @@
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/views/main_view.dart';
@ -7,6 +8,11 @@ import 'package:tetra_stats/views/rank_averages_view.dart';
import 'package:tetra_stats/views/ranks_averages_view.dart';
final TetrioService teto = TetrioService();
List<DropdownMenuItem> itemStats = [for (MapEntry e in chartsShortTitles.entries) DropdownMenuItem(value: e.key, child: Text(e.value))];
Stats sortBy = Stats.tr;
bool reversed = false;
List<DropdownMenuItem> itemCountries = [for (MapEntry e in t.countries.entries) DropdownMenuItem(value: e.key, child: Text(e.value))];
String country = "";
class TLLeaderboardView extends StatefulWidget {
const TLLeaderboardView({Key? key}) : super(key: key);
@ -51,7 +57,7 @@ class TLLeaderboardState extends State<TLLeaderboardView> {
case ConnectionState.active:
return const Center(child: Text('Fetching...'));
case ConnectionState.done:
final allPlayers = snapshot.data?.leaderboard;
final allPlayers = snapshot.data?.getStatRanking(snapshot.data!.leaderboard, sortBy, reversed: reversed, country: country);
return NestedScrollView(
headerSliverBuilder: (context, value) {
String howManyPlayers(int numberOfPlayers) => Intl.plural(
@ -87,6 +93,61 @@ class TLLeaderboardState extends State<TLLeaderboardView> {
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())
];
},
@ -98,7 +159,7 @@ class TLLeaderboardState extends State<TLLeaderboardView> {
leading: Text((index+1).toString(), style: bigScreen ? const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 28) : null),
title: Text(allPlayers[index].username, style: const TextStyle(fontFamily: "Eurostile Round Extended")),
subtitle: Text(
"${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"),
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]}"),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [

View File

@ -69,7 +69,8 @@ class TLThingy extends StatelessWidget {
maximum: tl.prevAt.toDouble(),
interval: tl.prevAt.toDouble() - tl.nextAt.toDouble(),
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)],
markerPointers: [LinearShapePointer(value: tl.standing.toDouble() <= tl.prevAt.toDouble() ? tl.standing.toDouble() : tl.prevAt.toDouble(), position: LinearElementPosition.inside, shapeType: LinearShapePointerType.triangle, color: Colors.white, height: 20),
LinearWidgetPointer(offset: 4, position: LinearElementPosition.outside, value: tl.standing.toDouble() <= tl.prevAt.toDouble() ? tl.standing.toDouble() : tl.prevAt.toDouble(), child: Text(NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 0).format(tl.standing)))],
isAxisInversed: true,
isMirrored: true,
showTicks: true,
@ -97,7 +98,6 @@ class TLThingy extends StatelessWidget {
if (tl.apm != null) StatCellNum(playerStat: tl.apm!, isScreenBig: bigScreen, fractionDigits: 2, playerStatLabel: t.statCellNum.apm, higherIsBetter: true, oldPlayerStat: oldTl?.apm),
if (tl.pps != null) StatCellNum(playerStat: tl.pps!, isScreenBig: bigScreen, fractionDigits: 2, playerStatLabel: t.statCellNum.pps, higherIsBetter: true, oldPlayerStat: oldTl?.pps),
if (tl.vs != null) StatCellNum(playerStat: tl.vs!, isScreenBig: bigScreen, fractionDigits: 2, playerStatLabel: t.statCellNum.vs, higherIsBetter: true, oldPlayerStat: oldTl?.vs),
if (tl.standing > 0) StatCellNum(playerStat: tl.standing, isScreenBig: bigScreen, playerStatLabel: t.statCellNum.lbp, higherIsBetter: false, oldPlayerStat: oldTl?.standing),
if (tl.standingLocal > 0) StatCellNum(playerStat: tl.standingLocal, isScreenBig: bigScreen, playerStatLabel: t.statCellNum.lbpc, higherIsBetter: false, oldPlayerStat: oldTl?.standingLocal),
StatCellNum(playerStat: tl.gamesPlayed, isScreenBig: bigScreen, playerStatLabel: t.statCellNum.gamesPlayed, higherIsBetter: true, oldPlayerStat: oldTl?.gamesPlayed),
StatCellNum(playerStat: tl.gamesWon, isScreenBig: bigScreen, playerStatLabel: t.statCellNum.gamesWonTL, higherIsBetter: true, oldPlayerStat: oldTl?.gamesWon),
@ -352,10 +352,10 @@ class TLThingy extends StatelessWidget {
clipBehavior: Clip.hardEdge,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(20, 0, 20, 48),
padding: const EdgeInsets.fromLTRB(18, 0, 18, 44),
child: SizedBox(
height: 300,
width: 300,
height: 310,
width: 310,
child: RadarChart(
RadarChartData(
radarShape: RadarShape.polygon,
@ -377,21 +377,21 @@ class TLThingy extends StatelessWidget {
angle: angle,
);
case 2:
return RadarChartTitle(text: 'VS', angle: angle);
return RadarChartTitle(text: 'VS', angle: angle, positionPercentageOffset: 0.05);
case 3:
return RadarChartTitle(text: 'APP', angle: angle + 180);
return RadarChartTitle(text: 'APP', angle: angle + 180, positionPercentageOffset: 0.05);
case 4:
return RadarChartTitle(text: 'DS/S', angle: angle + 180);
return RadarChartTitle(text: 'DS/S', angle: angle + 180, positionPercentageOffset: 0.05);
case 5:
return RadarChartTitle(text: 'DS/P', angle: angle + 180);
return RadarChartTitle(text: 'DS/P', angle: angle + 180, positionPercentageOffset: 0.05);
case 6:
return RadarChartTitle(text: 'APP+DS/P', angle: angle + 180);
return RadarChartTitle(text: 'APP+DS/P', angle: angle + 180, positionPercentageOffset: 0.05);
case 7:
return RadarChartTitle(text: 'VS/APM', angle: angle + 180);
return RadarChartTitle(text: 'VS/APM', angle: angle + 180, positionPercentageOffset: 0.05);
case 8:
return RadarChartTitle(text: 'Cheese', angle: angle);
return RadarChartTitle(text: 'Cheese', angle: angle, positionPercentageOffset: 0.05);
case 9:
return RadarChartTitle(text: 'Gb Eff.', angle: angle);
return RadarChartTitle(text: 'Gb Eff.', angle: angle, positionPercentageOffset: 0.05);
default:
return const RadarChartTitle(text: '');
}
@ -435,34 +435,30 @@ class TLThingy extends StatelessWidget {
),
),
Padding(
padding: const EdgeInsets.fromLTRB(20, 0, 20, 48),
padding: const EdgeInsets.fromLTRB(18, 0, 18, 44),
child: SizedBox(
height: 300,
width: 300,
height: 310,
width: 310,
child: RadarChart(
RadarChartData(
radarShape: RadarShape.polygon,
tickCount: 4,
ticksTextStyle: const TextStyle(color: Colors.transparent, fontSize: 10),
ticksTextStyle: const TextStyle(color: Colors.white24, fontSize: 10),
radarBorderData: const BorderSide(color: Colors.transparent, width: 1),
gridBorderData: const BorderSide(color: Colors.white24, width: 1),
tickBorderData: const BorderSide(color: Colors.transparent, width: 1),
titleTextStyle: const TextStyle(height: 1.1),
radarTouchData: RadarTouchData(),
getTitle: (index, angle) {
switch (index) {
case 0:
return RadarChartTitle(
text: 'Opener',
angle: angle,
);
return RadarChartTitle(text: 'Opener\n${f2.format(tl.playstyle!.opener)}', angle: 0, positionPercentageOffset: 0.05);
case 1:
return RadarChartTitle(
text: 'Stride',
angle: angle,
);
return RadarChartTitle(text: 'Stride\n${f2.format(tl.playstyle!.stride)}', angle: 0, positionPercentageOffset: 0.05);
case 2:
return RadarChartTitle(text: 'Inf Ds', angle: angle + 180);
return RadarChartTitle(text: 'Inf Ds\n${f2.format(tl.playstyle!.infds)}', angle: angle + 180, positionPercentageOffset: 0.05);
case 3:
return RadarChartTitle(text: 'Plonk', angle: angle);
return RadarChartTitle(text: 'Plonk\n${f2.format(tl.playstyle!.plonk)}', angle: 0, positionPercentageOffset: 0.05);
default:
return const RadarChartTitle(text: '');
}

View File

@ -100,10 +100,13 @@
"calcViewNoValues": "Enter values to calculate the stats",
"rankAveragesViewTitle": "Ranks cutoff and average stats",
"averages": "Averages",
"lbViewZeroEntrys": "Empty list. Looks like something is wrong...",
"lbViewOneEntry": "There is only one player... What?",
"lbViewManyEntrys": "There are ${numberOfPlayers}.",
"lbViewZeroEntrys": "Empty list",
"lbViewOneEntry": "There is only one player",
"lbViewManyEntrys": "There are ${numberOfPlayers}",
"everyoneAverages": "Values for leaderboard",
"sortBy": "Sort by",
"reversed": "Reversed",
"country": "Country",
"rankAverages": "Values for $rank rank",
"players":{
"zero": "$n players",
@ -205,6 +208,8 @@
"socketException": "Can't connect with ${host}: ${message}"
},
"countries(map)": {
"": "Not selected",
"AF": "Afghanistan",
"AX": "\u00c5land Islands",
"AL": "Albania",

View File

@ -100,10 +100,13 @@
"calcViewNoValues": "Введите значения, чтобы посчитать статистику",
"rankAveragesViewTitle": "Требования рангов и средние значения",
"averages": "Средние значения",
"lbViewZeroEntrys": "Рейтинговая таблица пуста. Похоже, что-то здесь не так...",
"lbViewOneEntry": "В рейтинговой таблице всего один игрок... Чего?",
"lbViewManyEntrys": "В рейтинговой таблице находится ${numberOfPlayers}.",
"lbViewZeroEntrys": "Рейтинговая таблица пуста",
"lbViewOneEntry": "В рейтинговой таблице всего один игрок",
"lbViewManyEntrys": "В рейтинговой таблице находится ${numberOfPlayers}",
"everyoneAverages": "Значения таблицы",
"sortBy": "Cортировать по",
"reversed": "Наоборот",
"country": "Страна",
"rankAverages": "Значения для $rank ранга",
"players":{
"zero": "$n игроков",
@ -205,6 +208,8 @@
"socketException": "Невозможно подключиться к ${host}: ${message}"
},
"countries(map)": {
"": "Не выбрана",
"AF": "Афганистан",
"AX": "Аландские острова",
"AL": "Албания",