i guess 1.5.1 is ready

This commit is contained in:
dan63047 2024-05-04 23:04:48 +03:00
parent 9aa67686da
commit fcab60f7ba
19 changed files with 380 additions and 217 deletions

View File

@ -1,5 +1,4 @@
import 'dart:math';
import 'package:vector_math/vector_math_64.dart';
import 'tetrio.dart';

View File

@ -4,9 +4,9 @@
/// To regenerate, run: `dart run slang`
///
/// Locales: 2
/// Strings: 1122 (561 per locale)
/// Strings: 1138 (569 per locale)
///
/// Built on 2024-04-11 at 22:23 UTC
/// Built on 2024-05-04 at 19:13 UTC
// coverage:ignore-file
// ignore_for_file: type=lint
@ -218,6 +218,10 @@ class Translations implements BaseTranslations<AppLocale, Translations> {
String get verdictWorse => 'worse';
String get smooth => 'Smooth';
String gamesUntilRanked({required Object left}) => '${left} games until being ranked';
String numOfVictories({required Object wins}) => '~${wins} victories';
String get promotionOnNextWin => 'Promotion on next win';
String numOfdefeats({required Object losses}) => '~${losses} defeats';
String get demotionOnNextLoss => 'Demotion on next loss';
String get nerdStats => 'Nerd Stats';
String get playersYouTrack => 'Players you track';
String get formula => 'Formula';
@ -336,6 +340,7 @@ class Translations implements BaseTranslations<AppLocale, Translations> {
String currentAxis({required Object axis}) => '${axis} axis:';
String get p1nkl0bst3rAlert => 'That data was retrived from third party API maintained by p1nkl0bst3r';
String get notForWeb => 'Function is not available for web version';
late final _StringsGraphsEn graphs = _StringsGraphsEn._(_root);
late final _StringsStatCellNumEn statCellNum = _StringsStatCellNumEn._(_root);
Map<String, String> get playerRole => {
'user': 'User',
@ -635,6 +640,19 @@ class _StringsNewsPartsEn {
String unknownNews({required Object type}) => 'Unknown news of type ${type}';
}
// Path: graphs
class _StringsGraphsEn {
_StringsGraphsEn._(this._root);
final Translations _root; // ignore: unused_field
// Translations
String get attack => 'Attack';
String get speed => 'Speed';
String get defense => 'Defense';
String get cheese => 'Cheese';
}
// Path: statCellNum
class _StringsStatCellNumEn {
_StringsStatCellNumEn._(this._root);
@ -873,6 +891,10 @@ class _StringsRu implements Translations {
@override String get verdictWorse => 'Хуже';
@override String get smooth => 'Гладкий';
@override String gamesUntilRanked({required Object left}) => '${left} матчей до получения рейтинга';
@override String numOfVictories({required Object wins}) => '~${wins} побед';
@override String get promotionOnNextWin => 'Повышение после следующей победы';
@override String numOfdefeats({required Object losses}) => '~${losses} поражений';
@override String get demotionOnNextLoss => 'Понижение после следующего поражения';
@override String get nerdStats => 'Для задротов';
@override String get playersYouTrack => 'Отслеживаемые игроки';
@override String get formula => 'Формула';
@ -991,6 +1013,7 @@ class _StringsRu implements Translations {
@override String currentAxis({required Object axis}) => 'Ось ${axis}:';
@override String get p1nkl0bst3rAlert => 'Эти данные были получены из стороннего API, который поддерживается p1nkl0bst3r';
@override String get notForWeb => 'Функция недоступна для веб версии';
@override late final _StringsGraphsRu graphs = _StringsGraphsRu._(_root);
@override late final _StringsStatCellNumRu statCellNum = _StringsStatCellNumRu._(_root);
@override Map<String, String> get playerRole => {
'user': 'Пользователь',
@ -1290,6 +1313,19 @@ class _StringsNewsPartsRu implements _StringsNewsPartsEn {
@override String unknownNews({required Object type}) => 'Неизвестная новость типа ${type}';
}
// Path: graphs
class _StringsGraphsRu implements _StringsGraphsEn {
_StringsGraphsRu._(this._root);
@override final _StringsRu _root; // ignore: unused_field
// Translations
@override String get attack => 'Атака';
@override String get speed => 'Скорость';
@override String get defense => 'Защита';
@override String get cheese => 'Сыр';
}
// Path: statCellNum
class _StringsStatCellNumRu implements _StringsStatCellNumEn {
_StringsStatCellNumRu._(this._root);
@ -1520,6 +1556,10 @@ extension on Translations {
case 'verdictWorse': return 'worse';
case 'smooth': return 'Smooth';
case 'gamesUntilRanked': return ({required Object left}) => '${left} games until being ranked';
case 'numOfVictories': return ({required Object wins}) => '~${wins} victories';
case 'promotionOnNextWin': return 'Promotion on next win';
case 'numOfdefeats': return ({required Object losses}) => '~${losses} defeats';
case 'demotionOnNextLoss': return 'Demotion on next loss';
case 'nerdStats': return 'Nerd Stats';
case 'playersYouTrack': return 'Players you track';
case 'formula': return 'Formula';
@ -1638,6 +1678,10 @@ extension on Translations {
case 'currentAxis': return ({required Object axis}) => '${axis} axis:';
case 'p1nkl0bst3rAlert': return 'That data was retrived from third party API maintained by p1nkl0bst3r';
case 'notForWeb': return 'Function is not available for web version';
case 'graphs.attack': return 'Attack';
case 'graphs.speed': return 'Speed';
case 'graphs.defense': return 'Defense';
case 'graphs.cheese': return 'Cheese';
case 'statCellNum.xpLevel': return 'XP Level';
case 'statCellNum.xpProgress': return 'Progress to next level';
case 'statCellNum.xpFrom0ToLevel': return ({required Object n}) => 'Progress from 0 XP to level ${n}';
@ -2101,6 +2145,10 @@ extension on _StringsRu {
case 'verdictWorse': return 'Хуже';
case 'smooth': return 'Гладкий';
case 'gamesUntilRanked': return ({required Object left}) => '${left} матчей до получения рейтинга';
case 'numOfVictories': return ({required Object wins}) => '~${wins} побед';
case 'promotionOnNextWin': return 'Повышение после следующей победы';
case 'numOfdefeats': return ({required Object losses}) => '~${losses} поражений';
case 'demotionOnNextLoss': return 'Понижение после следующего поражения';
case 'nerdStats': return 'Для задротов';
case 'playersYouTrack': return 'Отслеживаемые игроки';
case 'formula': return 'Формула';
@ -2219,6 +2267,10 @@ extension on _StringsRu {
case 'currentAxis': return ({required Object axis}) => 'Ось ${axis}:';
case 'p1nkl0bst3rAlert': return 'Эти данные были получены из стороннего API, который поддерживается p1nkl0bst3r';
case 'notForWeb': return 'Функция недоступна для веб версии';
case 'graphs.attack': return 'Атака';
case 'graphs.speed': return 'Скорость';
case 'graphs.defense': return 'Защита';
case 'graphs.cheese': return 'Сыр';
case 'statCellNum.xpLevel': return 'Уровень\nопыта';
case 'statCellNum.xpProgress': return 'Прогресс до следующего уровня';
case 'statCellNum.xpFrom0ToLevel': return ({required Object n}) => 'Прогресс от 0 XP до ${n} уровня';

View File

@ -70,6 +70,7 @@ class TetrioService extends DB {
// I'm trying to send as less requests, as possible, so i'm caching the results of those requests.
// Usually those maps looks like this: {"cached_until_unix_milliseconds": Object}
// TODO: Make a proper caching system
final Map<String, TetrioPlayer> _playersCache = {};
final Map<String, Map<String, dynamic>> _recordsCache = {};
final Map<String, dynamic> _replaysCache = {}; // the only one is different: {"replayID": [replayString, replayBytes]}
@ -78,6 +79,7 @@ class TetrioService extends DB {
final Map<String, List<News>> _newsCache = {};
final Map<String, Map<String, double?>> _topTRcache = {};
final Map<String, List<Map<String, double>>> _cutoffsCache = {};
final Map<String, TetrioPlayerFromLeaderboard> _topOneFromLB = {};
final Map<String, TetraLeagueAlphaStream> _tlStreamsCache = {};
/// Thing, that sends every request to the API endpoints
final client = kDebugMode ? UserAgentClient("Kagari-chan loves osk (Tetra Stats dev build)", http.Client()) : UserAgentClient("Tetra Stats v${packageInfo.version} (dm @dan63047 if someone abuse that software)", http.Client());
@ -314,7 +316,7 @@ class TetrioService extends DB {
Uri url;
if (kIsWeb) {
url = Uri.https('ts.dan63.by', 'oskware_bridge.php', {"endpoint": "PeakTR"});
url = Uri.https('ts.dan63.by', 'oskware_bridge.php', {"endpoint": "TLCutoffs"});
} else {
url = Uri.https('api.p1nkl0bst3r.xyz', 'rankcutoff', {"users": null});
}
@ -359,6 +361,58 @@ class TetrioService extends DB {
}
}
Future<TetrioPlayerFromLeaderboard> fetchTopOneFromTheLeaderboard() async {
try{
var cached = _topOneFromLB.entries.first;
if (DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true).isAfter(DateTime.now())){ // if not expired
developer.log("fetchTopOneFromTheLeaderboard: Leader retrieved from cache, that expires ${DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true)}", name: "services/tetrio_crud");
return cached.value;
}else{ // if cache expired
_topTRcache.remove(cached.key);
developer.log("fetchTopOneFromTheLeaderboard: Leader expired (${DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true)})", name: "services/tetrio_crud");
}
}catch(e){ // actually going to obtain
developer.log("fetchTopOneFromTheLeaderboard: Trying to retrieve leader", name: "services/tetrio_crud");
}
Uri url;
if (kIsWeb) {
url = Uri.https('ts.dan63.by', 'oskware_bridge.php', {"endpoint": "TLTopOne"});
} else {
url = Uri.https('ch.tetr.io', 'api/users/lists/league', {"after": "25000", "limit": "1"});
}
try{
final response = await client.get(url);
switch (response.statusCode) {
case 200:
var rawJson = jsonDecode(response.body);
return TetrioPlayerFromLeaderboard.fromJson(rawJson["data"]["users"][0], DateTime.fromMillisecondsSinceEpoch(rawJson["cache"]["cached_at"]));
case 404:
throw TetrioPlayerNotExist();
// if not 200 or 404 - throw a unique for each code exception
case 403:
throw TetrioForbidden();
case 429:
throw TetrioTooManyRequests();
case 418:
throw TetrioOskwareBridgeProblem();
case 500:
case 502:
case 503:
case 504:
throw P1nkl0bst3rInternalProblem();
default:
developer.log("fetchTopOneFromTheLeaderboard: Failed to fetch top one", 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
}
}
/// Retrieves Tetra League history from p1nkl0bst3r api for a player with given [id]. Returns a list of states
/// (state = instance of [TetrioPlayer] at some point of time). Can throw an exception if fails to retrieve data.
Future<List<TetrioPlayer>> fetchAndsaveTLHistory(String id) async {

View File

@ -109,7 +109,7 @@ class CalcState extends State<CalcView> {
],
),
),
Divider(),
const Divider(),
if (nerdStats == null) Text(t.calcViewNoValues)
else Column(children: [
_ListEntry(value: nerdStats!.app, label: t.statCellNum.app.replaceAll(RegExp(r'\n'), " "), fractionDigits: 3),

View File

@ -257,7 +257,7 @@ class CompareState extends State<CompareView> {
backgroundColor: Colors.black,
body: SingleChildScrollView(
controller: _scrollController,
physics: AlwaysScrollableScrollPhysics(),
physics: const AlwaysScrollableScrollPhysics(),
child: Center(
child: Container(
constraints: const BoxConstraints(maxWidth: 768),
@ -317,7 +317,7 @@ class CompareState extends State<CompareView> {
],
),
),
Divider(),
const Divider(),
if (!listEquals(theGreenSide, [null, null, null]) && !listEquals(theRedSide, [null, null, null])) Column(
children: [
if (theGreenSide[0] != null && theRedSide[0] != null && theGreenSide[0]!.role != "banned" && theRedSide[0]!.role != "banned")
@ -820,7 +820,7 @@ class CompareThingy extends StatelessWidget {
colors: const [Colors.green, Colors.transparent],
begin: Alignment.centerLeft,
end: Alignment.centerRight,
transform: GradientRotation(0.6),
transform: const GradientRotation(0.6),
stops: [
0.0,
higherIsBetter
@ -881,7 +881,7 @@ class CompareThingy extends StatelessWidget {
colors: const [Colors.red, Colors.transparent],
begin: Alignment.centerRight,
end: Alignment.centerLeft,
transform: GradientRotation(-0.6),
transform: const GradientRotation(-0.6),
stops: [
0.0,
higherIsBetter

View File

@ -1,10 +1,8 @@
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter_colorpicker/flutter_colorpicker.dart';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:tetra_stats/gen/strings.g.dart';
import 'package:tetra_stats/main.dart';
import 'package:window_manager/window_manager.dart';
late String oldWindowTitle;

View File

@ -1,6 +1,5 @@
// ignore_for_file: type_literal_in_constant_pattern
import 'dart:ffi';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
@ -8,7 +7,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:http/http.dart';
import 'package:intl/intl.dart';
import 'dart:math';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:flutter/services.dart';
import 'package:syncfusion_flutter_charts/charts.dart';
@ -177,18 +175,21 @@ class _MainState extends State<MainView> with TickerProviderStateMixin {
late TetraLeagueAlphaStream tlStream;
late Map<String, dynamic> records;
late List<News> news;
late TetrioPlayerFromLeaderboard? topOne;
late double? topTR;
requests = await Future.wait([ // all at once
teto.fetchTLStream(_searchFor),
teto.fetchRecords(_searchFor),
teto.fetchNews(_searchFor),
prefs.getBool("showPositions") != true ? teto.fetchCutoffs() : Future.delayed(Duration.zero, ()=>[]),
prefs.getBool("showPositions") != true ? teto.fetchCutoffs() : Future.delayed(Duration.zero, ()=><Map<String, double>>[]),
(me.tlSeason1.rank != "z" ? me.tlSeason1.rank == "x" : me.tlSeason1.percentileRank == "x") ? teto.fetchTopOneFromTheLeaderboard() : Future.delayed(Duration.zero, ()=>null),
if (me.tlSeason1.gamesPlayed > 9) teto.fetchTopTR(_searchFor) // can retrieve this only if player has TR
]);
tlStream = requests[0] as TetraLeagueAlphaStream;
records = requests[1] as Map<String, dynamic>;
news = requests[2] as List<News>;
topTR = requests.elementAtOrNull(4) as double?; // No TR - no Top TR
topOne = requests[4] as TetrioPlayerFromLeaderboard?;
topTR = requests.elementAtOrNull(5) as double?; // No TR - no Top TR
meAmongEveryone = teto.getCachedLeaderboardPositions(me.userId);
if (prefs.getBool("showPositions") == true){
@ -200,15 +201,14 @@ class _MainState extends State<MainView> with TickerProviderStateMixin {
if (meAmongEveryone != null) teto.cacheLeaderboardPositions(me.userId, meAmongEveryone!);
}
}
Map<String, double> cutoffs = prefs.getBool("showPositions") == true ? everyone!.cutoffs : requests[3][0];
Map<String, double> cutoffsGlicko = prefs.getBool("showPositions") == true ? everyone!.cutoffsGlicko : requests[3][1];
Map<String, double>? cutoffs = prefs.getBool("showPositions") == true ? everyone!.cutoffs : (requests[3] as List<Map<String, double>>).elementAtOrNull(0);
Map<String, double>? cutoffsGlicko = prefs.getBool("showPositions") == true ? everyone!.cutoffsGlicko : (requests[3] as List<Map<String, double>>).elementAtOrNull(1);
if (me.tlSeason1.gamesPlayed > 9) {
thatRankCutoff = cutoffs[me.tlSeason1.rank != "z" ? me.tlSeason1.rank : me.tlSeason1.percentileRank];
thatRankGlickoCutoff = cutoffsGlicko[me.tlSeason1.rank != "z" ? me.tlSeason1.rank : me.tlSeason1.percentileRank];
nextRankCutoff = cutoffs[ranks.elementAtOrNull(ranks.indexOf(me.tlSeason1.rank != "z" ? me.tlSeason1.rank : me.tlSeason1.percentileRank)+1)];
nextRankGlickoCutoff = cutoffsGlicko[ranks.elementAtOrNull(ranks.indexOf(me.tlSeason1.rank != "z" ? me.tlSeason1.rank : me.tlSeason1.percentileRank)+1)];
nextRankCutoff = nextRankCutoff??25000;
nextRankGlickoCutoff = nextRankGlickoCutoff??double.infinity;
thatRankCutoff = cutoffs?[me.tlSeason1.rank != "z" ? me.tlSeason1.rank : me.tlSeason1.percentileRank];
thatRankGlickoCutoff = cutoffsGlicko?[me.tlSeason1.rank != "z" ? me.tlSeason1.rank : me.tlSeason1.percentileRank];
nextRankCutoff = (me.tlSeason1.rank != "z" ? me.tlSeason1.rank == "x" : me.tlSeason1.percentileRank == "x") ? topOne?.rating??25000 : cutoffs?[ranks.elementAtOrNull(ranks.indexOf(me.tlSeason1.rank != "z" ? me.tlSeason1.rank : me.tlSeason1.percentileRank)+1)];
nextRankGlickoCutoff = (me.tlSeason1.rank != "z" ? me.tlSeason1.rank == "x" : me.tlSeason1.percentileRank == "x") ? topOne?.glicko??double.infinity : cutoffsGlicko?[ranks.elementAtOrNull(ranks.indexOf(me.tlSeason1.rank != "z" ? me.tlSeason1.rank : me.tlSeason1.percentileRank)+1)];
}
if (everyone != null && me.tlSeason1.gamesPlayed > 9) rankAverages = everyone?.averages[me.tlSeason1.percentileRank]?[0];
@ -487,6 +487,12 @@ class _MainState extends State<MainView> with TickerProviderStateMixin {
topTR: snapshot.data![7],
bot: snapshot.data![0].role == "bot",
guest: snapshot.data![0].role == "anon",
thatRankCutoff: thatRankCutoff,
thatRankCutoffGlicko: thatRankGlickoCutoff,
thatRankTarget: snapshot.data![0].tlSeason1.rank != "z" ? rankTargets[snapshot.data![0].tlSeason1.rank] : null,
nextRankCutoff: nextRankCutoff,
nextRankCutoffGlicko: nextRankGlickoCutoff,
nextRankTarget: (snapshot.data![0].tlSeason1.rank != "z" && snapshot.data![0].tlSeason1.rank != "x") ? rankTargets[ranks.elementAtOrNull(ranks.indexOf(snapshot.data![0].tlSeason1.rank)+1)] : null,
averages: rankAverages,
lbPositions: meAmongEveryone
),
@ -543,7 +549,7 @@ class _MainState extends State<MainView> with TickerProviderStateMixin {
Text(errText, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 42, fontWeight: FontWeight.bold), textAlign: TextAlign.center),
if (subText != null) Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Text(subText, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 18)),
child: Text(subText, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 18), textAlign: TextAlign.center),
),
],
)
@ -781,9 +787,6 @@ class _History extends StatelessWidget{
List<_HistoryChartSpot> selectedGraph = chartsData[_chartsIndex].value!;
return SingleChildScrollView(
scrollDirection: Axis.vertical,
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
primary: true,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
@ -831,7 +834,7 @@ class _History extends StatelessWidget{
Text(t.smooth, style: const TextStyle(color: Colors.white, fontSize: 22))
],
),
IconButton(onPressed: () => _zoomPanBehavior.reset(), icon: Icon(Icons.refresh), alignment: Alignment.center,)
IconButton(onPressed: () => _zoomPanBehavior.reset(), icon: const Icon(Icons.refresh), alignment: Alignment.center,)
],
),
if(chartsData[_chartsIndex].value!.length > 1) _HistoryChartThigy(data: selectedGraph, smooth: _smooth, yAxisTitle: _historyShortTitles[_chartsIndex], bigScreen: bigScreen, leftSpace: bigScreen? 80 : 45, yFormat: bigScreen? f2 : NumberFormat.compact(), xFormat: NumberFormat.compact())
@ -845,7 +848,6 @@ class _History extends StatelessWidget{
))
],
),
),
);
}
}
@ -901,10 +903,10 @@ class _HistoryChartThigyState extends State<_HistoryChartThigy> {
padding: const EdgeInsets.only(bottom: 8.0),
child: Text(
"${f4.format(data.stat)} ${widget.yAxisTitle}",
style: TextStyle(fontFamily: "Eurostile Round", fontSize: 20),
style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 20),
),
),
Text(_gamesPlayedInsteadOfDateAndTime ? "${f0.format(data.gamesPlayed)} games played" : _dateFormat.format(data.timestamp))
Text(_gamesPlayedInsteadOfDateAndTime ? t.gamesPlayed(games: t.games(n: data.gamesPlayed)) : _dateFormat.format(data.timestamp))
],
),
);
@ -943,8 +945,8 @@ class _HistoryChartThigyState extends State<_HistoryChartThigy> {
child: SfCartesianChart(
tooltipBehavior: _tooltipBehavior,
zoomPanBehavior: _zoomPanBehavior,
primaryXAxis: _gamesPlayedInsteadOfDateAndTime ? NumericAxis() : DateTimeAxis(),
primaryYAxis: NumericAxis(
primaryXAxis: _gamesPlayedInsteadOfDateAndTime ? const NumericAxis() : const DateTimeAxis(),
primaryYAxis: const NumericAxis(
rangePadding: ChartRangePadding.additional,
),
series: <CartesianSeries>[
@ -1079,7 +1081,7 @@ class _TwoRecordsThingy extends StatelessWidget {
),
if (sprint != null) FinesseThingy(sprint?.endContext?.finesse, sprint?.endContext?.finessePercentage),
if (sprint != null) LineclearsThingy(sprint!.endContext!.clears, sprint!.endContext!.lines, sprint!.endContext!.holds, sprint!.endContext!.tSpins),
if (sprint != null) Text("${sprint!.endContext!.inputs} KP • ${f2.format(sprint!.endContext!.kps)} KpS")
if (sprint != null) Text("${sprint!.endContext!.inputs} KP • ${f2.format(sprint!.endContext!.kps)} KPS")
]
),
Column(
@ -1140,7 +1142,7 @@ class _TwoRecordsThingy extends StatelessWidget {
),
if (blitz != null) FinesseThingy(blitz?.endContext?.finesse, blitz?.endContext?.finessePercentage),
if (blitz != null) LineclearsThingy(blitz!.endContext!.clears, blitz!.endContext!.lines, blitz!.endContext!.holds, blitz!.endContext!.tSpins),
if (blitz != null) Text("${blitz!.endContext!.piecesPlaced} P • ${blitz!.endContext!.inputs} KP • ${f2.format(blitz!.endContext!.kpp)} KpP • ${f2.format(blitz!.endContext!.kps)} KpS")
if (blitz != null) Text("${blitz!.endContext!.piecesPlaced} P • ${blitz!.endContext!.inputs} KP • ${f2.format(blitz!.endContext!.kpp)} KPP • ${f2.format(blitz!.endContext!.kps)} KPS")
],
),
]),
@ -1256,8 +1258,8 @@ class _RecordThingy extends StatelessWidget {
),
FinesseThingy(record?.endContext?.finesse, record?.endContext?.finessePercentage),
LineclearsThingy(record!.endContext!.clears, record!.endContext!.lines, record!.endContext!.holds, record!.endContext!.tSpins),
if (record!.stream.contains("40l")) Text("${record!.endContext!.inputs} KP • ${f2.format(record!.endContext!.kps)} KpS"),
if (record!.stream.contains("blitz")) Text("${record!.endContext!.piecesPlaced} P • ${record!.endContext!.inputs} KP • ${f2.format(record!.endContext!.kpp)} KpP • ${f2.format(record!.endContext!.kps)} KpS")
if (record!.stream.contains("40l")) Text("${record!.endContext!.inputs} KP • ${f2.format(record!.endContext!.kps)} KPS"),
if (record!.stream.contains("blitz")) Text("${record!.endContext!.piecesPlaced} P • ${record!.endContext!.inputs} KP • ${f2.format(record!.endContext!.kpp)} KPP • ${f2.format(record!.endContext!.kps)} KPS")
]
),
),

View File

@ -1,5 +1,4 @@
import 'dart:io';
import 'dart:math';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
@ -7,7 +6,6 @@ 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/views/main_view.dart' show MainView;
import 'package:tetra_stats/utils/text_shadow.dart';
import 'package:window_manager/window_manager.dart';
import 'package:syncfusion_flutter_charts/charts.dart';
@ -81,7 +79,7 @@ class RankState extends State<RankView> with SingleTickerProviderStateMixin {
padding: const EdgeInsets.only(bottom: 8.0),
child: Text(
"${data.nickname} (${data.rank.toUpperCase()})",
style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 20),
style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 20),
),
),
Text('${_f4.format(data.x)} ${chartsShortTitles[_chartsX]}\n${_f4.format(data.y)} ${chartsShortTitles[_chartsY]}')
@ -241,7 +239,7 @@ class RankState extends State<RankView> with SingleTickerProviderStateMixin {
),
],
),
IconButton(onPressed: () => _zoomPanBehavior.reset(), icon: Icon(Icons.refresh), alignment: Alignment.center,)
IconButton(onPressed: () => _zoomPanBehavior.reset(), icon: const Icon(Icons.refresh), alignment: Alignment.center,)
],
),
if (widget.rank[1]["entries"].length > 1)
@ -320,7 +318,9 @@ class RankState extends State<RankView> with SingleTickerProviderStateMixin {
checkColor: Colors.black,
onChanged: ((value) {
_reversed = value!;
setState(() {});
setState(() {
they = TetrioPlayersLeaderboard("lol", []).getStatRanking(widget.rank[1]["entries"]!, _sortBy, reversed: _reversed, country: _country);
});
}),
),
),
@ -337,7 +337,9 @@ class RankState extends State<RankView> with SingleTickerProviderStateMixin {
value: _country,
onChanged: ((value) {
_country = value;
setState(() {});
setState(() {
they = TetrioPlayersLeaderboard("lol", []).getStatRanking(widget.rank[1]["entries"]!, _sortBy, reversed: _reversed, country: _country);
});
}),
),
],
@ -352,7 +354,9 @@ class RankState extends State<RankView> with SingleTickerProviderStateMixin {
bool bigScreen = MediaQuery.of(context).size.width > 768;
return ListTile(
title: Text(they[index].username, style: const TextStyle(fontFamily: "Eurostile Round Extended")),
subtitle: Text(_sortBy == Stats.tr ? "${_f2.format(they[index].apm)} APM, ${_f2.format(they[index].pps)} PPS, ${_f2.format(they[index].vs)} VS, ${_f2.format(they[index].nerdStats.app)} APP, ${_f2.format(they[index].nerdStats.vsapm)} VS/APM" : "${_f4.format(they[index].getStatByEnum(_sortBy))} ${chartsShortTitles[_sortBy]}"),
subtitle: Text(
_sortBy == Stats.tr ? "${_f2.format(they[index].apm)} APM, ${_f2.format(they[index].pps)} PPS, ${_f2.format(they[index].vs)} VS, ${_f2.format(they[index].nerdStats.app)} APP, ${_f2.format(they[index].nerdStats.vsapm)} VS/APM" : "${_f4.format(they[index].getStatByEnum(_sortBy))} ${chartsShortTitles[_sortBy]}",
style: const TextStyle(fontFamily: "Eurostile Round Condensed", color: Colors.grey)),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [

View File

@ -55,7 +55,7 @@ class RanksAverages extends State<RankAveragesView> {
leading: Image.asset("res/tetrio_tl_alpha_ranks/${keys[index]}.png", height: 48),
title: Text(t.players(n: averages[keys[index]]?[1]["players"]), style: const TextStyle(fontFamily: "Eurostile Round Extended")),
subtitle: Text("${f2.format(averages[keys[index]]?[0].apm)} APM, ${f2.format(averages[keys[index]]?[0].pps)} PPS, ${f2.format(averages[keys[index]]?[0].vs)} VS, ${f2.format(averages[keys[index]]?[0].nerdStats.app)} APP, ${f2.format(averages[keys[index]]?[0].nerdStats.vsapm)} VS/APM",
style: TextStyle(fontFamily: "Eurostile Round Condensed", color: Colors.grey)),
style: const TextStyle(fontFamily: "Eurostile Round Condensed", color: Colors.grey)),
trailing: Text("${f2.format(averages[keys[index]]?[1]["toEnterTR"])} TR", style: bigScreen ? const TextStyle(fontSize: 28) : null),
onTap: (){
if (averages[keys[index]]?[1]["players"] > 0) {

View File

@ -97,7 +97,7 @@ class SettingsState extends State<SettingsView> {
children: [
ListTile(
title: Text(t.exportDB),
subtitle: Text(t.exportDBDescription),
subtitle: Text(t.exportDBDescription, style: const TextStyle(fontFamily: "Eurostile Round Condensed", color: Colors.grey)),
onTap: () {
if (kIsWeb){
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.notForWeb)));
@ -151,7 +151,7 @@ class SettingsState extends State<SettingsView> {
),
ListTile(
title: Text(t.importDB),
subtitle: Text(t.importDBDescription),
subtitle: Text(t.importDBDescription, style: const TextStyle(fontFamily: "Eurostile Round Condensed", color: Colors.grey)),
onTap: () {
if (kIsWeb){
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.notForWeb)));
@ -262,13 +262,13 @@ class SettingsState extends State<SettingsView> {
),
),
ListTile(title: Text(t.customization),
subtitle: Text(t.customizationDescription),
subtitle: Text(t.customizationDescription, style: const TextStyle(fontFamily: "Eurostile Round Condensed", color: Colors.grey)),
trailing: const Icon(Icons.arrow_right),
onTap: () {
context.go("/customization");
},),
ListTile(title: Text(t.lbStats),
subtitle: Text(t.lbStatsDescription),
subtitle: Text(t.lbStatsDescription, style: const TextStyle(fontFamily: "Eurostile Round Condensed", color: Colors.grey)),
trailing: Switch(value: showPositions, onChanged: (bool value){
prefs.setBool("showPositions", value);
setState(() {
@ -280,7 +280,7 @@ class SettingsState extends State<SettingsView> {
onTap: (){
launchInBrowser(Uri.https("github.com", "dan63047/TetraStats"));
},
title: Text(t.aboutApp),
title: Text(t.aboutApp, style: TextStyle(fontWeight: FontWeight.w500),),
subtitle: Text(t.aboutAppText(appName: packageInfo.appName, packageName: packageInfo.packageName, version: packageInfo.version, buildNumber: packageInfo.buildNumber)),
trailing: const Icon(Icons.arrow_right)
),

View File

@ -1,9 +1,6 @@
// ignore_for_file: use_build_context_synchronously
import 'dart:io';
import 'dart:math';
import 'package:flutter/cupertino.dart';
import 'package:flutter/widgets.dart';
import 'package:tetra_stats/data_objects/tetrio_multiplayer_replay.dart';
import 'package:tetra_stats/services/crud_exceptions.dart';
import 'package:tetra_stats/views/compare_view.dart' show CompareThingy, CompareBoolThingy;
@ -234,12 +231,12 @@ class TlMatchResultState extends State<TlMatchResultView> {
roundSelector.isNegative ? snapshot.data!.totalStats[greenSidePlayer].kpp : snapshot.data!.stats[roundSelector][greenSidePlayer].kpp,
redSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[redSidePlayer].kpp :
roundSelector.isNegative ? snapshot.data!.totalStats[redSidePlayer].kpp : snapshot.data!.stats[roundSelector][redSidePlayer].kpp,
label: "KpP", higherIsBetter: false, fractionDigits: 2,),
label: "KPP", higherIsBetter: false, fractionDigits: 2,),
CompareThingy(greenSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[greenSidePlayer].kps :
roundSelector.isNegative ? snapshot.data!.totalStats[greenSidePlayer].kps : snapshot.data!.stats[roundSelector][greenSidePlayer].kps,
redSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[redSidePlayer].kps :
roundSelector.isNegative ? snapshot.data!.totalStats[redSidePlayer].kps : snapshot.data!.stats[roundSelector][redSidePlayer].kps,
label: "KpS", higherIsBetter: true, fractionDigits: 2,),
label: "KPS", higherIsBetter: true, fractionDigits: 2,),
CompareThingy(greenSide: roundSelector.isNegative ? snapshot.data!.totalStats[greenSidePlayer].linesCleared : snapshot.data!.stats[roundSelector][greenSidePlayer].linesCleared,
redSide: roundSelector.isNegative ? snapshot.data!.totalStats[redSidePlayer].linesCleared : snapshot.data!.stats[roundSelector][redSidePlayer].linesCleared,
label: "Lines Cleared", higherIsBetter: true),
@ -250,7 +247,7 @@ class TlMatchResultState extends State<TlMatchResultView> {
roundSelector.isNegative ? snapshot.data!.totalStats[greenSidePlayer].spp : snapshot.data!.stats[roundSelector][greenSidePlayer].spp,
redSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[redSidePlayer].spp :
roundSelector.isNegative ? snapshot.data!.totalStats[redSidePlayer].spp : snapshot.data!.stats[roundSelector][redSidePlayer].spp,
label: "SpP", higherIsBetter: true, fractionDigits: 2,),
label: "SPP", higherIsBetter: true, fractionDigits: 2,),
CompareThingy(greenSide: roundSelector.isNegative ? snapshot.data!.totalStats[greenSidePlayer].finessePercentage * 100 : snapshot.data!.stats[roundSelector][greenSidePlayer].finessePercentage * 100,
redSide: roundSelector.isNegative ? snapshot.data!.totalStats[redSidePlayer].finessePercentage * 100 : snapshot.data!.stats[roundSelector][redSidePlayer].finessePercentage * 100,
label: "Finnese", postfix: "%", fractionDigits: 2, higherIsBetter: true),

View File

@ -1,6 +1,7 @@
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:tetra_stats/data_objects/tetrio.dart';
import 'package:tetra_stats/gen/strings.g.dart';
import 'package:tetra_stats/utils/numers_formats.dart';
class Graphs extends StatelessWidget{
@ -108,59 +109,6 @@ class Graphs extends StatelessWidget{
),
),
),
Padding( // sq graph
padding: const EdgeInsets.fromLTRB(18, 0, 18, 44),
child: SizedBox(
height: 310,
width: 310,
child: RadarChart(
RadarChartData(
radarShape: RadarShape.polygon,
tickCount: 4,
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: 'Attack\n${f2.format(apm)} APM', angle: 0, positionPercentageOffset: 0.05);
case 1:
return RadarChartTitle(text: 'Speed\n${f2.format(pps)} PPS', angle: 0, positionPercentageOffset: 0.05);
case 2:
return RadarChartTitle(text: 'Defense\n${f2.format(nerdStats.dss)} DS/S', angle: angle + 180, positionPercentageOffset: 0.05);
case 3:
return RadarChartTitle(text: 'Cheese\n${f3.format(nerdStats.cheese)}', angle: 0, positionPercentageOffset: 0.05);
default:
return const RadarChartTitle(text: '');
}
},
dataSets: [
RadarDataSet(
dataEntries: [
RadarEntry(value: attack),
RadarEntry(value: speed),
RadarEntry(value: defense),
RadarEntry(value: cheese),
],
),
RadarDataSet(
fillColor: Colors.transparent,
borderColor: Colors.transparent,
dataEntries: [
const RadarEntry(value: 0),
const RadarEntry(value: 1.2),
const RadarEntry(value: 0),
const RadarEntry(value: 0),
],
)
],
)
)
)
),
Padding( // psq graph
padding: const EdgeInsets.fromLTRB(18, 0, 18, 44),
child: SizedBox(
@ -216,6 +164,59 @@ class Graphs extends StatelessWidget{
),
),
),
Padding( // sq graph
padding: const EdgeInsets.fromLTRB(18, 0, 18, 44),
child: SizedBox(
height: 310,
width: 310,
child: RadarChart(
RadarChartData(
radarShape: RadarShape.polygon,
tickCount: 4,
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: '${t.graphs.attack}\n${f2.format(apm)} APM', angle: 0, positionPercentageOffset: 0.05);
case 1:
return RadarChartTitle(text: '${t.graphs.speed}\n${f2.format(pps)} PPS', angle: 0, positionPercentageOffset: 0.05);
case 2:
return RadarChartTitle(text: '${t.graphs.defense}\n${f2.format(nerdStats.dss)} DS/S', angle: angle + 180, positionPercentageOffset: 0.05);
case 3:
return RadarChartTitle(text: '${t.graphs.cheese}\n${f3.format(nerdStats.cheese)}', angle: 0, positionPercentageOffset: 0.05);
default:
return const RadarChartTitle(text: '');
}
},
dataSets: [
RadarDataSet(
dataEntries: [
RadarEntry(value: attack),
RadarEntry(value: speed),
RadarEntry(value: defense),
RadarEntry(value: cheese),
],
),
RadarDataSet(
fillColor: Colors.transparent,
borderColor: Colors.transparent,
dataEntries: [
const RadarEntry(value: 0),
const RadarEntry(value: 1.2),
const RadarEntry(value: 0),
const RadarEntry(value: 0),
],
)
],
)
)
)
)
],
);
}

View File

@ -31,6 +31,7 @@ class TLProgress extends StatelessWidget{
@override
Widget build(BuildContext context) {
if (nextRank == null && previousRank == null && nextRankTRcutoff == null && previousRankTRcutoff == null && nextRankGlickoCutoff == null && previousGlickoCutoff == null && nextRankTRcutoffTarget == null && previousRankTRcutoffTarget == null) return Container();
final glickoForWin = rate(tlData.glicko!, tlData.rd!, 0.06, [[tlData.glicko!, tlData.rd!, 1]], {})[0]-tlData.glicko!;
return Padding(
padding: const EdgeInsets.fromLTRB(8, 0, 8, 0),
@ -48,13 +49,13 @@ class TLProgress extends StatelessWidget{
child: RichText(
textAlign: TextAlign.left,
text: TextSpan(
style: TextStyle(color: Colors.white, fontFamily: "Eurostile Round", fontSize: 12),
style: const TextStyle(color: Colors.white, fontFamily: "Eurostile Round", fontSize: 12),
children: [
if (tlData.prevAt > 0) TextSpan(text: "${f0.format(tlData.prevAt)}"),
if (tlData.prevAt > 0 && previousRankTRcutoff != null) TextSpan(text: "\n"),
if (previousRankTRcutoff != null) TextSpan(text: "${intf.format(previousRankTRcutoff)} (${comparef2.format(previousRankTRcutoff!-tlData.rating)}) TR"),
if ((tlData.prevAt > 0 || previousRankTRcutoff != null) && previousGlickoCutoff != null) TextSpan(text: "\n"),
if (previousGlickoCutoff != null) TextSpan(text: "~${f2.format((tlData.glicko!-previousGlickoCutoff!)/glickoForWin)} defeats")
if (tlData.prevAt > 0 && previousRankTRcutoff != null) const TextSpan(text: "\n"),
if (previousRankTRcutoff != null) TextSpan(text: "${f2.format(previousRankTRcutoff)} (${comparef2.format(previousRankTRcutoff!-tlData.rating)}) TR"),
if ((tlData.prevAt > 0 || previousRankTRcutoff != null) && previousGlickoCutoff != null) const TextSpan(text: "\n"),
if (previousGlickoCutoff != null) TextSpan(text: (tlData.standing > tlData.prevAt || ((tlData.glicko!-previousGlickoCutoff!)/glickoForWin < 0.5 && tlData.percentileRank != "d")) ? t.demotionOnNextLoss : t.numOfdefeats(losses: f2.format((tlData.glicko!-previousGlickoCutoff!)/glickoForWin)), style: TextStyle(color: (tlData.standing > tlData.prevAt || ((tlData.glicko!-previousGlickoCutoff!)/glickoForWin < 0.5 && tlData.percentileRank != "d")) ? Colors.redAccent : null))
]
)
),
@ -63,13 +64,13 @@ class TLProgress extends StatelessWidget{
child: RichText(
textAlign: TextAlign.right,
text: TextSpan(
style: TextStyle(color: Colors.white, fontFamily: "Eurostile Round", fontSize: 12),
style: const TextStyle(color: Colors.white, fontFamily: "Eurostile Round", fontSize: 12),
children: [
if (tlData.nextAt > 0) TextSpan(text: "${f0.format(tlData.nextAt)}"),
if (tlData.nextAt > 0 && nextRankTRcutoff != null) TextSpan(text: "\n"),
if (nextRankTRcutoff != null) TextSpan(text: "${intf.format(nextRankTRcutoff)} (${comparef2.format(nextRankTRcutoff!-tlData.rating)}) TR"),
if ((tlData.nextAt > 0 || nextRankTRcutoff != null) && nextRankGlickoCutoff != null) TextSpan(text: "\n"),
if (nextRankGlickoCutoff != null) TextSpan(text: "~${f2.format((nextRankGlickoCutoff!-tlData.glicko!)/glickoForWin)} victories")
if (tlData.nextAt > 0 && nextRankTRcutoff != null) const TextSpan(text: "\n"),
if (nextRankTRcutoff != null) TextSpan(text: "${f2.format(nextRankTRcutoff)} (${comparef2.format(nextRankTRcutoff!-tlData.rating)}) TR"),
if ((tlData.nextAt > 0 || nextRankTRcutoff != null) && nextRankGlickoCutoff != null) const TextSpan(text: "\n"),
if (nextRankGlickoCutoff != null) TextSpan(text: (tlData.standing < tlData.nextAt || (nextRankGlickoCutoff!-tlData.glicko!)/glickoForWin < 0.5) ? t.promotionOnNextWin : t.numOfVictories(wins: f2.format((nextRankGlickoCutoff!-tlData.glicko!)/glickoForWin)), style: TextStyle(color: (tlData.standing < tlData.nextAt || (nextRankGlickoCutoff!-tlData.glicko!)/glickoForWin < 0.5) ? Colors.greenAccent : null))
]
)
),
@ -84,22 +85,14 @@ class TLProgress extends StatelessWidget{
if (previousRankTRcutoff != null && nextRankTRcutoff != null) LinearGaugeRange(endValue: getBarTR(tlData.rating)!, color: Colors.cyanAccent, position: LinearElementPosition.cross)
else if (tlData.standing != -1) LinearGaugeRange(endValue: getBarPosition(), color: Colors.cyanAccent, position: LinearElementPosition.cross),
if (previousRankTRcutoff != null && previousRankTRcutoffTarget != null) LinearGaugeRange(endValue: getBarTR(previousRankTRcutoffTarget!)!, color: Colors.greenAccent, position: LinearElementPosition.inside),
if (nextRankTRcutoff != null && nextRankTRcutoffTarget != null) LinearGaugeRange(startValue: getBarTR(nextRankTRcutoffTarget!)!, endValue: 1, color: Colors.yellowAccent, position: LinearElementPosition.inside)
if (nextRankTRcutoff != null && nextRankTRcutoffTarget != null && previousRankTRcutoff != null) LinearGaugeRange(startValue: getBarTR(nextRankTRcutoffTarget!)!, endValue: 1, color: Colors.yellowAccent, position: LinearElementPosition.inside)
],
markerPointers: [
LinearShapePointer(value: (previousRankTRcutoff != null && nextRankTRcutoff != null) ? getBarTR(tlData.rating)! : getBarPosition(), position: LinearElementPosition.cross, shapeType: LinearShapePointerType.diamond, color: Colors.white, height: 20),
if (tlData.standing != -1) LinearWidgetPointer(offset: 4, position: LinearElementPosition.outside, value: (previousRankTRcutoff != null && nextRankTRcutoff != null) ? getBarTR(tlData.rating)! : getBarPosition(), child: Text("${NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 0).format(tlData.standing)}", style: TextStyle(fontSize: 14),))
if (tlData.standing != -1) LinearWidgetPointer(offset: 4, position: LinearElementPosition.outside, value: (previousRankTRcutoff != null && nextRankTRcutoff != null) ? getBarTR(tlData.rating)! : getBarPosition(), child: Text("${NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 0).format(tlData.standing)}", style: const TextStyle(fontSize: 14),))
],
isMirrored: true,
showTicks: true,
// onGenerateLabels: () => [
// LinearAxisLabel(text: "${tlData.prevAt > 0 ? "${f0.format(tlData.prevAt)}" : ""}\n ${intf.format(previousRankTRcutoff)} TR", value: 0),
// LinearAxisLabel(text: "${tlData.nextAt > 0 ? "${f0.format(tlData.nextAt)}" : ""}\n ${intf.format(nextRankTRcutoff)} TR", value: 1),
// ],
// labelFormatterCallback: (value) {
// if (value == "0") return "${f0.format(previousRankPosition)}\n 26,700 TR";
// else return f0.format(nextRankPosition);
// },
showLabels: false
)
]

View File

@ -49,11 +49,7 @@ class _TLThingyState extends State<TLThingy> {
_currentRangeValues = const RangeValues(0, 1);
sortedStates = widget.states.reversed.toList();
oskKagariGimmick = prefs.getBool("oskKagariGimmick")??true;
try{
oldTl = sortedStates[1].tlSeason1;
}on RangeError{
oldTl = null;
}
oldTl = sortedStates.elementAtOrNull(1)?.tlSeason1;
currentTl = widget.tl;
super.initState();
}
@ -142,27 +138,7 @@ class _TLThingyState extends State<TLThingy> {
),
],
),
if (currentTl.gamesPlayed > 9)
// if (currentTl.gamesPlayed >= 10 && currentTl.rd! < 100 && currentTl.nextAt >=0 && currentTl.prevAt >= 0)
// Padding(
// padding: const EdgeInsets.all(8.0),
// child: SfLinearGauge(
// minimum: currentTl.nextAt.toDouble(),
// maximum: currentTl.prevAt.toDouble(),
// interval: currentTl.prevAt.toDouble() - currentTl.nextAt.toDouble(),
// ranges: [
// LinearGaugeRange(startValue: currentTl.standing.toDouble() <= currentTl.prevAt.toDouble() ? currentTl.standing.toDouble() : currentTl.prevAt.toDouble(), endValue: currentTl.prevAt.toDouble(), color: Colors.cyanAccent, position: LinearElementPosition.cross,),
// //LinearGaugeRange(startValue: currentTl.standing.toDouble() <= currentTl.prevAt.toDouble() ? currentTl.standing.toDouble() + 500.00 : currentTl.prevAt.toDouble(), endValue: currentTl.prevAt.toDouble(), color: Colors.amber, position: LinearElementPosition.inside,)
// ],
// markerPointers: [LinearShapePointer(value: currentTl.standing.toDouble() <= currentTl.prevAt.toDouble() ? currentTl.standing.toDouble() : currentTl.prevAt.toDouble(), position: LinearElementPosition.inside, shapeType: LinearShapePointerType.triangle, color: Colors.white, height: 20),
// LinearWidgetPointer(offset: 4, position: LinearElementPosition.outside, value: currentTl.standing.toDouble() <= currentTl.prevAt.toDouble() ? currentTl.standing.toDouble() : currentTl.prevAt.toDouble(), child: Text(NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 0).format(currentTl.standing)))],
// isAxisInversed: true,
// isMirrored: true,
// showTicks: true,
// showLabels: true
// ),
// ),
TLProgress(
if (currentTl.gamesPlayed > 9) TLProgress(
tlData: currentTl,
previousRankTRcutoff: widget.thatRankCutoff,
previousGlickoCutoff: widget.thatRankCutoffGlicko,

View File

@ -1,3 +1,4 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:syncfusion_flutter_gauges/gauges.dart';
@ -52,7 +53,7 @@ class UserThingy extends StatelessWidget {
alignment: Alignment.topCenter,
children: [
if (player.bannerRevision != null)
Image.network("https://tetr.io/user-content/banners/${player.userId}.jpg?rv=${player.bannerRevision}",
Image.network(kIsWeb ? "https://ts.dan63.by/oskware_bridge.php?endpoint=TetrioBanner&user=${player.userId}&rv=${player.bannerRevision}" : "https://tetr.io/user-content/banners/${player.userId}.jpg?rv=${player.bannerRevision}",
fit: BoxFit.cover,
height: bannerHeight,
errorBuilder: (context, error, stackTrace) {
@ -90,7 +91,7 @@ class UserThingy extends StatelessWidget {
child: player.role == "banned"
? Image.asset("res/avatars/tetrio_banned.png", fit: BoxFit.fitHeight, height: pfpHeight,)
: player.avatarRevision != null
? Image.network("https://tetr.io/user-content/avatars/${player.userId}.jpg?rv=${player.avatarRevision}",
? Image.network(kIsWeb ? "https://ts.dan63.by/oskware_bridge.php?endpoint=TetrioProfilePicture&user=${player.userId}&rv=${player.avatarRevision}" : "https://tetr.io/user-content/avatars/${player.userId}.jpg?rv=${player.avatarRevision}",
// TODO: osk banner can cause memory leak
fit: BoxFit.fitHeight, height: 128, errorBuilder: (context, error, stackTrace) {
developer.log("Error with building profile picture", name: "main_view", error: error, stackTrace: stackTrace);

View File

@ -1,6 +1,7 @@
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:tetra_stats/data_objects/tetrio.dart';
import 'package:tetra_stats/gen/strings.g.dart';
class VsGraphs extends StatelessWidget{
final double greenAPM;
@ -205,6 +206,71 @@ class VsGraphs extends StatelessWidget{
),
),
),
Padding( // sq graph
padding: const EdgeInsets.fromLTRB(18, 0, 18, 44),
child: SizedBox(
height: 310,
width: 310,
child: RadarChart(
RadarChartData(
radarShape: RadarShape.polygon,
tickCount: 4,
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: t.graphs.attack, angle: 0, positionPercentageOffset: 0.05);
case 1:
return RadarChartTitle(text: t.graphs.speed, angle: 0, positionPercentageOffset: 0.05);
case 2:
return RadarChartTitle(text: t.graphs.defense, angle: angle + 180, positionPercentageOffset: 0.05);
case 3:
return RadarChartTitle(text: t.graphs.cheese, angle: 0, positionPercentageOffset: 0.05);
default:
return const RadarChartTitle(text: '');
}
},
dataSets: [
RadarDataSet(
fillColor: const Color.fromARGB(115, 76, 175, 79),
borderColor: Colors.green,
dataEntries: [
RadarEntry(value: greenAPM / 60 * 0.4),
RadarEntry(value: greenPPS / 3.75),
RadarEntry(value: greenNerdStats.dss * 1.15),
RadarEntry(value: greenNerdStats.cheese / 110),
],
),
RadarDataSet(
fillColor: const Color.fromARGB(115, 244, 67, 54),
borderColor: Colors.red,
dataEntries: [
RadarEntry(value: redAPM / 60 * 0.4),
RadarEntry(value: redPPS / 3.75),
RadarEntry(value: redNerdStats.dss * 1.15),
RadarEntry(value: redNerdStats.cheese / 110),
],
),
RadarDataSet(
fillColor: Colors.transparent,
borderColor: Colors.transparent,
dataEntries: [
const RadarEntry(value: 0),
const RadarEntry(value: 1.2),
const RadarEntry(value: 0),
const RadarEntry(value: 0),
],
)
],
)
)
)
)
],
);
}

View File

@ -2,7 +2,7 @@ name: tetra_stats
description: Track your and other player stats in TETR.IO
publish_to: 'none'
version: 1.5.0+16
version: 1.5.1+17
environment:
sdk: '>=3.0.0'

View File

@ -83,6 +83,10 @@
"verdictWorse": "worse",
"smooth": "Smooth",
"gamesUntilRanked": "${left} games until being ranked",
"numOfVictories": "~${wins} victories",
"promotionOnNextWin": "Promotion on next win",
"numOfdefeats": "~${losses} defeats",
"demotionOnNextLoss": "Demotion on next loss",
"nerdStats": "Nerd Stats",
"playersYouTrack": "Players you track",
"formula": "Formula",
@ -201,6 +205,12 @@
"currentAxis": "$axis axis:",
"p1nkl0bst3rAlert": "That data was retrived from third party API maintained by p1nkl0bst3r",
"notForWeb": "Function is not available for web version",
"graphs": {
"attack": "Attack",
"speed": "Speed",
"defense": "Defense",
"cheese": "Cheese"
},
"statCellNum":{
"xpLevel": "XP Level",
"xpProgress": "Progress to next level",

View File

@ -83,6 +83,10 @@
"verdictWorse": "Хуже",
"smooth": "Гладкий",
"gamesUntilRanked": "${left} матчей до получения рейтинга",
"numOfVictories": "~${wins} побед",
"promotionOnNextWin": "Повышение после следующей победы",
"numOfdefeats": "~${losses} поражений",
"demotionOnNextLoss": "Понижение после следующего поражения",
"nerdStats": "Для задротов",
"playersYouTrack": "Отслеживаемые игроки",
"formula": "Формула",
@ -201,6 +205,12 @@
"currentAxis": "Ось $axis:",
"p1nkl0bst3rAlert": "Эти данные были получены из стороннего API, который поддерживается p1nkl0bst3r",
"notForWeb": "Функция недоступна для веб версии",
"graphs": {
"attack": "Атака",
"speed": "Скорость",
"defense": "Защита",
"cheese": "Сыр"
},
"statCellNum": {
"xpLevel": "Уровень\nопыта",
"xpProgress": "Прогресс до следующего уровня",