Releasing the CN locale + redesign graphs
This commit is contained in:
parent
d0a6946ccf
commit
28f0d0ad7a
|
@ -151,6 +151,69 @@ class TetraLeague {
|
|||
pps ?? 0,
|
||||
vs ?? 0,
|
||||
decaying);
|
||||
|
||||
num? getStatByEnum(Stats stat){
|
||||
switch (stat) {
|
||||
case Stats.tr:
|
||||
return tr;
|
||||
case Stats.glicko:
|
||||
return glicko;
|
||||
case Stats.gxe:
|
||||
return gxe;
|
||||
case Stats.s1tr:
|
||||
return s1tr;
|
||||
case Stats.rd:
|
||||
return rd;
|
||||
case Stats.gp:
|
||||
return gamesPlayed;
|
||||
case Stats.gw:
|
||||
return gamesWon;
|
||||
case Stats.wr:
|
||||
return winrate*100;
|
||||
case Stats.apm:
|
||||
return apm;
|
||||
case Stats.pps:
|
||||
return pps;
|
||||
case Stats.vs:
|
||||
return vs;
|
||||
case Stats.app:
|
||||
return nerdStats?.app;
|
||||
case Stats.dss:
|
||||
return nerdStats?.dss;
|
||||
case Stats.dsp:
|
||||
return nerdStats?.dsp;
|
||||
case Stats.appdsp:
|
||||
return nerdStats?.appdsp;
|
||||
case Stats.vsapm:
|
||||
return nerdStats?.vsapm;
|
||||
case Stats.cheese:
|
||||
return nerdStats?.cheese;
|
||||
case Stats.gbe:
|
||||
return nerdStats?.gbe;
|
||||
case Stats.nyaapp:
|
||||
return nerdStats?.nyaapp;
|
||||
case Stats.area:
|
||||
return nerdStats?.area;
|
||||
case Stats.eTR:
|
||||
return estTr?.esttr;
|
||||
case Stats.acceTR:
|
||||
return esttracc;
|
||||
case Stats.acceTRabs:
|
||||
return esttracc?.abs();
|
||||
case Stats.opener:
|
||||
return playstyle?.opener;
|
||||
case Stats.plonk:
|
||||
return playstyle?.plonk;
|
||||
case Stats.infDS:
|
||||
return playstyle?.infds;
|
||||
case Stats.stride:
|
||||
return playstyle?.stride;
|
||||
case Stats.stridemMinusPlonk:
|
||||
return (playstyle?.stride??0.00) - (playstyle?.plonk??0.00);
|
||||
case Stats.openerMinusInfDS:
|
||||
return (playstyle?.opener??0.00) - (playstyle?.infds??0.00);
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
/// Locales: 3
|
||||
/// Strings: 1818 (606 per locale)
|
||||
///
|
||||
/// Built on 2024-09-11 at 14:14 UTC
|
||||
/// Built on 2024-09-12 at 20:23 UTC
|
||||
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: type=lint
|
||||
|
@ -1620,7 +1620,7 @@ class _StringsZhCn implements Translations {
|
|||
many: '只有 ${n} 个记录',
|
||||
other: '只有 ${n} 个记录',
|
||||
);
|
||||
@override String get noRecord => '只有 个记录';
|
||||
@override String get noRecord => '没有记录';
|
||||
@override String get botRecord => '机器人不予参加排位赛';
|
||||
@override String get anonRecord => '匿名用户不予参加排位赛';
|
||||
@override String get notEnoughData => '没有足够的数据';
|
||||
|
@ -3580,7 +3580,7 @@ extension on _StringsZhCn {
|
|||
many: '只有 ${n} 个记录',
|
||||
other: '只有 ${n} 个记录',
|
||||
);
|
||||
case 'noRecord': return ({required Object n}) => '只有 ${n} 个记录';
|
||||
case 'noRecord': return '没有记录';
|
||||
case 'botRecord': return '机器人不予参加排位赛';
|
||||
case 'anonRecord': return '匿名用户不予参加排位赛';
|
||||
case 'notEnoughData': return '没有足够的数据';
|
||||
|
|
|
@ -16,7 +16,7 @@ import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
|||
import 'package:sqflite_common_ffi_web/sqflite_ffi_web.dart';
|
||||
import 'package:tetra_stats/gen/strings.g.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:tetra_stats/views/main_view_tiles.dart';
|
||||
import 'package:tetra_stats/views/main_view.dart';
|
||||
import 'package:tetra_stats/views/settings_view.dart';
|
||||
import 'package:tetra_stats/views/tracked_players_view.dart';
|
||||
import 'package:tetra_stats/views/calc_view.dart';
|
||||
|
|
|
@ -546,6 +546,61 @@ class TetrioService extends DB {
|
|||
}
|
||||
}
|
||||
|
||||
Future<List<Cutoffs>> fetchCutoffsHistory() async {
|
||||
Uri url = Uri.https('ts.dan63.by', 'beanserver_blaster/history.csv');
|
||||
|
||||
try{
|
||||
final response = await client.get(url);
|
||||
|
||||
switch (response.statusCode) {
|
||||
case 200:
|
||||
List<List<dynamic>> csv = const CsvToListConverter().convert(response.body)..removeAt(0);
|
||||
List<Cutoffs> history = [];
|
||||
for (List<dynamic> entry in csv){
|
||||
Map<String, double> tr = {};
|
||||
Map<String, double> glicko = {};
|
||||
Map<String, double> gxe = {};
|
||||
for(int i = 0; i < ranks.length; i++){
|
||||
tr[ranks[ranks.length + i - ranks.length]] = entry[1 + i*3];
|
||||
glicko[ranks[ranks.length + i - ranks.length]] = entry[2 + i*3];
|
||||
glicko[ranks[ranks.length + i - ranks.length]] = entry[3 + i*3];
|
||||
}
|
||||
history.add(
|
||||
Cutoffs(
|
||||
DateTime.fromMillisecondsSinceEpoch(entry[0]),
|
||||
tr,
|
||||
glicko,
|
||||
gxe
|
||||
)
|
||||
);
|
||||
}
|
||||
return history;
|
||||
case 404:
|
||||
developer.log("fetchCutoffsHistory: Cutoffs are gone", name: "services/tetrio_crud", error: response.statusCode);
|
||||
return [];
|
||||
// if not 200 or 404 - throw a unique for each code exception
|
||||
case 403:
|
||||
throw P1nkl0bst3rForbidden();
|
||||
case 429:
|
||||
throw P1nkl0bst3rTooManyRequests();
|
||||
case 418:
|
||||
throw TetrioOskwareBridgeProblem();
|
||||
case 500:
|
||||
case 502:
|
||||
case 503:
|
||||
case 504:
|
||||
developer.log("fetchCutoffsHistory: Cutoffs are unavalable (${response.statusCode})", name: "services/tetrio_crud", error: response.statusCode);
|
||||
return [];
|
||||
default:
|
||||
developer.log("fetchCutoffsHistory: Failed to fetch top Cutoffs", name: "services/tetrio_crud", error: response.statusCode);
|
||||
throw ConnectionIssue(response.statusCode, response.reasonPhrase??"No reason");
|
||||
}
|
||||
} on http.ClientException catch (e, s) { // If local http client fails
|
||||
developer.log("$e, $s");
|
||||
throw http.ClientException(e.message, e.uri); // just assuming, that our end user don't have acess to the internet
|
||||
}
|
||||
}
|
||||
|
||||
Future<TetrioPlayerFromLeaderboard> fetchTopOneFromTheLeaderboard() async {
|
||||
TetrioPlayerFromLeaderboard? cached = _cache.get("topone", TetrioPlayerFromLeaderboard);
|
||||
if (cached != null) return cached;
|
||||
|
|
|
@ -23,6 +23,8 @@ import 'package:tetra_stats/data_objects/tetra_league.dart';
|
|||
import 'package:tetra_stats/data_objects/tetra_league_beta_stream.dart';
|
||||
import 'package:tetra_stats/data_objects/tetrio_constants.dart';
|
||||
import 'package:tetra_stats/data_objects/tetrio_player.dart';
|
||||
import 'package:tetra_stats/data_objects/tetrio_player_from_leaderboard.dart';
|
||||
import 'package:tetra_stats/data_objects/tetrio_players_leaderboard.dart';
|
||||
import 'package:tetra_stats/gen/strings.g.dart';
|
||||
import 'package:tetra_stats/services/crud_exceptions.dart';
|
||||
import 'package:tetra_stats/utils/colors_functions.dart';
|
||||
|
@ -289,19 +291,22 @@ class _DestinationGraphsState extends State<DestinationGraphs> {
|
|||
bool fetchData = false;
|
||||
bool _gamesPlayedInsteadOfDateAndTime = false;
|
||||
late ZoomPanBehavior _zoomPanBehavior;
|
||||
late TooltipBehavior _historyTooltipBehavior;
|
||||
late TooltipBehavior _tooltipBehavior;
|
||||
String yAxisTitle = "";
|
||||
bool _smooth = false;
|
||||
final 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"];
|
||||
//final 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"];
|
||||
final List<DropdownMenuItem<Stats>> _yAxis = [for (MapEntry e in chartsShortTitles.entries) DropdownMenuItem(value: e.key, child: Text(e.value))];
|
||||
Graph _graph = Graph.history;
|
||||
int _chartsIndex = 0;
|
||||
Stats _Ychart = Stats.tr;
|
||||
Stats _Xchart = Stats.tr;
|
||||
int _season = currentSeason-1;
|
||||
late List<List<DropdownMenuItem<List<_HistoryChartSpot>>>> historyData;
|
||||
//late List<List<DropdownMenuItem<List<_HistoryChartSpot>>>> historyData;
|
||||
//Duration postSeasonLeft = seasonStart.difference(DateTime.now());
|
||||
|
||||
@override
|
||||
void initState(){
|
||||
_tooltipBehavior = TooltipBehavior(
|
||||
_historyTooltipBehavior = TooltipBehavior(
|
||||
color: Colors.black,
|
||||
borderColor: Colors.white,
|
||||
enable: true,
|
||||
|
@ -326,6 +331,31 @@ class _DestinationGraphsState extends State<DestinationGraphs> {
|
|||
);
|
||||
}
|
||||
);
|
||||
_tooltipBehavior = TooltipBehavior(
|
||||
color: Colors.black,
|
||||
borderColor: Colors.white,
|
||||
enable: true,
|
||||
animationDuration: 0,
|
||||
builder: (dynamic data, dynamic point, dynamic series,
|
||||
int pointIndex, int seriesIndex) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8.0),
|
||||
child: Text(
|
||||
"${data.nickname} (${data.rank.toUpperCase()})",
|
||||
style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 20),
|
||||
),
|
||||
),
|
||||
Text('${f4.format(data.x)} ${chartsShortTitles[_Xchart]}\n${f4.format(data.y)} ${chartsShortTitles[_Ychart]}')
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
);
|
||||
_zoomPanBehavior = ZoomPanBehavior(
|
||||
enablePinching: true,
|
||||
enableSelectionZooming: true,
|
||||
|
@ -335,7 +365,7 @@ class _DestinationGraphsState extends State<DestinationGraphs> {
|
|||
super.initState();
|
||||
}
|
||||
|
||||
Future<List<List<DropdownMenuItem<List<_HistoryChartSpot>>>>> getHistoryData(bool fetchHistory) async {
|
||||
Future<List<Map<Stats, List<_HistoryChartSpot>>>> getHistoryData(bool fetchHistory) async {
|
||||
if(fetchHistory){
|
||||
try{
|
||||
var history = await teto.fetchAndsaveTLHistory(widget.searchFor);
|
||||
|
@ -354,217 +384,261 @@ class _DestinationGraphsState extends State<DestinationGraphs> {
|
|||
List<List<TetraLeague>> states = await Future.wait<List<TetraLeague>>([
|
||||
teto.getStates(widget.searchFor, season: 1), teto.getStates(widget.searchFor, season: 2),
|
||||
]);
|
||||
|
||||
if (states.length >= 2){
|
||||
historyData = [for (List<TetraLeague> s in states) <DropdownMenuItem<List<_HistoryChartSpot>>>[ // Dumping charts data into dropdown menu items, while cheking if every entry is valid
|
||||
DropdownMenuItem(value: [for (var tl in s) if (tl.gamesPlayed > 9) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.tr)], child: Text(t.statCellNum.tr)),
|
||||
DropdownMenuItem(value: [for (var tl in s) if (tl.gamesPlayed > 9) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.glicko!)], child: const Text("Glicko")),
|
||||
DropdownMenuItem(value: [for (var tl in s) if (tl.gamesPlayed > 9) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.rd!)], child: const Text("Rating Deviation")),
|
||||
DropdownMenuItem(value: [for (var tl in s) if (tl.apm != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.apm!)], child: Text(t.statCellNum.apm.replaceAll(RegExp(r'\n'), " "))),
|
||||
DropdownMenuItem(value: [for (var tl in s) if (tl.pps != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.pps!)], child: Text(t.statCellNum.pps.replaceAll(RegExp(r'\n'), " "))),
|
||||
DropdownMenuItem(value: [for (var tl in s) if (tl.vs != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.vs!)], child: Text(t.statCellNum.vs.replaceAll(RegExp(r'\n'), " "))),
|
||||
DropdownMenuItem(value: [for (var tl in s) if (tl.nerdStats != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.nerdStats!.app)], child: Text(t.statCellNum.app.replaceAll(RegExp(r'\n'), " "))),
|
||||
DropdownMenuItem(value: [for (var tl in s) if (tl.nerdStats != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.nerdStats!.dss)], child: Text(t.statCellNum.dss.replaceAll(RegExp(r'\n'), " "))),
|
||||
DropdownMenuItem(value: [for (var tl in s) if (tl.nerdStats != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.nerdStats!.dsp)], child: Text(t.statCellNum.dsp.replaceAll(RegExp(r'\n'), " "))),
|
||||
DropdownMenuItem(value: [for (var tl in s) if (tl.nerdStats != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.nerdStats!.appdsp)], child: const Text("APP + DS/P")),
|
||||
DropdownMenuItem(value: [for (var tl in s) if (tl.nerdStats != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.nerdStats!.vsapm)], child: const Text("VS/APM")),
|
||||
DropdownMenuItem(value: [for (var tl in s) if (tl.nerdStats != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.nerdStats!.cheese)], child: Text(t.statCellNum.cheese.replaceAll(RegExp(r'\n'), " "))),
|
||||
DropdownMenuItem(value: [for (var tl in s) if (tl.nerdStats != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.nerdStats!.gbe)], child: Text(t.statCellNum.gbe.replaceAll(RegExp(r'\n'), " "))),
|
||||
DropdownMenuItem(value: [for (var tl in s) if (tl.nerdStats != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.nerdStats!.nyaapp)], child: Text(t.statCellNum.nyaapp.replaceAll(RegExp(r'\n'), " "))),
|
||||
DropdownMenuItem(value: [for (var tl in s) if (tl.nerdStats != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.nerdStats!.area)], child: Text(t.statCellNum.area.replaceAll(RegExp(r'\n'), " "))),
|
||||
DropdownMenuItem(value: [for (var tl in s) if (tl.estTr != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.estTr!.esttr)], child: Text(t.statCellNum.estOfTR.replaceAll(RegExp(r'\n'), " "))),
|
||||
DropdownMenuItem(value: [for (var tl in s) if (tl.esttracc != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.esttracc!)], child: Text(t.statCellNum.accOfEst.replaceAll(RegExp(r'\n'), " "))),
|
||||
DropdownMenuItem(value: [for (var tl in s) if (tl.playstyle != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.playstyle!.opener)], child: const Text("Opener")),
|
||||
DropdownMenuItem(value: [for (var tl in s) if (tl.playstyle != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.playstyle!.plonk)], child: const Text("Plonk")),
|
||||
DropdownMenuItem(value: [for (var tl in s) if (tl.playstyle != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.playstyle!.infds)], child: const Text("Inf. DS")),
|
||||
DropdownMenuItem(value: [for (var tl in s) if (tl.playstyle != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.playstyle!.stride)], child: const Text("Stride")),
|
||||
]];
|
||||
}else{
|
||||
historyData = [];
|
||||
List<Map<Stats, List<_HistoryChartSpot>>> historyData = []; // [season][metric][spot]
|
||||
for (int season = 0; season < currentSeason; season++){
|
||||
if (states[season].length >= 2){
|
||||
Map<Stats, List<_HistoryChartSpot>> statsMap = {};
|
||||
for (var stat in Stats.values) statsMap[stat] = [for (var tl in states[season]) if (tl.getStatByEnum(stat) != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.getStatByEnum(stat)!.toDouble())];
|
||||
historyData.add(statsMap);
|
||||
}else{
|
||||
historyData.add({});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
fetchData = false;
|
||||
|
||||
return historyData;
|
||||
}
|
||||
|
||||
Future<List<_MyScatterSpot>> getTetraLeagueData(Stats x, Stats y) async {
|
||||
TetrioPlayersLeaderboard leaderboard = await teto.fetchTLLeaderboard();
|
||||
List<_MyScatterSpot> _spots = [
|
||||
for (TetrioPlayerFromLeaderboard entry in leaderboard.leaderboard)
|
||||
_MyScatterSpot(
|
||||
entry.getStatByEnum(x).toDouble(),
|
||||
entry.getStatByEnum(y).toDouble(),
|
||||
entry.userId,
|
||||
entry.username,
|
||||
entry.rank,
|
||||
rankColors[entry.rank]??Colors.white
|
||||
)
|
||||
];
|
||||
return _spots;
|
||||
}
|
||||
|
||||
Widget getHistoryGraph(){
|
||||
return FutureBuilder<List<Map<Stats, List<_HistoryChartSpot>>>>(
|
||||
future: getHistoryData(fetchData),
|
||||
builder: (context, snapshot) {
|
||||
switch (snapshot.connectionState){
|
||||
case ConnectionState.none:
|
||||
case ConnectionState.waiting:
|
||||
case ConnectionState.active:
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
case ConnectionState.done:
|
||||
if (snapshot.hasData && snapshot.data!.isNotEmpty){
|
||||
List<_HistoryChartSpot> selectedGraph = snapshot.data![_season][_Ychart]!;
|
||||
yAxisTitle = chartsShortTitles[_Ychart]!;
|
||||
return SfCartesianChart(
|
||||
tooltipBehavior: _historyTooltipBehavior,
|
||||
zoomPanBehavior: _zoomPanBehavior,
|
||||
primaryXAxis: _gamesPlayedInsteadOfDateAndTime ? const NumericAxis() : const DateTimeAxis(),
|
||||
primaryYAxis: const NumericAxis(
|
||||
rangePadding: ChartRangePadding.additional,
|
||||
),
|
||||
margin: const EdgeInsets.all(0),
|
||||
series: <CartesianSeries>[
|
||||
if (_gamesPlayedInsteadOfDateAndTime) StepLineSeries<_HistoryChartSpot, int>(
|
||||
enableTooltip: true,
|
||||
dataSource: selectedGraph,
|
||||
animationDuration: 0,
|
||||
opacity: _smooth ? 0 : 1,
|
||||
xValueMapper: (_HistoryChartSpot data, _) => data.gamesPlayed,
|
||||
yValueMapper: (_HistoryChartSpot data, _) => data.stat,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
trendlines:<Trendline>[
|
||||
Trendline(
|
||||
isVisible: _smooth,
|
||||
period: (selectedGraph.length/175).floor(),
|
||||
type: TrendlineType.movingAverage,
|
||||
color: Theme.of(context).colorScheme.primary)
|
||||
],
|
||||
)
|
||||
else StepLineSeries<_HistoryChartSpot, DateTime>(
|
||||
enableTooltip: true,
|
||||
dataSource: selectedGraph,
|
||||
animationDuration: 0,
|
||||
opacity: _smooth ? 0 : 1,
|
||||
xValueMapper: (_HistoryChartSpot data, _) => data.timestamp,
|
||||
yValueMapper: (_HistoryChartSpot data, _) => data.stat,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
trendlines:<Trendline>[
|
||||
Trendline(
|
||||
isVisible: _smooth,
|
||||
period: (selectedGraph.length/175).floor(),
|
||||
type: TrendlineType.movingAverage,
|
||||
color: Theme.of(context).colorScheme.primary)
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}else{
|
||||
return Center(child:
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(snapshot.error.toString(), style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 42, fontWeight: FontWeight.bold), textAlign: TextAlign.center),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Text(snapshot.stackTrace.toString(), textAlign: TextAlign.center),
|
||||
),
|
||||
],
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
Widget getLeagueState (){
|
||||
return FutureBuilder<List<_MyScatterSpot>>(
|
||||
future: getTetraLeagueData(_Xchart, _Ychart),
|
||||
builder: (context, snapshot) {
|
||||
switch (snapshot.connectionState){
|
||||
case ConnectionState.none:
|
||||
case ConnectionState.waiting:
|
||||
case ConnectionState.active:
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
case ConnectionState.done:
|
||||
if (snapshot.hasData){
|
||||
return SfCartesianChart(
|
||||
tooltipBehavior: _tooltipBehavior,
|
||||
zoomPanBehavior: _zoomPanBehavior,
|
||||
//primaryXAxis: CategoryAxis(),
|
||||
series: [
|
||||
ScatterSeries(
|
||||
enableTooltip: true,
|
||||
dataSource: snapshot.data,
|
||||
animationDuration: 0,
|
||||
pointColorMapper: (data, _) => data.color,
|
||||
xValueMapper: (data, _) => data.x,
|
||||
yValueMapper: (data, _) => data.y,
|
||||
onPointTap: (point) => Navigator.push(context, MaterialPageRoute(builder: (context) => MainView(player: snapshot.data![point.pointIndex!].nickname), maintainState: false)),
|
||||
)
|
||||
],
|
||||
);
|
||||
}else{
|
||||
return Center(child:
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(snapshot.error.toString(), style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 42, fontWeight: FontWeight.bold), textAlign: TextAlign.center),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Text(snapshot.stackTrace.toString(), textAlign: TextAlign.center),
|
||||
),
|
||||
],
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
Widget getCutoffsHistory(){
|
||||
return Container(); // TODO
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
FutureBuilder<List<List<DropdownMenuItem<List<_HistoryChartSpot>>>>>(
|
||||
future: getHistoryData(fetchData),
|
||||
builder: (context, snapshot) {
|
||||
switch (snapshot.connectionState){
|
||||
case ConnectionState.none:
|
||||
case ConnectionState.waiting:
|
||||
case ConnectionState.active:
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
case ConnectionState.done:
|
||||
if (snapshot.hasData && snapshot.data!.isNotEmpty){
|
||||
List<_HistoryChartSpot> selectedGraph = snapshot.data![_season][_chartsIndex].value!;
|
||||
yAxisTitle = _historyShortTitles[_chartsIndex];
|
||||
return SingleChildScrollView(
|
||||
scrollDirection: Axis.vertical,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Card(
|
||||
child: Wrap(
|
||||
spacing: 20,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Padding(padding: EdgeInsets.all(8.0), child: Text("Season:", style: TextStyle(fontSize: 22))),
|
||||
DropdownButton(
|
||||
items: [for (int i = 1; i <= currentSeason; i++) DropdownMenuItem(value: i-1, child: Text("$i"))],
|
||||
value: _season,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_season = value!;
|
||||
});
|
||||
}
|
||||
),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Padding(padding: EdgeInsets.all(8.0), child: Text("X:", style: TextStyle(fontSize: 22))),
|
||||
DropdownButton(
|
||||
items: const [DropdownMenuItem(value: false, child: Text("Date & Time")), DropdownMenuItem(value: true, child: Text("Games Played"))],
|
||||
value: _gamesPlayedInsteadOfDateAndTime,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_gamesPlayedInsteadOfDateAndTime = value!;
|
||||
});
|
||||
}
|
||||
),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Padding(padding: EdgeInsets.all(8.0), child: Text("Y:", style: TextStyle(fontSize: 22))),
|
||||
DropdownButton(
|
||||
items: historyData[_season],
|
||||
value: historyData[_season][_chartsIndex].value,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_chartsIndex = historyData[_season].indexWhere((element) => element.value == value);
|
||||
});
|
||||
}
|
||||
),
|
||||
],
|
||||
),
|
||||
if (selectedGraph.length > 300) Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Checkbox(value: _smooth,
|
||||
checkColor: Colors.black,
|
||||
onChanged: ((value) {
|
||||
setState(() {
|
||||
_smooth = value!;
|
||||
});
|
||||
})),
|
||||
Text(t.smooth, style: const TextStyle(color: Colors.white, fontSize: 22))
|
||||
],
|
||||
),
|
||||
IconButton(onPressed: () => _zoomPanBehavior.reset(), icon: const Icon(Icons.refresh), alignment: Alignment.center,)
|
||||
],
|
||||
),
|
||||
),
|
||||
if(historyData[_season][_chartsIndex].value!.length > 1) Card(
|
||||
child: SizedBox(
|
||||
width: MediaQuery.of(context).size.width - 88,
|
||||
height: MediaQuery.of(context).size.height - 96,
|
||||
child: Padding( padding: const EdgeInsets.fromLTRB(40, 30, 40, 30),
|
||||
child: SfCartesianChart(
|
||||
tooltipBehavior: _tooltipBehavior,
|
||||
zoomPanBehavior: _zoomPanBehavior,
|
||||
primaryXAxis: _gamesPlayedInsteadOfDateAndTime ? const NumericAxis() : const DateTimeAxis(),
|
||||
primaryYAxis: const NumericAxis(
|
||||
rangePadding: ChartRangePadding.additional,
|
||||
),
|
||||
margin: const EdgeInsets.all(0),
|
||||
series: <CartesianSeries>[
|
||||
if (_gamesPlayedInsteadOfDateAndTime) StepLineSeries<_HistoryChartSpot, int>(
|
||||
enableTooltip: true,
|
||||
dataSource: historyData[_season][_chartsIndex].value!,
|
||||
animationDuration: 0,
|
||||
opacity: _smooth ? 0 : 1,
|
||||
xValueMapper: (_HistoryChartSpot data, _) => data.gamesPlayed,
|
||||
yValueMapper: (_HistoryChartSpot data, _) => data.stat,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
trendlines:<Trendline>[
|
||||
Trendline(
|
||||
isVisible: _smooth,
|
||||
period: (historyData[_season][_chartsIndex].value!.length/175).floor(),
|
||||
type: TrendlineType.movingAverage,
|
||||
color: Theme.of(context).colorScheme.primary)
|
||||
],
|
||||
)
|
||||
else StepLineSeries<_HistoryChartSpot, DateTime>(
|
||||
enableTooltip: true,
|
||||
dataSource: historyData[_season][_chartsIndex].value!,
|
||||
animationDuration: 0,
|
||||
opacity: _smooth ? 0 : 1,
|
||||
xValueMapper: (_HistoryChartSpot data, _) => data.timestamp,
|
||||
yValueMapper: (_HistoryChartSpot data, _) => data.stat,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
trendlines:<Trendline>[
|
||||
Trendline(
|
||||
isVisible: _smooth,
|
||||
period: (historyData[_season][_chartsIndex].value!.length/175).floor(),
|
||||
type: TrendlineType.movingAverage,
|
||||
color: Theme.of(context).colorScheme.primary)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
),
|
||||
)
|
||||
else if (historyData[_season][_chartsIndex].value!.length <= 1) Center(child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(t.notEnoughData, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 28)),
|
||||
Text(t.errors.actionSuggestion),
|
||||
TextButton(onPressed: (){setState(() {
|
||||
fetchData = true;
|
||||
});}, child: Text(t.fetchAndsaveTLHistory))
|
||||
],
|
||||
))
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
if (snapshot.hasError || snapshot.data!.isEmpty){
|
||||
return Center(child:
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
SingleChildScrollView(
|
||||
scrollDirection: Axis.vertical,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Card(
|
||||
child: Wrap(
|
||||
spacing: 20,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
children: [
|
||||
Text(snapshot.error != null ? snapshot.error.toString() : t.noHistorySaved, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 42, fontWeight: FontWeight.bold), textAlign: TextAlign.center),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Text(snapshot.stackTrace != null ? snapshot.stackTrace.toString() : "lol", textAlign: TextAlign.center),
|
||||
if (_graph == Graph.history) Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Padding(padding: EdgeInsets.all(8.0), child: Text("Season:", style: TextStyle(fontSize: 22))),
|
||||
DropdownButton(
|
||||
items: [for (int i = 1; i <= currentSeason; i++) DropdownMenuItem(value: i-1, child: Text("$i"))],
|
||||
value: _season,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_season = value!;
|
||||
});
|
||||
}
|
||||
),
|
||||
],
|
||||
),
|
||||
if (_graph != Graph.leagueCutoffs) Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Padding(padding: EdgeInsets.all(8.0), child: Text("X:", style: TextStyle(fontSize: 22))),
|
||||
DropdownButton(
|
||||
items: switch (_graph){
|
||||
Graph.history => [DropdownMenuItem(value: false, child: Text("Date & Time")), DropdownMenuItem(value: true, child: Text("Games Played"))],
|
||||
Graph.leagueState => _yAxis,
|
||||
Graph.leagueCutoffs => [],
|
||||
},
|
||||
value: _graph == Graph.history ? _gamesPlayedInsteadOfDateAndTime : _Xchart,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
if (_graph == Graph.history)
|
||||
_gamesPlayedInsteadOfDateAndTime = value! as bool;
|
||||
else _Xchart = value! as Stats;
|
||||
});
|
||||
}
|
||||
),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Padding(padding: EdgeInsets.all(8.0), child: Text("Y:", style: TextStyle(fontSize: 22))),
|
||||
DropdownButton<Stats>(
|
||||
items: _yAxis,
|
||||
value: _Ychart,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_Ychart = value!;
|
||||
});
|
||||
}
|
||||
),
|
||||
],
|
||||
),
|
||||
if (_graph != Graph.leagueState) Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Checkbox(value: _smooth,
|
||||
checkColor: Colors.black,
|
||||
onChanged: ((value) {
|
||||
setState(() {
|
||||
_smooth = value!;
|
||||
});
|
||||
})),
|
||||
Text(t.smooth, style: const TextStyle(color: Colors.white, fontSize: 22))
|
||||
],
|
||||
),
|
||||
IconButton(onPressed: () => _zoomPanBehavior.reset(), icon: const Icon(Icons.refresh), alignment: Alignment.center,)
|
||||
],
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
return const Center(child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text("lol", style: TextStyle(fontFamily: "Eurostile Round", fontSize: 28)),
|
||||
],
|
||||
));
|
||||
},
|
||||
),
|
||||
),
|
||||
Card(
|
||||
child: SizedBox(
|
||||
width: MediaQuery.of(context).size.width - 88,
|
||||
height: MediaQuery.of(context).size.height - 96,
|
||||
child: Padding( padding: const EdgeInsets.fromLTRB(40, 30, 40, 30),
|
||||
child: switch (_graph){
|
||||
Graph.history => getHistoryGraph(),
|
||||
Graph.leagueState => getLeagueState(),
|
||||
Graph.leagueCutoffs => getCutoffsHistory()
|
||||
},
|
||||
)
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
SegmentedButton<Graph>(
|
||||
showSelectedIcon: false,
|
||||
|
@ -584,6 +658,13 @@ class _DestinationGraphsState extends State<DestinationGraphs> {
|
|||
onSelectionChanged: (Set<Graph> newSelection) {
|
||||
setState(() {
|
||||
_graph = newSelection.first;
|
||||
switch (newSelection.first){
|
||||
case Graph.leagueCutoffs:
|
||||
case Graph.history:
|
||||
_Ychart = Stats.tr;
|
||||
case Graph.leagueState:
|
||||
_Ychart = Stats.apm;
|
||||
}
|
||||
});})
|
||||
],
|
||||
);
|
||||
|
@ -598,6 +679,16 @@ class _HistoryChartSpot{
|
|||
const _HistoryChartSpot(this.timestamp, this.gamesPlayed, this.rank, this.stat);
|
||||
}
|
||||
|
||||
class _MyScatterSpot{
|
||||
num x;
|
||||
num y;
|
||||
String id;
|
||||
String nickname;
|
||||
String rank;
|
||||
Color color;
|
||||
_MyScatterSpot(this.x, this.y, this.id, this.nickname, this.rank, this.color);
|
||||
}
|
||||
|
||||
class DestinationHome extends StatefulWidget{
|
||||
final String searchFor;
|
||||
//final Function setState;
|
||||
|
|
|
@ -11,7 +11,7 @@ import 'package:tetra_stats/views/main_view.dart' show MainView;
|
|||
import 'package:window_manager/window_manager.dart';
|
||||
import 'package:syncfusion_flutter_charts/charts.dart';
|
||||
|
||||
var _chartsShortTitlesDropdowns = <DropdownMenuItem>[for (MapEntry e in chartsShortTitles.entries) DropdownMenuItem(value: e.key, child: Text(e.value),)];
|
||||
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;
|
||||
late TooltipBehavior _tooltipBehavior;
|
||||
|
|
|
@ -2,7 +2,7 @@ name: tetra_stats
|
|||
description: Track your and other player stats in TETR.IO
|
||||
publish_to: 'none'
|
||||
|
||||
version: 1.6.10+36
|
||||
version: 1.6.11+37
|
||||
|
||||
environment:
|
||||
sdk: '>=3.0.0'
|
||||
|
|
Loading…
Reference in New Issue