diff --git a/lib/data_objects/tetra_league.dart b/lib/data_objects/tetra_league.dart index c509d07..773c9ab 100644 --- a/lib/data_objects/tetra_league.dart +++ b/lib/data_objects/tetra_league.dart @@ -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 toJson() { final Map data = {}; diff --git a/lib/gen/strings.g.dart b/lib/gen/strings.g.dart index 83f9021..12ea442 100644 --- a/lib/gen/strings.g.dart +++ b/lib/gen/strings.g.dart @@ -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 '没有足够的数据'; diff --git a/lib/main.dart b/lib/main.dart index a847a1e..bbba219 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -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'; diff --git a/lib/services/tetrio_crud.dart b/lib/services/tetrio_crud.dart index 7a468c6..6520bf7 100644 --- a/lib/services/tetrio_crud.dart +++ b/lib/services/tetrio_crud.dart @@ -546,6 +546,61 @@ class TetrioService extends DB { } } + Future> 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> csv = const CsvToListConverter().convert(response.body)..removeAt(0); + List history = []; + for (List entry in csv){ + Map tr = {}; + Map glicko = {}; + Map 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 fetchTopOneFromTheLeaderboard() async { TetrioPlayerFromLeaderboard? cached = _cache.get("topone", TetrioPlayerFromLeaderboard); if (cached != null) return cached; diff --git a/lib/views/main_view_tiles.dart b/lib/views/main_view_tiles.dart index 572845b..729becc 100644 --- a/lib/views/main_view_tiles.dart +++ b/lib/views/main_view_tiles.dart @@ -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 { 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> _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>>> historyData; + //late List>>> 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 { ); } ); + _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 { super.initState(); } - Future>>>> getHistoryData(bool fetchHistory) async { + Future>>> getHistoryData(bool fetchHistory) async { if(fetchHistory){ try{ var history = await teto.fetchAndsaveTLHistory(widget.searchFor); @@ -354,217 +384,261 @@ class _DestinationGraphsState extends State { List> states = await Future.wait>([ teto.getStates(widget.searchFor, season: 1), teto.getStates(widget.searchFor, season: 2), ]); - - if (states.length >= 2){ - historyData = [for (List s in states) >>[ // 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>> historyData = []; // [season][metric][spot] + for (int season = 0; season < currentSeason; season++){ + if (states[season].length >= 2){ + Map> 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> 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>>>( + 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: [ + 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( + 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( + 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>( + 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>>>>( - 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: [ - 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( - 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( - 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( + 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( showSelectedIcon: false, @@ -584,6 +658,13 @@ class _DestinationGraphsState extends State { onSelectionChanged: (Set 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; diff --git a/lib/views/rank_averages_view.dart b/lib/views/rank_averages_view.dart index d9d4a42..a0422f4 100644 --- a/lib/views/rank_averages_view.dart +++ b/lib/views/rank_averages_view.dart @@ -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 = [for (MapEntry e in chartsShortTitles.entries) DropdownMenuItem(value: e.key, child: Text(e.value),)]; +var _chartsShortTitlesDropdowns = [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; diff --git a/pubspec.yaml b/pubspec.yaml index 92fc8fa..bfafc4d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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'