i guess 1.5.1 is ready
This commit is contained in:
parent
9aa67686da
commit
fcab60f7ba
|
@ -1,5 +1,4 @@
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
import 'package:vector_math/vector_math_64.dart';
|
|
||||||
|
|
||||||
import 'tetrio.dart';
|
import 'tetrio.dart';
|
||||||
|
|
||||||
|
|
|
@ -4,9 +4,9 @@
|
||||||
/// To regenerate, run: `dart run slang`
|
/// To regenerate, run: `dart run slang`
|
||||||
///
|
///
|
||||||
/// Locales: 2
|
/// 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
|
// coverage:ignore-file
|
||||||
// ignore_for_file: type=lint
|
// ignore_for_file: type=lint
|
||||||
|
@ -218,6 +218,10 @@ class Translations implements BaseTranslations<AppLocale, Translations> {
|
||||||
String get verdictWorse => 'worse';
|
String get verdictWorse => 'worse';
|
||||||
String get smooth => 'Smooth';
|
String get smooth => 'Smooth';
|
||||||
String gamesUntilRanked({required Object left}) => '${left} games until being ranked';
|
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 nerdStats => 'Nerd Stats';
|
||||||
String get playersYouTrack => 'Players you track';
|
String get playersYouTrack => 'Players you track';
|
||||||
String get formula => 'Formula';
|
String get formula => 'Formula';
|
||||||
|
@ -336,6 +340,7 @@ class Translations implements BaseTranslations<AppLocale, Translations> {
|
||||||
String currentAxis({required Object axis}) => '${axis} axis:';
|
String currentAxis({required Object axis}) => '${axis} axis:';
|
||||||
String get p1nkl0bst3rAlert => 'That data was retrived from third party API maintained by p1nkl0bst3r';
|
String get p1nkl0bst3rAlert => 'That data was retrived from third party API maintained by p1nkl0bst3r';
|
||||||
String get notForWeb => 'Function is not available for web version';
|
String get notForWeb => 'Function is not available for web version';
|
||||||
|
late final _StringsGraphsEn graphs = _StringsGraphsEn._(_root);
|
||||||
late final _StringsStatCellNumEn statCellNum = _StringsStatCellNumEn._(_root);
|
late final _StringsStatCellNumEn statCellNum = _StringsStatCellNumEn._(_root);
|
||||||
Map<String, String> get playerRole => {
|
Map<String, String> get playerRole => {
|
||||||
'user': 'User',
|
'user': 'User',
|
||||||
|
@ -635,6 +640,19 @@ class _StringsNewsPartsEn {
|
||||||
String unknownNews({required Object type}) => 'Unknown news of type ${type}';
|
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
|
// Path: statCellNum
|
||||||
class _StringsStatCellNumEn {
|
class _StringsStatCellNumEn {
|
||||||
_StringsStatCellNumEn._(this._root);
|
_StringsStatCellNumEn._(this._root);
|
||||||
|
@ -873,6 +891,10 @@ class _StringsRu implements Translations {
|
||||||
@override String get verdictWorse => 'Хуже';
|
@override String get verdictWorse => 'Хуже';
|
||||||
@override String get smooth => 'Гладкий';
|
@override String get smooth => 'Гладкий';
|
||||||
@override String gamesUntilRanked({required Object left}) => '${left} матчей до получения рейтинга';
|
@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 nerdStats => 'Для задротов';
|
||||||
@override String get playersYouTrack => 'Отслеживаемые игроки';
|
@override String get playersYouTrack => 'Отслеживаемые игроки';
|
||||||
@override String get formula => 'Формула';
|
@override String get formula => 'Формула';
|
||||||
|
@ -991,6 +1013,7 @@ class _StringsRu implements Translations {
|
||||||
@override String currentAxis({required Object axis}) => 'Ось ${axis}:';
|
@override String currentAxis({required Object axis}) => 'Ось ${axis}:';
|
||||||
@override String get p1nkl0bst3rAlert => 'Эти данные были получены из стороннего API, который поддерживается p1nkl0bst3r';
|
@override String get p1nkl0bst3rAlert => 'Эти данные были получены из стороннего API, который поддерживается p1nkl0bst3r';
|
||||||
@override String get notForWeb => 'Функция недоступна для веб версии';
|
@override String get notForWeb => 'Функция недоступна для веб версии';
|
||||||
|
@override late final _StringsGraphsRu graphs = _StringsGraphsRu._(_root);
|
||||||
@override late final _StringsStatCellNumRu statCellNum = _StringsStatCellNumRu._(_root);
|
@override late final _StringsStatCellNumRu statCellNum = _StringsStatCellNumRu._(_root);
|
||||||
@override Map<String, String> get playerRole => {
|
@override Map<String, String> get playerRole => {
|
||||||
'user': 'Пользователь',
|
'user': 'Пользователь',
|
||||||
|
@ -1290,6 +1313,19 @@ class _StringsNewsPartsRu implements _StringsNewsPartsEn {
|
||||||
@override String unknownNews({required Object type}) => 'Неизвестная новость типа ${type}';
|
@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
|
// Path: statCellNum
|
||||||
class _StringsStatCellNumRu implements _StringsStatCellNumEn {
|
class _StringsStatCellNumRu implements _StringsStatCellNumEn {
|
||||||
_StringsStatCellNumRu._(this._root);
|
_StringsStatCellNumRu._(this._root);
|
||||||
|
@ -1520,6 +1556,10 @@ extension on Translations {
|
||||||
case 'verdictWorse': return 'worse';
|
case 'verdictWorse': return 'worse';
|
||||||
case 'smooth': return 'Smooth';
|
case 'smooth': return 'Smooth';
|
||||||
case 'gamesUntilRanked': return ({required Object left}) => '${left} games until being ranked';
|
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 'nerdStats': return 'Nerd Stats';
|
||||||
case 'playersYouTrack': return 'Players you track';
|
case 'playersYouTrack': return 'Players you track';
|
||||||
case 'formula': return 'Formula';
|
case 'formula': return 'Formula';
|
||||||
|
@ -1638,6 +1678,10 @@ extension on Translations {
|
||||||
case 'currentAxis': return ({required Object axis}) => '${axis} axis:';
|
case 'currentAxis': return ({required Object axis}) => '${axis} axis:';
|
||||||
case 'p1nkl0bst3rAlert': return 'That data was retrived from third party API maintained by p1nkl0bst3r';
|
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 '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.xpLevel': return 'XP Level';
|
||||||
case 'statCellNum.xpProgress': return 'Progress to next level';
|
case 'statCellNum.xpProgress': return 'Progress to next level';
|
||||||
case 'statCellNum.xpFrom0ToLevel': return ({required Object n}) => 'Progress from 0 XP to level ${n}';
|
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 'verdictWorse': return 'Хуже';
|
||||||
case 'smooth': return 'Гладкий';
|
case 'smooth': return 'Гладкий';
|
||||||
case 'gamesUntilRanked': return ({required Object left}) => '${left} матчей до получения рейтинга';
|
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 'nerdStats': return 'Для задротов';
|
||||||
case 'playersYouTrack': return 'Отслеживаемые игроки';
|
case 'playersYouTrack': return 'Отслеживаемые игроки';
|
||||||
case 'formula': return 'Формула';
|
case 'formula': return 'Формула';
|
||||||
|
@ -2219,6 +2267,10 @@ extension on _StringsRu {
|
||||||
case 'currentAxis': return ({required Object axis}) => 'Ось ${axis}:';
|
case 'currentAxis': return ({required Object axis}) => 'Ось ${axis}:';
|
||||||
case 'p1nkl0bst3rAlert': return 'Эти данные были получены из стороннего API, который поддерживается p1nkl0bst3r';
|
case 'p1nkl0bst3rAlert': return 'Эти данные были получены из стороннего API, который поддерживается p1nkl0bst3r';
|
||||||
case 'notForWeb': return 'Функция недоступна для веб версии';
|
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.xpLevel': return 'Уровень\nопыта';
|
||||||
case 'statCellNum.xpProgress': return 'Прогресс до следующего уровня';
|
case 'statCellNum.xpProgress': return 'Прогресс до следующего уровня';
|
||||||
case 'statCellNum.xpFrom0ToLevel': return ({required Object n}) => 'Прогресс от 0 XP до ${n} уровня';
|
case 'statCellNum.xpFrom0ToLevel': return ({required Object n}) => 'Прогресс от 0 XP до ${n} уровня';
|
||||||
|
|
|
@ -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.
|
// 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}
|
// 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, TetrioPlayer> _playersCache = {};
|
||||||
final Map<String, Map<String, dynamic>> _recordsCache = {};
|
final Map<String, Map<String, dynamic>> _recordsCache = {};
|
||||||
final Map<String, dynamic> _replaysCache = {}; // the only one is different: {"replayID": [replayString, replayBytes]}
|
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, List<News>> _newsCache = {};
|
||||||
final Map<String, Map<String, double?>> _topTRcache = {};
|
final Map<String, Map<String, double?>> _topTRcache = {};
|
||||||
final Map<String, List<Map<String, double>>> _cutoffsCache = {};
|
final Map<String, List<Map<String, double>>> _cutoffsCache = {};
|
||||||
|
final Map<String, TetrioPlayerFromLeaderboard> _topOneFromLB = {};
|
||||||
final Map<String, TetraLeagueAlphaStream> _tlStreamsCache = {};
|
final Map<String, TetraLeagueAlphaStream> _tlStreamsCache = {};
|
||||||
/// Thing, that sends every request to the API endpoints
|
/// 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());
|
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;
|
Uri url;
|
||||||
if (kIsWeb) {
|
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 {
|
} else {
|
||||||
url = Uri.https('api.p1nkl0bst3r.xyz', 'rankcutoff', {"users": null});
|
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
|
/// 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.
|
/// (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 {
|
Future<List<TetrioPlayer>> fetchAndsaveTLHistory(String id) async {
|
||||||
|
|
|
@ -109,7 +109,7 @@ class CalcState extends State<CalcView> {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Divider(),
|
const Divider(),
|
||||||
if (nerdStats == null) Text(t.calcViewNoValues)
|
if (nerdStats == null) Text(t.calcViewNoValues)
|
||||||
else Column(children: [
|
else Column(children: [
|
||||||
_ListEntry(value: nerdStats!.app, label: t.statCellNum.app.replaceAll(RegExp(r'\n'), " "), fractionDigits: 3),
|
_ListEntry(value: nerdStats!.app, label: t.statCellNum.app.replaceAll(RegExp(r'\n'), " "), fractionDigits: 3),
|
||||||
|
|
|
@ -257,7 +257,7 @@ class CompareState extends State<CompareView> {
|
||||||
backgroundColor: Colors.black,
|
backgroundColor: Colors.black,
|
||||||
body: SingleChildScrollView(
|
body: SingleChildScrollView(
|
||||||
controller: _scrollController,
|
controller: _scrollController,
|
||||||
physics: AlwaysScrollableScrollPhysics(),
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Container(
|
child: Container(
|
||||||
constraints: const BoxConstraints(maxWidth: 768),
|
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(
|
if (!listEquals(theGreenSide, [null, null, null]) && !listEquals(theRedSide, [null, null, null])) Column(
|
||||||
children: [
|
children: [
|
||||||
if (theGreenSide[0] != null && theRedSide[0] != null && theGreenSide[0]!.role != "banned" && theRedSide[0]!.role != "banned")
|
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],
|
colors: const [Colors.green, Colors.transparent],
|
||||||
begin: Alignment.centerLeft,
|
begin: Alignment.centerLeft,
|
||||||
end: Alignment.centerRight,
|
end: Alignment.centerRight,
|
||||||
transform: GradientRotation(0.6),
|
transform: const GradientRotation(0.6),
|
||||||
stops: [
|
stops: [
|
||||||
0.0,
|
0.0,
|
||||||
higherIsBetter
|
higherIsBetter
|
||||||
|
@ -881,7 +881,7 @@ class CompareThingy extends StatelessWidget {
|
||||||
colors: const [Colors.red, Colors.transparent],
|
colors: const [Colors.red, Colors.transparent],
|
||||||
begin: Alignment.centerRight,
|
begin: Alignment.centerRight,
|
||||||
end: Alignment.centerLeft,
|
end: Alignment.centerLeft,
|
||||||
transform: GradientRotation(-0.6),
|
transform: const GradientRotation(-0.6),
|
||||||
stops: [
|
stops: [
|
||||||
0.0,
|
0.0,
|
||||||
higherIsBetter
|
higherIsBetter
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter_colorpicker/flutter_colorpicker.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:tetra_stats/gen/strings.g.dart';
|
import 'package:tetra_stats/gen/strings.g.dart';
|
||||||
import 'package:tetra_stats/main.dart';
|
|
||||||
import 'package:window_manager/window_manager.dart';
|
import 'package:window_manager/window_manager.dart';
|
||||||
|
|
||||||
late String oldWindowTitle;
|
late String oldWindowTitle;
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
// ignore_for_file: type_literal_in_constant_pattern
|
// ignore_for_file: type_literal_in_constant_pattern
|
||||||
|
|
||||||
import 'dart:ffi';
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
|
@ -8,7 +7,6 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
import 'package:http/http.dart';
|
import 'package:http/http.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'dart:math';
|
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:syncfusion_flutter_charts/charts.dart';
|
import 'package:syncfusion_flutter_charts/charts.dart';
|
||||||
|
@ -177,18 +175,21 @@ class _MainState extends State<MainView> with TickerProviderStateMixin {
|
||||||
late TetraLeagueAlphaStream tlStream;
|
late TetraLeagueAlphaStream tlStream;
|
||||||
late Map<String, dynamic> records;
|
late Map<String, dynamic> records;
|
||||||
late List<News> news;
|
late List<News> news;
|
||||||
|
late TetrioPlayerFromLeaderboard? topOne;
|
||||||
late double? topTR;
|
late double? topTR;
|
||||||
requests = await Future.wait([ // all at once
|
requests = await Future.wait([ // all at once
|
||||||
teto.fetchTLStream(_searchFor),
|
teto.fetchTLStream(_searchFor),
|
||||||
teto.fetchRecords(_searchFor),
|
teto.fetchRecords(_searchFor),
|
||||||
teto.fetchNews(_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
|
if (me.tlSeason1.gamesPlayed > 9) teto.fetchTopTR(_searchFor) // can retrieve this only if player has TR
|
||||||
]);
|
]);
|
||||||
tlStream = requests[0] as TetraLeagueAlphaStream;
|
tlStream = requests[0] as TetraLeagueAlphaStream;
|
||||||
records = requests[1] as Map<String, dynamic>;
|
records = requests[1] as Map<String, dynamic>;
|
||||||
news = requests[2] as List<News>;
|
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);
|
meAmongEveryone = teto.getCachedLeaderboardPositions(me.userId);
|
||||||
if (prefs.getBool("showPositions") == true){
|
if (prefs.getBool("showPositions") == true){
|
||||||
|
@ -200,15 +201,14 @@ class _MainState extends State<MainView> with TickerProviderStateMixin {
|
||||||
if (meAmongEveryone != null) teto.cacheLeaderboardPositions(me.userId, meAmongEveryone!);
|
if (meAmongEveryone != null) teto.cacheLeaderboardPositions(me.userId, meAmongEveryone!);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Map<String, double> cutoffs = prefs.getBool("showPositions") == true ? everyone!.cutoffs : requests[3][0];
|
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][1];
|
Map<String, double>? cutoffsGlicko = prefs.getBool("showPositions") == true ? everyone!.cutoffsGlicko : (requests[3] as List<Map<String, double>>).elementAtOrNull(1);
|
||||||
|
|
||||||
if (me.tlSeason1.gamesPlayed > 9) {
|
if (me.tlSeason1.gamesPlayed > 9) {
|
||||||
thatRankCutoff = cutoffs[me.tlSeason1.rank != "z" ? me.tlSeason1.rank : me.tlSeason1.percentileRank];
|
thatRankCutoff = cutoffs?[me.tlSeason1.rank != "z" ? me.tlSeason1.rank : me.tlSeason1.percentileRank];
|
||||||
thatRankGlickoCutoff = cutoffsGlicko[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)];
|
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 = cutoffsGlicko[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)];
|
||||||
nextRankCutoff = nextRankCutoff??25000;
|
|
||||||
nextRankGlickoCutoff = nextRankGlickoCutoff??double.infinity;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (everyone != null && me.tlSeason1.gamesPlayed > 9) rankAverages = everyone?.averages[me.tlSeason1.percentileRank]?[0];
|
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],
|
topTR: snapshot.data![7],
|
||||||
bot: snapshot.data![0].role == "bot",
|
bot: snapshot.data![0].role == "bot",
|
||||||
guest: snapshot.data![0].role == "anon",
|
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,
|
averages: rankAverages,
|
||||||
lbPositions: meAmongEveryone
|
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),
|
Text(errText, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 42, fontWeight: FontWeight.bold), textAlign: TextAlign.center),
|
||||||
if (subText != null) Padding(
|
if (subText != null) Padding(
|
||||||
padding: const EdgeInsets.only(top: 8.0),
|
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,71 +787,67 @@ class _History extends StatelessWidget{
|
||||||
List<_HistoryChartSpot> selectedGraph = chartsData[_chartsIndex].value!;
|
List<_HistoryChartSpot> selectedGraph = chartsData[_chartsIndex].value!;
|
||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
scrollDirection: Axis.vertical,
|
scrollDirection: Axis.vertical,
|
||||||
child: SingleChildScrollView(
|
child: Column(
|
||||||
scrollDirection: Axis.horizontal,
|
mainAxisSize: MainAxisSize.min,
|
||||||
primary: true,
|
children: [
|
||||||
child: Column(
|
Wrap(
|
||||||
mainAxisSize: MainAxisSize.min,
|
spacing: 20,
|
||||||
children: [
|
crossAxisAlignment: WrapCrossAlignment.center,
|
||||||
Wrap(
|
children: [
|
||||||
spacing: 20,
|
Row(
|
||||||
crossAxisAlignment: WrapCrossAlignment.center,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Row(
|
const Padding(padding: EdgeInsets.all(8.0), child: Text("X:", style: TextStyle(fontSize: 22))),
|
||||||
mainAxisSize: MainAxisSize.min,
|
DropdownButton(
|
||||||
children: [
|
items: const [DropdownMenuItem(value: false, child: Text("Date & Time")), DropdownMenuItem(value: true, child: Text("Games Played"))],
|
||||||
const Padding(padding: EdgeInsets.all(8.0), child: Text("X:", style: TextStyle(fontSize: 22))),
|
value: _gamesPlayedInsteadOfDateAndTime,
|
||||||
DropdownButton(
|
onChanged: (value) {
|
||||||
items: const [DropdownMenuItem(value: false, child: Text("Date & Time")), DropdownMenuItem(value: true, child: Text("Games Played"))],
|
_gamesPlayedInsteadOfDateAndTime = value!;
|
||||||
value: _gamesPlayedInsteadOfDateAndTime,
|
update();
|
||||||
onChanged: (value) {
|
}
|
||||||
_gamesPlayedInsteadOfDateAndTime = value!;
|
),
|
||||||
update();
|
|
||||||
}
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
const Padding(padding: EdgeInsets.all(8.0), child: Text("Y:", style: TextStyle(fontSize: 22))),
|
|
||||||
DropdownButton(
|
|
||||||
items: chartsData,
|
|
||||||
value: chartsData[_chartsIndex].value,
|
|
||||||
onChanged: (value) {
|
|
||||||
_chartsIndex = chartsData.indexWhere((element) => element.value == value);
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (selectedGraph.length > 300) Row(
|
Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Checkbox(value: _smooth,
|
const Padding(padding: EdgeInsets.all(8.0), child: Text("Y:", style: TextStyle(fontSize: 22))),
|
||||||
checkColor: Colors.black,
|
DropdownButton(
|
||||||
onChanged: ((value) {
|
items: chartsData,
|
||||||
_smooth = value!;
|
value: chartsData[_chartsIndex].value,
|
||||||
update();
|
onChanged: (value) {
|
||||||
})),
|
_chartsIndex = chartsData.indexWhere((element) => element.value == value);
|
||||||
Text(t.smooth, style: const TextStyle(color: Colors.white, fontSize: 22))
|
update();
|
||||||
],
|
}
|
||||||
),
|
),
|
||||||
IconButton(onPressed: () => _zoomPanBehavior.reset(), icon: Icon(Icons.refresh), alignment: Alignment.center,)
|
],
|
||||||
],
|
),
|
||||||
),
|
if (selectedGraph.length > 300) Row(
|
||||||
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())
|
mainAxisSize: MainAxisSize.min,
|
||||||
else if (chartsData[_chartsIndex].value!.length <= 1) Center(child: Column(
|
children: [
|
||||||
mainAxisSize: MainAxisSize.min,
|
Checkbox(value: _smooth,
|
||||||
children: [
|
checkColor: Colors.black,
|
||||||
Text(t.notEnoughData, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 28)),
|
onChanged: ((value) {
|
||||||
if (wasActiveInTL) Text(t.errors.actionSuggestion),
|
_smooth = value!;
|
||||||
if (wasActiveInTL) TextButton(onPressed: (){changePlayer(userID, fetchHistory: true);}, child: Text(t.fetchAndsaveTLHistory))
|
update();
|
||||||
],
|
})),
|
||||||
))
|
Text(t.smooth, style: const TextStyle(color: Colors.white, fontSize: 22))
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
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())
|
||||||
|
else if (chartsData[_chartsIndex].value!.length <= 1) Center(child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(t.notEnoughData, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 28)),
|
||||||
|
if (wasActiveInTL) Text(t.errors.actionSuggestion),
|
||||||
|
if (wasActiveInTL) TextButton(onPressed: (){changePlayer(userID, fetchHistory: true);}, child: Text(t.fetchAndsaveTLHistory))
|
||||||
|
],
|
||||||
|
))
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -901,10 +903,10 @@ class _HistoryChartThigyState extends State<_HistoryChartThigy> {
|
||||||
padding: const EdgeInsets.only(bottom: 8.0),
|
padding: const EdgeInsets.only(bottom: 8.0),
|
||||||
child: Text(
|
child: Text(
|
||||||
"${f4.format(data.stat)} ${widget.yAxisTitle}",
|
"${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(
|
child: SfCartesianChart(
|
||||||
tooltipBehavior: _tooltipBehavior,
|
tooltipBehavior: _tooltipBehavior,
|
||||||
zoomPanBehavior: _zoomPanBehavior,
|
zoomPanBehavior: _zoomPanBehavior,
|
||||||
primaryXAxis: _gamesPlayedInsteadOfDateAndTime ? NumericAxis() : DateTimeAxis(),
|
primaryXAxis: _gamesPlayedInsteadOfDateAndTime ? const NumericAxis() : const DateTimeAxis(),
|
||||||
primaryYAxis: NumericAxis(
|
primaryYAxis: const NumericAxis(
|
||||||
rangePadding: ChartRangePadding.additional,
|
rangePadding: ChartRangePadding.additional,
|
||||||
),
|
),
|
||||||
series: <CartesianSeries>[
|
series: <CartesianSeries>[
|
||||||
|
@ -1079,7 +1081,7 @@ class _TwoRecordsThingy extends StatelessWidget {
|
||||||
),
|
),
|
||||||
if (sprint != null) FinesseThingy(sprint?.endContext?.finesse, sprint?.endContext?.finessePercentage),
|
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) 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(
|
Column(
|
||||||
|
@ -1140,7 +1142,7 @@ class _TwoRecordsThingy extends StatelessWidget {
|
||||||
),
|
),
|
||||||
if (blitz != null) FinesseThingy(blitz?.endContext?.finesse, blitz?.endContext?.finessePercentage),
|
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) 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),
|
FinesseThingy(record?.endContext?.finesse, record?.endContext?.finessePercentage),
|
||||||
LineclearsThingy(record!.endContext!.clears, record!.endContext!.lines, record!.endContext!.holds, record!.endContext!.tSpins),
|
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("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("blitz")) Text("${record!.endContext!.piecesPlaced} P • ${record!.endContext!.inputs} KP • ${f2.format(record!.endContext!.kpp)} KPP • ${f2.format(record!.endContext!.kps)} KPS")
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:math';
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.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/data_objects/tetrio.dart';
|
||||||
import 'package:tetra_stats/gen/strings.g.dart';
|
import 'package:tetra_stats/gen/strings.g.dart';
|
||||||
import 'package:tetra_stats/views/main_view.dart' show MainView;
|
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:window_manager/window_manager.dart';
|
||||||
import 'package:syncfusion_flutter_charts/charts.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),
|
padding: const EdgeInsets.only(bottom: 8.0),
|
||||||
child: Text(
|
child: Text(
|
||||||
"${data.nickname} (${data.rank.toUpperCase()})",
|
"${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]}')
|
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)
|
if (widget.rank[1]["entries"].length > 1)
|
||||||
|
@ -320,7 +318,9 @@ class RankState extends State<RankView> with SingleTickerProviderStateMixin {
|
||||||
checkColor: Colors.black,
|
checkColor: Colors.black,
|
||||||
onChanged: ((value) {
|
onChanged: ((value) {
|
||||||
_reversed = 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,
|
value: _country,
|
||||||
onChanged: ((value) {
|
onChanged: ((value) {
|
||||||
_country = 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;
|
bool bigScreen = MediaQuery.of(context).size.width > 768;
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: Text(they[index].username, style: const TextStyle(fontFamily: "Eurostile Round Extended")),
|
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(
|
trailing: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
|
|
|
@ -55,7 +55,7 @@ class RanksAverages extends State<RankAveragesView> {
|
||||||
leading: Image.asset("res/tetrio_tl_alpha_ranks/${keys[index]}.png", height: 48),
|
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")),
|
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",
|
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),
|
trailing: Text("${f2.format(averages[keys[index]]?[1]["toEnterTR"])} TR", style: bigScreen ? const TextStyle(fontSize: 28) : null),
|
||||||
onTap: (){
|
onTap: (){
|
||||||
if (averages[keys[index]]?[1]["players"] > 0) {
|
if (averages[keys[index]]?[1]["players"] > 0) {
|
||||||
|
|
|
@ -97,7 +97,7 @@ class SettingsState extends State<SettingsView> {
|
||||||
children: [
|
children: [
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(t.exportDB),
|
title: Text(t.exportDB),
|
||||||
subtitle: Text(t.exportDBDescription),
|
subtitle: Text(t.exportDBDescription, style: const TextStyle(fontFamily: "Eurostile Round Condensed", color: Colors.grey)),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (kIsWeb){
|
if (kIsWeb){
|
||||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.notForWeb)));
|
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.notForWeb)));
|
||||||
|
@ -151,7 +151,7 @@ class SettingsState extends State<SettingsView> {
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(t.importDB),
|
title: Text(t.importDB),
|
||||||
subtitle: Text(t.importDBDescription),
|
subtitle: Text(t.importDBDescription, style: const TextStyle(fontFamily: "Eurostile Round Condensed", color: Colors.grey)),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (kIsWeb){
|
if (kIsWeb){
|
||||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.notForWeb)));
|
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.notForWeb)));
|
||||||
|
@ -262,13 +262,13 @@ class SettingsState extends State<SettingsView> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
ListTile(title: Text(t.customization),
|
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),
|
trailing: const Icon(Icons.arrow_right),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.go("/customization");
|
context.go("/customization");
|
||||||
},),
|
},),
|
||||||
ListTile(title: Text(t.lbStats),
|
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){
|
trailing: Switch(value: showPositions, onChanged: (bool value){
|
||||||
prefs.setBool("showPositions", value);
|
prefs.setBool("showPositions", value);
|
||||||
setState(() {
|
setState(() {
|
||||||
|
@ -280,7 +280,7 @@ class SettingsState extends State<SettingsView> {
|
||||||
onTap: (){
|
onTap: (){
|
||||||
launchInBrowser(Uri.https("github.com", "dan63047/TetraStats"));
|
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)),
|
subtitle: Text(t.aboutAppText(appName: packageInfo.appName, packageName: packageInfo.packageName, version: packageInfo.version, buildNumber: packageInfo.buildNumber)),
|
||||||
trailing: const Icon(Icons.arrow_right)
|
trailing: const Icon(Icons.arrow_right)
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
// ignore_for_file: use_build_context_synchronously
|
// ignore_for_file: use_build_context_synchronously
|
||||||
|
|
||||||
import 'dart:io';
|
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/data_objects/tetrio_multiplayer_replay.dart';
|
||||||
import 'package:tetra_stats/services/crud_exceptions.dart';
|
import 'package:tetra_stats/services/crud_exceptions.dart';
|
||||||
import 'package:tetra_stats/views/compare_view.dart' show CompareThingy, CompareBoolThingy;
|
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,
|
roundSelector.isNegative ? snapshot.data!.totalStats[greenSidePlayer].kpp : snapshot.data!.stats[roundSelector][greenSidePlayer].kpp,
|
||||||
redSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[redSidePlayer].kpp :
|
redSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[redSidePlayer].kpp :
|
||||||
roundSelector.isNegative ? snapshot.data!.totalStats[redSidePlayer].kpp : snapshot.data!.stats[roundSelector][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 :
|
CompareThingy(greenSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[greenSidePlayer].kps :
|
||||||
roundSelector.isNegative ? snapshot.data!.totalStats[greenSidePlayer].kps : snapshot.data!.stats[roundSelector][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 :
|
redSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[redSidePlayer].kps :
|
||||||
roundSelector.isNegative ? snapshot.data!.totalStats[redSidePlayer].kps : snapshot.data!.stats[roundSelector][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,
|
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,
|
redSide: roundSelector.isNegative ? snapshot.data!.totalStats[redSidePlayer].linesCleared : snapshot.data!.stats[roundSelector][redSidePlayer].linesCleared,
|
||||||
label: "Lines Cleared", higherIsBetter: true),
|
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,
|
roundSelector.isNegative ? snapshot.data!.totalStats[greenSidePlayer].spp : snapshot.data!.stats[roundSelector][greenSidePlayer].spp,
|
||||||
redSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[redSidePlayer].spp :
|
redSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[redSidePlayer].spp :
|
||||||
roundSelector.isNegative ? snapshot.data!.totalStats[redSidePlayer].spp : snapshot.data!.stats[roundSelector][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,
|
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,
|
redSide: roundSelector.isNegative ? snapshot.data!.totalStats[redSidePlayer].finessePercentage * 100 : snapshot.data!.stats[roundSelector][redSidePlayer].finessePercentage * 100,
|
||||||
label: "Finnese", postfix: "%", fractionDigits: 2, higherIsBetter: true),
|
label: "Finnese", postfix: "%", fractionDigits: 2, higherIsBetter: true),
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import 'package:fl_chart/fl_chart.dart';
|
import 'package:fl_chart/fl_chart.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:tetra_stats/data_objects/tetrio.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';
|
import 'package:tetra_stats/utils/numers_formats.dart';
|
||||||
|
|
||||||
class Graphs extends StatelessWidget{
|
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( // psq graph
|
||||||
padding: const EdgeInsets.fromLTRB(18, 0, 18, 44),
|
padding: const EdgeInsets.fromLTRB(18, 0, 18, 44),
|
||||||
child: SizedBox(
|
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),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,7 @@ class TLProgress extends StatelessWidget{
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
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!;
|
final glickoForWin = rate(tlData.glicko!, tlData.rd!, 0.06, [[tlData.glicko!, tlData.rd!, 1]], {})[0]-tlData.glicko!;
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(8, 0, 8, 0),
|
padding: const EdgeInsets.fromLTRB(8, 0, 8, 0),
|
||||||
|
@ -48,13 +49,13 @@ class TLProgress extends StatelessWidget{
|
||||||
child: RichText(
|
child: RichText(
|
||||||
textAlign: TextAlign.left,
|
textAlign: TextAlign.left,
|
||||||
text: TextSpan(
|
text: TextSpan(
|
||||||
style: TextStyle(color: Colors.white, fontFamily: "Eurostile Round", fontSize: 12),
|
style: const TextStyle(color: Colors.white, fontFamily: "Eurostile Round", fontSize: 12),
|
||||||
children: [
|
children: [
|
||||||
if (tlData.prevAt > 0) TextSpan(text: "№ ${f0.format(tlData.prevAt)}"),
|
if (tlData.prevAt > 0) TextSpan(text: "№ ${f0.format(tlData.prevAt)}"),
|
||||||
if (tlData.prevAt > 0 && previousRankTRcutoff != null) TextSpan(text: "\n"),
|
if (tlData.prevAt > 0 && previousRankTRcutoff != null) const TextSpan(text: "\n"),
|
||||||
if (previousRankTRcutoff != null) TextSpan(text: "${intf.format(previousRankTRcutoff)} (${comparef2.format(previousRankTRcutoff!-tlData.rating)}) TR"),
|
if (previousRankTRcutoff != null) TextSpan(text: "${f2.format(previousRankTRcutoff)} (${comparef2.format(previousRankTRcutoff!-tlData.rating)}) TR"),
|
||||||
if ((tlData.prevAt > 0 || previousRankTRcutoff != null) && previousGlickoCutoff != null) TextSpan(text: "\n"),
|
if ((tlData.prevAt > 0 || previousRankTRcutoff != null) && previousGlickoCutoff != null) const TextSpan(text: "\n"),
|
||||||
if (previousGlickoCutoff != null) TextSpan(text: "~${f2.format((tlData.glicko!-previousGlickoCutoff!)/glickoForWin)} defeats")
|
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(
|
child: RichText(
|
||||||
textAlign: TextAlign.right,
|
textAlign: TextAlign.right,
|
||||||
text: TextSpan(
|
text: TextSpan(
|
||||||
style: TextStyle(color: Colors.white, fontFamily: "Eurostile Round", fontSize: 12),
|
style: const TextStyle(color: Colors.white, fontFamily: "Eurostile Round", fontSize: 12),
|
||||||
children: [
|
children: [
|
||||||
if (tlData.nextAt > 0) TextSpan(text: "№ ${f0.format(tlData.nextAt)}"),
|
if (tlData.nextAt > 0) TextSpan(text: "№ ${f0.format(tlData.nextAt)}"),
|
||||||
if (tlData.nextAt > 0 && nextRankTRcutoff != null) TextSpan(text: "\n"),
|
if (tlData.nextAt > 0 && nextRankTRcutoff != null) const TextSpan(text: "\n"),
|
||||||
if (nextRankTRcutoff != null) TextSpan(text: "${intf.format(nextRankTRcutoff)} (${comparef2.format(nextRankTRcutoff!-tlData.rating)}) TR"),
|
if (nextRankTRcutoff != null) TextSpan(text: "${f2.format(nextRankTRcutoff)} (${comparef2.format(nextRankTRcutoff!-tlData.rating)}) TR"),
|
||||||
if ((tlData.nextAt > 0 || nextRankTRcutoff != null) && nextRankGlickoCutoff != null) TextSpan(text: "\n"),
|
if ((tlData.nextAt > 0 || nextRankTRcutoff != null) && nextRankGlickoCutoff != null) const TextSpan(text: "\n"),
|
||||||
if (nextRankGlickoCutoff != null) TextSpan(text: "~${f2.format((nextRankGlickoCutoff!-tlData.glicko!)/glickoForWin)} victories")
|
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)
|
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),
|
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 (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: [
|
markerPointers: [
|
||||||
LinearShapePointer(value: (previousRankTRcutoff != null && nextRankTRcutoff != null) ? getBarTR(tlData.rating)! : getBarPosition(), position: LinearElementPosition.cross, shapeType: LinearShapePointerType.diamond, color: Colors.white, height: 20),
|
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,
|
isMirrored: true,
|
||||||
showTicks: 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
|
showLabels: false
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
|
@ -49,11 +49,7 @@ class _TLThingyState extends State<TLThingy> {
|
||||||
_currentRangeValues = const RangeValues(0, 1);
|
_currentRangeValues = const RangeValues(0, 1);
|
||||||
sortedStates = widget.states.reversed.toList();
|
sortedStates = widget.states.reversed.toList();
|
||||||
oskKagariGimmick = prefs.getBool("oskKagariGimmick")??true;
|
oskKagariGimmick = prefs.getBool("oskKagariGimmick")??true;
|
||||||
try{
|
oldTl = sortedStates.elementAtOrNull(1)?.tlSeason1;
|
||||||
oldTl = sortedStates[1].tlSeason1;
|
|
||||||
}on RangeError{
|
|
||||||
oldTl = null;
|
|
||||||
}
|
|
||||||
currentTl = widget.tl;
|
currentTl = widget.tl;
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
@ -142,27 +138,7 @@ class _TLThingyState extends State<TLThingy> {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (currentTl.gamesPlayed > 9)
|
if (currentTl.gamesPlayed > 9) TLProgress(
|
||||||
// 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(
|
|
||||||
tlData: currentTl,
|
tlData: currentTl,
|
||||||
previousRankTRcutoff: widget.thatRankCutoff,
|
previousRankTRcutoff: widget.thatRankCutoff,
|
||||||
previousGlickoCutoff: widget.thatRankCutoffGlicko,
|
previousGlickoCutoff: widget.thatRankCutoffGlicko,
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:syncfusion_flutter_gauges/gauges.dart';
|
import 'package:syncfusion_flutter_gauges/gauges.dart';
|
||||||
|
@ -52,7 +53,7 @@ class UserThingy extends StatelessWidget {
|
||||||
alignment: Alignment.topCenter,
|
alignment: Alignment.topCenter,
|
||||||
children: [
|
children: [
|
||||||
if (player.bannerRevision != null)
|
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,
|
fit: BoxFit.cover,
|
||||||
height: bannerHeight,
|
height: bannerHeight,
|
||||||
errorBuilder: (context, error, stackTrace) {
|
errorBuilder: (context, error, stackTrace) {
|
||||||
|
@ -90,7 +91,7 @@ class UserThingy extends StatelessWidget {
|
||||||
child: player.role == "banned"
|
child: player.role == "banned"
|
||||||
? Image.asset("res/avatars/tetrio_banned.png", fit: BoxFit.fitHeight, height: pfpHeight,)
|
? Image.asset("res/avatars/tetrio_banned.png", fit: BoxFit.fitHeight, height: pfpHeight,)
|
||||||
: player.avatarRevision != null
|
: 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
|
// TODO: osk banner can cause memory leak
|
||||||
fit: BoxFit.fitHeight, height: 128, errorBuilder: (context, error, stackTrace) {
|
fit: BoxFit.fitHeight, height: 128, errorBuilder: (context, error, stackTrace) {
|
||||||
developer.log("Error with building profile picture", name: "main_view", error: error, stackTrace: stackTrace);
|
developer.log("Error with building profile picture", name: "main_view", error: error, stackTrace: stackTrace);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import 'package:fl_chart/fl_chart.dart';
|
import 'package:fl_chart/fl_chart.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:tetra_stats/data_objects/tetrio.dart';
|
import 'package:tetra_stats/data_objects/tetrio.dart';
|
||||||
|
import 'package:tetra_stats/gen/strings.g.dart';
|
||||||
|
|
||||||
class VsGraphs extends StatelessWidget{
|
class VsGraphs extends StatelessWidget{
|
||||||
final double greenAPM;
|
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),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ name: tetra_stats
|
||||||
description: Track your and other player stats in TETR.IO
|
description: Track your and other player stats in TETR.IO
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
|
|
||||||
version: 1.5.0+16
|
version: 1.5.1+17
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.0.0'
|
sdk: '>=3.0.0'
|
||||||
|
|
|
@ -83,6 +83,10 @@
|
||||||
"verdictWorse": "worse",
|
"verdictWorse": "worse",
|
||||||
"smooth": "Smooth",
|
"smooth": "Smooth",
|
||||||
"gamesUntilRanked": "${left} games until being ranked",
|
"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",
|
"nerdStats": "Nerd Stats",
|
||||||
"playersYouTrack": "Players you track",
|
"playersYouTrack": "Players you track",
|
||||||
"formula": "Formula",
|
"formula": "Formula",
|
||||||
|
@ -201,6 +205,12 @@
|
||||||
"currentAxis": "$axis axis:",
|
"currentAxis": "$axis axis:",
|
||||||
"p1nkl0bst3rAlert": "That data was retrived from third party API maintained by p1nkl0bst3r",
|
"p1nkl0bst3rAlert": "That data was retrived from third party API maintained by p1nkl0bst3r",
|
||||||
"notForWeb": "Function is not available for web version",
|
"notForWeb": "Function is not available for web version",
|
||||||
|
"graphs": {
|
||||||
|
"attack": "Attack",
|
||||||
|
"speed": "Speed",
|
||||||
|
"defense": "Defense",
|
||||||
|
"cheese": "Cheese"
|
||||||
|
},
|
||||||
"statCellNum":{
|
"statCellNum":{
|
||||||
"xpLevel": "XP Level",
|
"xpLevel": "XP Level",
|
||||||
"xpProgress": "Progress to next level",
|
"xpProgress": "Progress to next level",
|
||||||
|
|
|
@ -83,6 +83,10 @@
|
||||||
"verdictWorse": "Хуже",
|
"verdictWorse": "Хуже",
|
||||||
"smooth": "Гладкий",
|
"smooth": "Гладкий",
|
||||||
"gamesUntilRanked": "${left} матчей до получения рейтинга",
|
"gamesUntilRanked": "${left} матчей до получения рейтинга",
|
||||||
|
"numOfVictories": "~${wins} побед",
|
||||||
|
"promotionOnNextWin": "Повышение после следующей победы",
|
||||||
|
"numOfdefeats": "~${losses} поражений",
|
||||||
|
"demotionOnNextLoss": "Понижение после следующего поражения",
|
||||||
"nerdStats": "Для задротов",
|
"nerdStats": "Для задротов",
|
||||||
"playersYouTrack": "Отслеживаемые игроки",
|
"playersYouTrack": "Отслеживаемые игроки",
|
||||||
"formula": "Формула",
|
"formula": "Формула",
|
||||||
|
@ -201,6 +205,12 @@
|
||||||
"currentAxis": "Ось $axis:",
|
"currentAxis": "Ось $axis:",
|
||||||
"p1nkl0bst3rAlert": "Эти данные были получены из стороннего API, который поддерживается p1nkl0bst3r",
|
"p1nkl0bst3rAlert": "Эти данные были получены из стороннего API, который поддерживается p1nkl0bst3r",
|
||||||
"notForWeb": "Функция недоступна для веб версии",
|
"notForWeb": "Функция недоступна для веб версии",
|
||||||
|
"graphs": {
|
||||||
|
"attack": "Атака",
|
||||||
|
"speed": "Скорость",
|
||||||
|
"defense": "Защита",
|
||||||
|
"cheese": "Сыр"
|
||||||
|
},
|
||||||
"statCellNum": {
|
"statCellNum": {
|
||||||
"xpLevel": "Уровень\nопыта",
|
"xpLevel": "Уровень\nопыта",
|
||||||
"xpProgress": "Прогресс до следующего уровня",
|
"xpProgress": "Прогресс до следующего уровня",
|
||||||
|
|
Loading…
Reference in New Issue