Compare commits
No commits in common. "5652efa02f063a6e44a7dff86bf56741c58498de" and "0cdeefe3709c25911aeee61e6b33c9bde1f1b9cd" have entirely different histories.
5652efa02f
...
0cdeefe370
|
@ -1,15 +0,0 @@
|
||||||
// p1nkl0bst3r data objects
|
|
||||||
|
|
||||||
class Cutoffs{
|
|
||||||
Map<String, double> tr;
|
|
||||||
Map<String, double> glicko;
|
|
||||||
|
|
||||||
Cutoffs(this.tr, this.glicko);
|
|
||||||
}
|
|
||||||
|
|
||||||
class TopTr{
|
|
||||||
String id;
|
|
||||||
double? tr;
|
|
||||||
|
|
||||||
TopTr(this.id, this.tr);
|
|
||||||
}
|
|
|
@ -265,7 +265,6 @@ class TetrioPlayer {
|
||||||
List<RecordSingle?> blitz = [];
|
List<RecordSingle?> blitz = [];
|
||||||
TetrioZen? zen;
|
TetrioZen? zen;
|
||||||
Distinguishment? distinguishment;
|
Distinguishment? distinguishment;
|
||||||
DateTime? cachedUntil;
|
|
||||||
|
|
||||||
TetrioPlayer({
|
TetrioPlayer({
|
||||||
required this.userId,
|
required this.userId,
|
||||||
|
@ -293,12 +292,11 @@ class TetrioPlayer {
|
||||||
required this.blitz,
|
required this.blitz,
|
||||||
this.zen,
|
this.zen,
|
||||||
this.distinguishment,
|
this.distinguishment,
|
||||||
this.cachedUntil
|
|
||||||
});
|
});
|
||||||
|
|
||||||
double get level => pow((xp / 500), 0.6) + (xp / (5000 + (max(0, xp - 4 * pow(10, 6)) / 5000))) + 1;
|
double get level => pow((xp / 500), 0.6) + (xp / (5000 + (max(0, xp - 4 * pow(10, 6)) / 5000))) + 1;
|
||||||
|
|
||||||
TetrioPlayer.fromJson(Map<String, dynamic> json, DateTime stateTime, String id, String nick, [DateTime? cUntil]) {
|
TetrioPlayer.fromJson(Map<String, dynamic> json, DateTime stateTime, String id, String nick) {
|
||||||
//developer.log("TetrioPlayer.fromJson $stateTime: $json", name: "data_objects/tetrio");
|
//developer.log("TetrioPlayer.fromJson $stateTime: $json", name: "data_objects/tetrio");
|
||||||
userId = id;
|
userId = id;
|
||||||
username = nick;
|
username = nick;
|
||||||
|
@ -326,7 +324,6 @@ class TetrioPlayer {
|
||||||
friendCount = json['friend_count'] ?? 0;
|
friendCount = json['friend_count'] ?? 0;
|
||||||
badstanding = json['badstanding'];
|
badstanding = json['badstanding'];
|
||||||
botmaster = json['botmaster'];
|
botmaster = json['botmaster'];
|
||||||
cachedUntil = cUntil;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
|
@ -872,21 +869,6 @@ class TetraLeagueAlphaStream{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SingleplayerStream{
|
|
||||||
late String userId;
|
|
||||||
late String type;
|
|
||||||
late List<RecordSingle> records;
|
|
||||||
|
|
||||||
SingleplayerStream({required this.userId, required this.records, required this.type});
|
|
||||||
|
|
||||||
SingleplayerStream.fromJson(List<dynamic> json, String userID, String tp) {
|
|
||||||
userId = userID;
|
|
||||||
type = tp;
|
|
||||||
records = [];
|
|
||||||
for (var value in json) {records.add(RecordSingle.fromJson(value, null));}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class TetraLeagueAlphaRecord{
|
class TetraLeagueAlphaRecord{
|
||||||
late String replayId;
|
late String replayId;
|
||||||
late String ownId;
|
late String ownId;
|
||||||
|
@ -1131,17 +1113,19 @@ class RecordSingle {
|
||||||
late String userId;
|
late String userId;
|
||||||
late String replayId;
|
late String replayId;
|
||||||
late String ownId;
|
late String ownId;
|
||||||
late DateTime timestamp;
|
late String stream;
|
||||||
late EndContextSingle endContext;
|
DateTime? timestamp;
|
||||||
|
EndContextSingle? endContext;
|
||||||
int? rank;
|
int? rank;
|
||||||
|
|
||||||
RecordSingle({required this.userId, required this.replayId, required this.ownId, required this.timestamp, required this.endContext, this.rank});
|
RecordSingle({required this.userId, required this.replayId, required this.ownId, this.timestamp, this.endContext, this.rank});
|
||||||
|
|
||||||
RecordSingle.fromJson(Map<String, dynamic> json, int? ran) {
|
RecordSingle.fromJson(Map<String, dynamic> json, int? ran) {
|
||||||
//developer.log("RecordSingle.fromJson: $json", name: "data_objects/tetrio");
|
//developer.log("RecordSingle.fromJson: $json", name: "data_objects/tetrio");
|
||||||
ownId = json['_id'];
|
ownId = json['_id'];
|
||||||
endContext = EndContextSingle.fromJson(json['endcontext']);
|
endContext = json['endcontext'] != null ? EndContextSingle.fromJson(json['endcontext']) : null;
|
||||||
replayId = json['replayid'];
|
replayId = json['replayid'];
|
||||||
|
stream = json['stream'];
|
||||||
timestamp = DateTime.parse(json['ts']);
|
timestamp = DateTime.parse(json['ts']);
|
||||||
userId = json['user']['_id'];
|
userId = json['user']['_id'];
|
||||||
rank = ran;
|
rank = ran;
|
||||||
|
@ -1150,7 +1134,9 @@ class RecordSingle {
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
final Map<String, dynamic> data = <String, dynamic>{};
|
final Map<String, dynamic> data = <String, dynamic>{};
|
||||||
data['_id'] = ownId;
|
data['_id'] = ownId;
|
||||||
data['endcontext'] = endContext.toJson();
|
if (endContext != null) {
|
||||||
|
data['endcontext'] = endContext!.toJson();
|
||||||
|
}
|
||||||
data['ismulti'] = false;
|
data['ismulti'] = false;
|
||||||
data['replayid'] = replayId;
|
data['replayid'] = replayId;
|
||||||
data['ts'] = timestamp;
|
data['ts'] = timestamp;
|
||||||
|
@ -1178,15 +1164,6 @@ class TetrioZen {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class UserRecords{
|
|
||||||
String id;
|
|
||||||
RecordSingle? sprint;
|
|
||||||
RecordSingle? blitz;
|
|
||||||
TetrioZen zen;
|
|
||||||
|
|
||||||
UserRecords(this.id, this.sprint, this.blitz, this.zen);
|
|
||||||
}
|
|
||||||
|
|
||||||
class Distinguishment {
|
class Distinguishment {
|
||||||
late String type;
|
late String type;
|
||||||
String? detail;
|
String? detail;
|
||||||
|
@ -1217,26 +1194,16 @@ class Distinguishment {
|
||||||
|
|
||||||
class News {
|
class News {
|
||||||
late String id;
|
late String id;
|
||||||
late List<NewsEntry> news;
|
late String stream;
|
||||||
|
|
||||||
News(this.id, this.news);
|
|
||||||
|
|
||||||
News.fromJson(Map<String, dynamic> json, String? userID){
|
|
||||||
id = userID != null ? "user_$userID" : json['news'].first['stream'];
|
|
||||||
news = [for (var entry in json['news']) NewsEntry.fromJson(entry)];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class NewsEntry {
|
|
||||||
//late String id; do i need it?
|
|
||||||
late String type;
|
late String type;
|
||||||
late Map<String, dynamic> data;
|
late Map<String, dynamic> data;
|
||||||
late DateTime timestamp;
|
late DateTime timestamp;
|
||||||
|
|
||||||
NewsEntry({required this.type, required this.data, required this.timestamp});
|
News({required this.type, required this.id, required this.stream, required this.data, required this.timestamp});
|
||||||
|
|
||||||
NewsEntry.fromJson(Map<String, dynamic> json){
|
News.fromJson(Map<String, dynamic> json){
|
||||||
//id = json["_id"];
|
id = json["_id"];
|
||||||
|
stream = json["stream"];
|
||||||
type = json["type"];
|
type = json["type"];
|
||||||
data = json["data"];
|
data = json["data"];
|
||||||
timestamp = DateTime.parse(json['ts']);
|
timestamp = DateTime.parse(json['ts']);
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
import 'dart:typed_data';
|
|
||||||
|
|
||||||
import 'tetrio.dart';
|
import 'tetrio.dart';
|
||||||
|
|
||||||
|
@ -58,14 +57,6 @@ class Garbage{ // charsys where???
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class RawReplay{
|
|
||||||
String id;
|
|
||||||
Uint8List asBytes;
|
|
||||||
String asString;
|
|
||||||
|
|
||||||
RawReplay(this.id, this.asBytes, this.asString);
|
|
||||||
}
|
|
||||||
|
|
||||||
class ReplayStats{
|
class ReplayStats{
|
||||||
late int seed;
|
late int seed;
|
||||||
late int linesCleared;
|
late int linesCleared;
|
||||||
|
|
|
@ -4,9 +4,9 @@
|
||||||
/// To regenerate, run: `dart run slang`
|
/// To regenerate, run: `dart run slang`
|
||||||
///
|
///
|
||||||
/// Locales: 2
|
/// Locales: 2
|
||||||
/// Strings: 1182 (591 per locale)
|
/// Strings: 1144 (572 per locale)
|
||||||
///
|
///
|
||||||
/// Built on 2024-06-16 at 21:03 UTC
|
/// Built on 2024-05-28 at 20:38 UTC
|
||||||
|
|
||||||
// coverage:ignore-file
|
// coverage:ignore-file
|
||||||
// ignore_for_file: type=lint
|
// ignore_for_file: type=lint
|
||||||
|
@ -157,11 +157,6 @@ class Translations implements BaseTranslations<AppLocale, Translations> {
|
||||||
String get history => 'History';
|
String get history => 'History';
|
||||||
String get sprint => '40 Lines';
|
String get sprint => '40 Lines';
|
||||||
String get blitz => 'Blitz';
|
String get blitz => 'Blitz';
|
||||||
String get recent => 'Recent';
|
|
||||||
String get recentRuns => 'Recent runs';
|
|
||||||
String blitzScore({required Object p}) => '${p} points';
|
|
||||||
String get openSPreplay => 'Open replay in TETR.IO';
|
|
||||||
String get downloadSPreplay => 'Download replay';
|
|
||||||
String get other => 'Other';
|
String get other => 'Other';
|
||||||
String get distinguishment => 'Distinguishment';
|
String get distinguishment => 'Distinguishment';
|
||||||
String get zen => 'Zen';
|
String get zen => 'Zen';
|
||||||
|
@ -249,28 +244,14 @@ class Translations implements BaseTranslations<AppLocale, Translations> {
|
||||||
String get yourIDAlertTitle => 'Your nickname in TETR.IO';
|
String get yourIDAlertTitle => 'Your nickname in TETR.IO';
|
||||||
String get yourIDText => 'When app loads, it will retrieve data for this account';
|
String get yourIDText => 'When app loads, it will retrieve data for this account';
|
||||||
String get language => 'Language';
|
String get language => 'Language';
|
||||||
String get updateInBackground => 'Update stats in the background';
|
|
||||||
String get updateInBackgroundDescription => 'While Tetra Stats is running, it can update stats of the current player when cache expires';
|
|
||||||
String get customization => 'Customization';
|
String get customization => 'Customization';
|
||||||
String get customizationDescription => 'Change appearance of different things in Tetra Stats UI';
|
String get customizationDescription => 'There is only one toggle, planned to add more settings';
|
||||||
String get oskKagari => 'Osk Kagari gimmick';
|
|
||||||
String get oskKagariDescription => 'If on, osk\'s rank on main view will be rendered as :kagari:';
|
|
||||||
String get AccentColor => 'Accent color';
|
|
||||||
String get AccentColorDescription => 'Almost all interactive UI elements highlighted with this color';
|
|
||||||
String get timestamps => 'Timestamps';
|
|
||||||
String get timestampsDescription => 'You can choose, in which way timestamps shows time';
|
|
||||||
String get timestampsAbsoluteGMT => 'Absolute (GMT)';
|
|
||||||
String get timestampsAbsoluteLocalTime => 'Absolute (Your timezone)';
|
|
||||||
String get timestampsRelative => 'Relative';
|
|
||||||
String get rating => 'Main representation of rating';
|
|
||||||
String get ratingDescription => 'TR is not linear, while Glicko does not have boundaries and percentile is volatile';
|
|
||||||
String get ratingLBposition => 'LB position';
|
|
||||||
String get sheetbotGraphs => 'Sheetbot-like behavior for radar graphs';
|
|
||||||
String get sheetbotGraphsDescription => 'If on, points on the graphs can appear on the opposite half of the graph if value is negative';
|
|
||||||
String get lbStats => 'Show leaderboard based stats';
|
String get lbStats => 'Show leaderboard based stats';
|
||||||
String get lbStatsDescription => 'That will impact on loading times, but will allow you to see position on LB by stats and comparison with average values';
|
String get lbStatsDescription => 'That will impact on loading times, but will allow you to see position on LB by stats and comparison with average values';
|
||||||
String get aboutApp => 'About app';
|
String get aboutApp => 'About app';
|
||||||
String aboutAppText({required Object appName, required Object packageName, required Object version, required Object buildNumber}) => '${appName} (${packageName}) Version ${version} Build ${buildNumber}\n\nDeveloped by dan63047\nFormulas provided by kerrmunism\nHistory provided by p1nkl0bst3r\nTETR.IO replay grabber API by szy';
|
String aboutAppText({required Object appName, required Object packageName, required Object version, required Object buildNumber}) => '${appName} (${packageName}) Version ${version} Build ${buildNumber}\n\nDeveloped by dan63047\nFormulas provided by kerrmunism\nHistory provided by p1nkl0bst3r\nTETR.IO replay grabber API by szy';
|
||||||
|
String get oskKagari => 'Osk Kagari gimmick';
|
||||||
|
String get oskKagariDescription => 'If on, osk\'s rank on main view will be rendered as :kagari:';
|
||||||
String stateViewTitle({required Object nickname, required Object date}) => '${nickname} account on ${date}';
|
String stateViewTitle({required Object nickname, required Object date}) => '${nickname} account on ${date}';
|
||||||
String statesViewTitle({required Object number, required Object nickname}) => '${number} states of ${nickname} account';
|
String statesViewTitle({required Object number, required Object nickname}) => '${number} states of ${nickname} account';
|
||||||
String matchesViewTitle({required Object nickname}) => '${nickname} TL matches';
|
String matchesViewTitle({required Object nickname}) => '${nickname} TL matches';
|
||||||
|
@ -852,11 +833,6 @@ class _StringsRu implements Translations {
|
||||||
@override String get history => 'История';
|
@override String get history => 'История';
|
||||||
@override String get sprint => '40 линий';
|
@override String get sprint => '40 линий';
|
||||||
@override String get blitz => 'Блиц';
|
@override String get blitz => 'Блиц';
|
||||||
@override String get recent => 'Недавно';
|
|
||||||
@override String get recentRuns => 'Недавние';
|
|
||||||
@override String blitzScore({required Object p}) => '${p} очков';
|
|
||||||
@override String get openSPreplay => 'Открыть повтор в TETR.IO';
|
|
||||||
@override String get downloadSPreplay => 'Скачать повтор';
|
|
||||||
@override String get other => 'Другое';
|
@override String get other => 'Другое';
|
||||||
@override String get distinguishment => 'Заслуга';
|
@override String get distinguishment => 'Заслуга';
|
||||||
@override String get zen => 'Дзен';
|
@override String get zen => 'Дзен';
|
||||||
|
@ -944,28 +920,14 @@ class _StringsRu implements Translations {
|
||||||
@override String get yourIDAlertTitle => 'Ваш ник в TETR.IO';
|
@override String get yourIDAlertTitle => 'Ваш ник в TETR.IO';
|
||||||
@override String get yourIDText => 'При запуске приложения оно будет получать статистику этого игрока.';
|
@override String get yourIDText => 'При запуске приложения оно будет получать статистику этого игрока.';
|
||||||
@override String get language => 'Язык (Language)';
|
@override String get language => 'Язык (Language)';
|
||||||
@override String get updateInBackground => 'Обновлять статистику в фоне';
|
|
||||||
@override String get updateInBackgroundDescription => 'Пока Tetra Stats работает, он может обновлять статистику самостоятельно когда кеш истекает';
|
|
||||||
@override String get customization => 'Кастомизация';
|
@override String get customization => 'Кастомизация';
|
||||||
@override String get customizationDescription => 'Измените внешний вид пользовательского интерфейса Tetra Stats';
|
@override String get customizationDescription => 'Здесь только один переключатель, в планах добавить больше';
|
||||||
@override String get oskKagari => '"Оск Кагари" прикол';
|
|
||||||
@override String get oskKagariDescription => 'Если включено, вместо настоящего ранга оска будет рендерится :kagari:';
|
|
||||||
@override String get AccentColor => 'Цветовой акцент';
|
|
||||||
@override String get AccentColorDescription => 'Почти все интерактивные элементы пользовательского интерфейса окрашены в этот цвет';
|
|
||||||
@override String get timestamps => 'Метки времени';
|
|
||||||
@override String get timestampsDescription => 'Вы можете выбрать, каким образом метки времени показывают время';
|
|
||||||
@override String get timestampsAbsoluteGMT => 'Абсолютные (GMT)';
|
|
||||||
@override String get timestampsAbsoluteLocalTime => 'Абсолютные (Ваш часовой пояс)';
|
|
||||||
@override String get timestampsRelative => 'Относительные';
|
|
||||||
@override String get rating => 'Основное представление рейтинга';
|
|
||||||
@override String get ratingDescription => 'TR нелинеен, тогда как Glicko не имеет границ, а положение в таблице лидеров волатильно';
|
|
||||||
@override String get ratingLBposition => 'Позиция в рейтинге';
|
|
||||||
@override String get sheetbotGraphs => 'Графики-радары как у sheetBot';
|
|
||||||
@override String get sheetbotGraphsDescription => 'Если включено, точки на графике могут появляться на противоположной стороне графика если значение отрицательное';
|
|
||||||
@override String get lbStats => 'Показывать статистику, основанную на рейтинговой таблице';
|
@override String get lbStats => 'Показывать статистику, основанную на рейтинговой таблице';
|
||||||
@override String get lbStatsDescription => 'Это повлияет на время загрузки, но позволит видеть положение в рейтинге и сравнение со средними значениями по рангу по каждой стате';
|
@override String get lbStatsDescription => 'Это повлияет на время загрузки, но позволит видеть положение в рейтинге и сравнение со средними значениями по рангу по каждой стате';
|
||||||
@override String get aboutApp => 'О приложении';
|
@override String get aboutApp => 'О приложении';
|
||||||
@override String aboutAppText({required Object appName, required Object packageName, required Object version, required Object buildNumber}) => '${appName} (${packageName}) Версия ${version} Сборка ${buildNumber}\n\nРазработал dan63047\nФормулы предоставил kerrmunism\nИсторию предоставляет p1nkl0bst3r\nВозможность скачивать повторы из TETR.IO предоставляет szy';
|
@override String aboutAppText({required Object appName, required Object packageName, required Object version, required Object buildNumber}) => '${appName} (${packageName}) Версия ${version} Сборка ${buildNumber}\n\nРазработал dan63047\nФормулы предоставил kerrmunism\nИсторию предоставляет p1nkl0bst3r\nВозможность скачивать повторы из TETR.IO предоставляет szy';
|
||||||
|
@override String get oskKagari => '"Оск Кагари" прикол';
|
||||||
|
@override String get oskKagariDescription => 'Если включено, вместо настоящего ранга оска будет рендерится :kagari:';
|
||||||
@override String stateViewTitle({required Object nickname, required Object date}) => 'Аккаунт ${nickname} ${date}';
|
@override String stateViewTitle({required Object nickname, required Object date}) => 'Аккаунт ${nickname} ${date}';
|
||||||
@override String statesViewTitle({required Object number, required Object nickname}) => '${number} состояний аккаунта ${nickname}';
|
@override String statesViewTitle({required Object number, required Object nickname}) => '${number} состояний аккаунта ${nickname}';
|
||||||
@override String matchesViewTitle({required Object nickname}) => 'Матчи аккаунта ${nickname}';
|
@override String matchesViewTitle({required Object nickname}) => 'Матчи аккаунта ${nickname}';
|
||||||
|
@ -1527,11 +1489,6 @@ extension on Translations {
|
||||||
case 'history': return 'History';
|
case 'history': return 'History';
|
||||||
case 'sprint': return '40 Lines';
|
case 'sprint': return '40 Lines';
|
||||||
case 'blitz': return 'Blitz';
|
case 'blitz': return 'Blitz';
|
||||||
case 'recent': return 'Recent';
|
|
||||||
case 'recentRuns': return 'Recent runs';
|
|
||||||
case 'blitzScore': return ({required Object p}) => '${p} points';
|
|
||||||
case 'openSPreplay': return 'Open replay in TETR.IO';
|
|
||||||
case 'downloadSPreplay': return 'Download replay';
|
|
||||||
case 'other': return 'Other';
|
case 'other': return 'Other';
|
||||||
case 'distinguishment': return 'Distinguishment';
|
case 'distinguishment': return 'Distinguishment';
|
||||||
case 'zen': return 'Zen';
|
case 'zen': return 'Zen';
|
||||||
|
@ -1631,28 +1588,14 @@ extension on Translations {
|
||||||
case 'yourIDAlertTitle': return 'Your nickname in TETR.IO';
|
case 'yourIDAlertTitle': return 'Your nickname in TETR.IO';
|
||||||
case 'yourIDText': return 'When app loads, it will retrieve data for this account';
|
case 'yourIDText': return 'When app loads, it will retrieve data for this account';
|
||||||
case 'language': return 'Language';
|
case 'language': return 'Language';
|
||||||
case 'updateInBackground': return 'Update stats in the background';
|
|
||||||
case 'updateInBackgroundDescription': return 'While Tetra Stats is running, it can update stats of the current player when cache expires';
|
|
||||||
case 'customization': return 'Customization';
|
case 'customization': return 'Customization';
|
||||||
case 'customizationDescription': return 'Change appearance of different things in Tetra Stats UI';
|
case 'customizationDescription': return 'There is only one toggle, planned to add more settings';
|
||||||
case 'oskKagari': return 'Osk Kagari gimmick';
|
|
||||||
case 'oskKagariDescription': return 'If on, osk\'s rank on main view will be rendered as :kagari:';
|
|
||||||
case 'AccentColor': return 'Accent color';
|
|
||||||
case 'AccentColorDescription': return 'Almost all interactive UI elements highlighted with this color';
|
|
||||||
case 'timestamps': return 'Timestamps';
|
|
||||||
case 'timestampsDescription': return 'You can choose, in which way timestamps shows time';
|
|
||||||
case 'timestampsAbsoluteGMT': return 'Absolute (GMT)';
|
|
||||||
case 'timestampsAbsoluteLocalTime': return 'Absolute (Your timezone)';
|
|
||||||
case 'timestampsRelative': return 'Relative';
|
|
||||||
case 'rating': return 'Main representation of rating';
|
|
||||||
case 'ratingDescription': return 'TR is not linear, while Glicko does not have boundaries and percentile is volatile';
|
|
||||||
case 'ratingLBposition': return 'LB position';
|
|
||||||
case 'sheetbotGraphs': return 'Sheetbot-like behavior for radar graphs';
|
|
||||||
case 'sheetbotGraphsDescription': return 'If on, points on the graphs can appear on the opposite half of the graph if value is negative';
|
|
||||||
case 'lbStats': return 'Show leaderboard based stats';
|
case 'lbStats': return 'Show leaderboard based stats';
|
||||||
case 'lbStatsDescription': return 'That will impact on loading times, but will allow you to see position on LB by stats and comparison with average values';
|
case 'lbStatsDescription': return 'That will impact on loading times, but will allow you to see position on LB by stats and comparison with average values';
|
||||||
case 'aboutApp': return 'About app';
|
case 'aboutApp': return 'About app';
|
||||||
case 'aboutAppText': return ({required Object appName, required Object packageName, required Object version, required Object buildNumber}) => '${appName} (${packageName}) Version ${version} Build ${buildNumber}\n\nDeveloped by dan63047\nFormulas provided by kerrmunism\nHistory provided by p1nkl0bst3r\nTETR.IO replay grabber API by szy';
|
case 'aboutAppText': return ({required Object appName, required Object packageName, required Object version, required Object buildNumber}) => '${appName} (${packageName}) Version ${version} Build ${buildNumber}\n\nDeveloped by dan63047\nFormulas provided by kerrmunism\nHistory provided by p1nkl0bst3r\nTETR.IO replay grabber API by szy';
|
||||||
|
case 'oskKagari': return 'Osk Kagari gimmick';
|
||||||
|
case 'oskKagariDescription': return 'If on, osk\'s rank on main view will be rendered as :kagari:';
|
||||||
case 'stateViewTitle': return ({required Object nickname, required Object date}) => '${nickname} account on ${date}';
|
case 'stateViewTitle': return ({required Object nickname, required Object date}) => '${nickname} account on ${date}';
|
||||||
case 'statesViewTitle': return ({required Object number, required Object nickname}) => '${number} states of ${nickname} account';
|
case 'statesViewTitle': return ({required Object number, required Object nickname}) => '${number} states of ${nickname} account';
|
||||||
case 'matchesViewTitle': return ({required Object nickname}) => '${nickname} TL matches';
|
case 'matchesViewTitle': return ({required Object nickname}) => '${nickname} TL matches';
|
||||||
|
@ -2138,11 +2081,6 @@ extension on _StringsRu {
|
||||||
case 'history': return 'История';
|
case 'history': return 'История';
|
||||||
case 'sprint': return '40 линий';
|
case 'sprint': return '40 линий';
|
||||||
case 'blitz': return 'Блиц';
|
case 'blitz': return 'Блиц';
|
||||||
case 'recent': return 'Недавно';
|
|
||||||
case 'recentRuns': return 'Недавние';
|
|
||||||
case 'blitzScore': return ({required Object p}) => '${p} очков';
|
|
||||||
case 'openSPreplay': return 'Открыть повтор в TETR.IO';
|
|
||||||
case 'downloadSPreplay': return 'Скачать повтор';
|
|
||||||
case 'other': return 'Другое';
|
case 'other': return 'Другое';
|
||||||
case 'distinguishment': return 'Заслуга';
|
case 'distinguishment': return 'Заслуга';
|
||||||
case 'zen': return 'Дзен';
|
case 'zen': return 'Дзен';
|
||||||
|
@ -2242,28 +2180,14 @@ extension on _StringsRu {
|
||||||
case 'yourIDAlertTitle': return 'Ваш ник в TETR.IO';
|
case 'yourIDAlertTitle': return 'Ваш ник в TETR.IO';
|
||||||
case 'yourIDText': return 'При запуске приложения оно будет получать статистику этого игрока.';
|
case 'yourIDText': return 'При запуске приложения оно будет получать статистику этого игрока.';
|
||||||
case 'language': return 'Язык (Language)';
|
case 'language': return 'Язык (Language)';
|
||||||
case 'updateInBackground': return 'Обновлять статистику в фоне';
|
|
||||||
case 'updateInBackgroundDescription': return 'Пока Tetra Stats работает, он может обновлять статистику самостоятельно когда кеш истекает';
|
|
||||||
case 'customization': return 'Кастомизация';
|
case 'customization': return 'Кастомизация';
|
||||||
case 'customizationDescription': return 'Измените внешний вид пользовательского интерфейса Tetra Stats';
|
case 'customizationDescription': return 'Здесь только один переключатель, в планах добавить больше';
|
||||||
case 'oskKagari': return '"Оск Кагари" прикол';
|
|
||||||
case 'oskKagariDescription': return 'Если включено, вместо настоящего ранга оска будет рендерится :kagari:';
|
|
||||||
case 'AccentColor': return 'Цветовой акцент';
|
|
||||||
case 'AccentColorDescription': return 'Почти все интерактивные элементы пользовательского интерфейса окрашены в этот цвет';
|
|
||||||
case 'timestamps': return 'Метки времени';
|
|
||||||
case 'timestampsDescription': return 'Вы можете выбрать, каким образом метки времени показывают время';
|
|
||||||
case 'timestampsAbsoluteGMT': return 'Абсолютные (GMT)';
|
|
||||||
case 'timestampsAbsoluteLocalTime': return 'Абсолютные (Ваш часовой пояс)';
|
|
||||||
case 'timestampsRelative': return 'Относительные';
|
|
||||||
case 'rating': return 'Основное представление рейтинга';
|
|
||||||
case 'ratingDescription': return 'TR нелинеен, тогда как Glicko не имеет границ, а положение в таблице лидеров волатильно';
|
|
||||||
case 'ratingLBposition': return 'Позиция в рейтинге';
|
|
||||||
case 'sheetbotGraphs': return 'Графики-радары как у sheetBot';
|
|
||||||
case 'sheetbotGraphsDescription': return 'Если включено, точки на графике могут появляться на противоположной стороне графика если значение отрицательное';
|
|
||||||
case 'lbStats': return 'Показывать статистику, основанную на рейтинговой таблице';
|
case 'lbStats': return 'Показывать статистику, основанную на рейтинговой таблице';
|
||||||
case 'lbStatsDescription': return 'Это повлияет на время загрузки, но позволит видеть положение в рейтинге и сравнение со средними значениями по рангу по каждой стате';
|
case 'lbStatsDescription': return 'Это повлияет на время загрузки, но позволит видеть положение в рейтинге и сравнение со средними значениями по рангу по каждой стате';
|
||||||
case 'aboutApp': return 'О приложении';
|
case 'aboutApp': return 'О приложении';
|
||||||
case 'aboutAppText': return ({required Object appName, required Object packageName, required Object version, required Object buildNumber}) => '${appName} (${packageName}) Версия ${version} Сборка ${buildNumber}\n\nРазработал dan63047\nФормулы предоставил kerrmunism\nИсторию предоставляет p1nkl0bst3r\nВозможность скачивать повторы из TETR.IO предоставляет szy';
|
case 'aboutAppText': return ({required Object appName, required Object packageName, required Object version, required Object buildNumber}) => '${appName} (${packageName}) Версия ${version} Сборка ${buildNumber}\n\nРазработал dan63047\nФормулы предоставил kerrmunism\nИсторию предоставляет p1nkl0bst3r\nВозможность скачивать повторы из TETR.IO предоставляет szy';
|
||||||
|
case 'oskKagari': return '"Оск Кагари" прикол';
|
||||||
|
case 'oskKagariDescription': return 'Если включено, вместо настоящего ранга оска будет рендерится :kagari:';
|
||||||
case 'stateViewTitle': return ({required Object nickname, required Object date}) => 'Аккаунт ${nickname} ${date}';
|
case 'stateViewTitle': return ({required Object nickname, required Object date}) => 'Аккаунт ${nickname} ${date}';
|
||||||
case 'statesViewTitle': return ({required Object number, required Object nickname}) => '${number} состояний аккаунта ${nickname}';
|
case 'statesViewTitle': return ({required Object number, required Object nickname}) => '${number} состояний аккаунта ${nickname}';
|
||||||
case 'matchesViewTitle': return ({required Object nickname}) => 'Матчи аккаунта ${nickname}';
|
case 'matchesViewTitle': return ({required Object nickname}) => 'Матчи аккаунта ${nickname}';
|
||||||
|
|
103
lib/main.dart
103
lib/main.dart
|
@ -1,16 +1,10 @@
|
||||||
import 'dart:async';
|
|
||||||
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';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'dart:developer' as developer;
|
|
||||||
import 'package:package_info_plus/package_info_plus.dart';
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:tetra_stats/services/tetrio_crud.dart';
|
|
||||||
import 'package:tetra_stats/views/customization_view.dart';
|
import 'package:tetra_stats/views/customization_view.dart';
|
||||||
import 'package:tetra_stats/views/ranks_averages_view.dart';
|
|
||||||
import 'package:tetra_stats/views/sprint_and_blitz_averages.dart';
|
|
||||||
import 'package:tetra_stats/views/tl_leaderboard_view.dart';
|
|
||||||
import 'package:window_manager/window_manager.dart';
|
import 'package:window_manager/window_manager.dart';
|
||||||
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
||||||
import 'package:sqflite_common_ffi_web/sqflite_ffi_web.dart';
|
import 'package:sqflite_common_ffi_web/sqflite_ffi_web.dart';
|
||||||
|
@ -24,40 +18,11 @@ import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
late final PackageInfo packageInfo;
|
late final PackageInfo packageInfo;
|
||||||
late SharedPreferences prefs;
|
late SharedPreferences prefs;
|
||||||
late TetrioService teto;
|
ColorScheme sheme = const ColorScheme.dark(primary: Colors.cyanAccent, secondary: Colors.white);
|
||||||
ThemeData theme = ThemeData(fontFamily: 'Eurostile Round', colorScheme: const ColorScheme.dark(primary: Colors.cyanAccent, secondary: Colors.white), scaffoldBackgroundColor: Colors.black);
|
|
||||||
|
|
||||||
// Future<dynamic> computeIsolate(Future Function() function) async {
|
void setAccentColor(Color color){ // does this thing work??? yes??? no???
|
||||||
// final receivePort = ReceivePort();
|
sheme = ColorScheme.dark(primary: color, secondary: Colors.white);
|
||||||
// var rootToken = RootIsolateToken.instance!;
|
}
|
||||||
// await Isolate.spawn<_IsolateData>(
|
|
||||||
// _isolateEntry,
|
|
||||||
// _IsolateData(
|
|
||||||
// token: rootToken,
|
|
||||||
// function: function,
|
|
||||||
// answerPort: receivePort.sendPort,
|
|
||||||
// ),
|
|
||||||
// );
|
|
||||||
// return await receivePort.first;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// void _isolateEntry(_IsolateData isolateData) async {
|
|
||||||
// BackgroundIsolateBinaryMessenger.ensureInitialized(isolateData.token);
|
|
||||||
// final answer = await isolateData.function();
|
|
||||||
// isolateData.answerPort.send(answer);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// class _IsolateData {
|
|
||||||
// final RootIsolateToken token;
|
|
||||||
// final Function function;
|
|
||||||
// final SendPort answerPort;
|
|
||||||
|
|
||||||
// _IsolateData({
|
|
||||||
// required this.token,
|
|
||||||
// required this.function,
|
|
||||||
// required this.answerPort,
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
final router = GoRouter(
|
final router = GoRouter(
|
||||||
initialLocation: "/",
|
initialLocation: "/",
|
||||||
|
@ -69,26 +34,6 @@ final router = GoRouter(
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: 'settings',
|
path: 'settings',
|
||||||
builder: (_, __) => const SettingsView(),
|
builder: (_, __) => const SettingsView(),
|
||||||
routes: [
|
|
||||||
GoRoute(
|
|
||||||
path: 'customization',
|
|
||||||
builder: (_, __) => const CustomizationView(),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
),
|
|
||||||
GoRoute(
|
|
||||||
path: "leaderboard",
|
|
||||||
builder: (_, __) => const TLLeaderboardView(),
|
|
||||||
routes: [
|
|
||||||
GoRoute(
|
|
||||||
path: "LBvalues",
|
|
||||||
builder: (_, __) => const RankAveragesView(),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
),
|
|
||||||
GoRoute(
|
|
||||||
path: "LBvalues",
|
|
||||||
builder: (_, __) => const RankAveragesView(),
|
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: 'states',
|
path: 'states',
|
||||||
|
@ -99,9 +44,9 @@ final router = GoRouter(
|
||||||
builder: (_, __) => const CalcView(),
|
builder: (_, __) => const CalcView(),
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: 'sprintAndBlitzAverages',
|
path: 'customization',
|
||||||
builder: (_, __) => const SprintAndBlitzView(),
|
builder: (_, __) => const CustomizationView(),
|
||||||
)
|
),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
GoRoute( // that one intended for Android users, that can open https://ch.tetr.io/u/ links
|
GoRoute( // that one intended for Android users, that can open https://ch.tetr.io/u/ links
|
||||||
|
@ -132,7 +77,6 @@ void main() async {
|
||||||
|
|
||||||
packageInfo = await PackageInfo.fromPlatform();
|
packageInfo = await PackageInfo.fromPlatform();
|
||||||
prefs = await SharedPreferences.getInstance();
|
prefs = await SharedPreferences.getInstance();
|
||||||
teto = TetrioService();
|
|
||||||
|
|
||||||
// Choosing the locale
|
// Choosing the locale
|
||||||
String? locale = prefs.getString("locale");
|
String? locale = prefs.getString("locale");
|
||||||
|
@ -142,39 +86,14 @@ void main() async {
|
||||||
LocaleSettings.setLocaleRaw(locale);
|
LocaleSettings.setLocaleRaw(locale);
|
||||||
}
|
}
|
||||||
|
|
||||||
// I dont want to store old cache
|
|
||||||
Timer.periodic(const Duration(minutes: 5), (Timer timer) {
|
|
||||||
teto.cacheRoutine();
|
|
||||||
developer.log("Cache routine complete, next one in ${DateTime.now().add(const Duration(minutes: 5))}", name: "main");
|
|
||||||
// if (prefs.getBool("updateInBG") == true) teto.fetchTracked(); // TODO: Somehow avoid doing that in main isolate
|
|
||||||
});
|
|
||||||
|
|
||||||
runApp(TranslationProvider(
|
runApp(TranslationProvider(
|
||||||
child: const MyApp(),
|
child: const MyApp(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
class MyApp extends StatefulWidget {
|
class MyApp extends StatelessWidget {
|
||||||
const MyApp({super.key});
|
const MyApp({super.key});
|
||||||
|
|
||||||
@override
|
|
||||||
State<MyApp> createState() => MyAppState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class MyAppState extends State<MyApp> {
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
setAccentColor(prefs.getInt("accentColor") != null ? Color(prefs.getInt("accentColor")!) : Colors.cyanAccent);
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
void setAccentColor(Color color){ // does this thing work??? yes??? no???
|
|
||||||
setState(() {
|
|
||||||
theme = theme.copyWith(colorScheme: theme.colorScheme.copyWith(primary: color));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MaterialApp.router(
|
return MaterialApp.router(
|
||||||
|
@ -186,7 +105,11 @@ class MyAppState extends State<MyApp> {
|
||||||
locale: TranslationProvider.of(context).flutterLocale,
|
locale: TranslationProvider.of(context).flutterLocale,
|
||||||
supportedLocales: AppLocaleUtils.supportedLocales,
|
supportedLocales: AppLocaleUtils.supportedLocales,
|
||||||
localizationsDelegates: GlobalMaterialLocalizations.delegates,
|
localizationsDelegates: GlobalMaterialLocalizations.delegates,
|
||||||
theme: theme
|
theme: ThemeData(
|
||||||
|
fontFamily: 'Eurostile Round',
|
||||||
|
colorScheme: sheme,
|
||||||
|
scaffoldBackgroundColor: Colors.black
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,8 @@
|
||||||
// ignore_for_file: type_literal_in_constant_pattern
|
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:developer' as developer;
|
import 'dart:developer' as developer;
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:tetra_stats/data_objects/tetra_stats.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/main.dart' show packageInfo;
|
import 'package:tetra_stats/main.dart' show packageInfo;
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
@ -69,79 +65,22 @@ const String createTetrioTLReplayStats = '''
|
||||||
)
|
)
|
||||||
''';
|
''';
|
||||||
|
|
||||||
class CacheController {
|
|
||||||
late Map<String, dynamic> _cache;
|
|
||||||
late Map<String, String> _nicknames;
|
|
||||||
|
|
||||||
CacheController.init(){
|
|
||||||
_cache = {};
|
|
||||||
_nicknames = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
String _getObjectId(dynamic object){
|
|
||||||
switch (object.runtimeType){
|
|
||||||
case TetrioPlayer:
|
|
||||||
object as TetrioPlayer;
|
|
||||||
_nicknames[object.username] = object.userId;
|
|
||||||
return object.userId;
|
|
||||||
case TetrioPlayersLeaderboard:
|
|
||||||
return object.runtimeType.toString()+object.type;
|
|
||||||
case Cutoffs:
|
|
||||||
return object.runtimeType.toString();
|
|
||||||
case TetrioPlayerFromLeaderboard: // i may be a little stupid
|
|
||||||
return "${object.runtimeType}topone";
|
|
||||||
case TetraLeagueAlphaStream:
|
|
||||||
return object.runtimeType.toString()+object.userId;
|
|
||||||
case SingleplayerStream:
|
|
||||||
return object.type+object.userId;
|
|
||||||
default:
|
|
||||||
return object.runtimeType.toString()+object.id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void store(dynamic object, int? cachedUntil) async {
|
|
||||||
String key = _getObjectId(object) + cachedUntil!.toString();
|
|
||||||
_cache[key] = object;
|
|
||||||
}
|
|
||||||
|
|
||||||
dynamic get(String id, Type datatype){
|
|
||||||
if (_cache.isEmpty) return null;
|
|
||||||
MapEntry<String, dynamic>? objectEntry;
|
|
||||||
try{
|
|
||||||
switch (datatype){
|
|
||||||
case TetrioPlayer:
|
|
||||||
objectEntry = id.length <= 16 ? _cache.entries.firstWhere((element) => element.key.startsWith(_nicknames[id]??"huh?")) : _cache.entries.firstWhere((element) => element.key.startsWith(id));
|
|
||||||
if (id.length <= 16) id = _nicknames[id]??"huh?";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
objectEntry = _cache.entries.firstWhere((element) => element.key.startsWith(datatype.toString()+id));
|
|
||||||
id = datatype.toString()+id;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} on StateError{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (int.parse(objectEntry.key.substring(id.length)) <= DateTime.now().millisecondsSinceEpoch){
|
|
||||||
_cache.remove(objectEntry.key);
|
|
||||||
return null;
|
|
||||||
}else{
|
|
||||||
return objectEntry.value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void removeOld() async {
|
|
||||||
_cache.removeWhere((key, value) => int.parse(key.substring(_getObjectId(value).length)) <= DateTime.now().millisecondsSinceEpoch);
|
|
||||||
}
|
|
||||||
|
|
||||||
void reset(){
|
|
||||||
_cache.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class TetrioService extends DB {
|
class TetrioService extends DB {
|
||||||
final Map<String, String> _players = {};
|
final Map<String, String> _players = {};
|
||||||
final _cache = CacheController.init(); // I'm trying to send as less requests, as possible, so i'm caching the results of those requests.
|
|
||||||
final Map<String, PlayerLeaderboardPosition> _lbPositions = {}; // separate one because attached to the leaderboard
|
// 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]}
|
||||||
|
final Map<String, TetrioPlayersLeaderboard> _leaderboardsCache = {};
|
||||||
|
final Map<String, PlayerLeaderboardPosition> _lbPositions = {};
|
||||||
|
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
|
/// 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());
|
||||||
/// We should have only one instanse of this service
|
/// We should have only one instanse of this service
|
||||||
|
@ -215,27 +154,17 @@ class TetrioService extends DB {
|
||||||
return _lbPositions[userID];
|
return _lbPositions[userID];
|
||||||
}
|
}
|
||||||
|
|
||||||
void cacheRoutine(){
|
|
||||||
_cache.removeOld();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Downloads replay from inoue (szy API). Requiers [replayID]. If request have
|
/// Downloads replay from inoue (szy API). Requiers [replayID]. If request have
|
||||||
/// different from 200 statusCode, it will throw an excepction. Returns list, that contains same replay
|
/// different from 200 statusCode, it will throw an excepction. Returns list, that contains same replay
|
||||||
/// as string and as binary.
|
/// as string and as binary.
|
||||||
Future<RawReplay> szyGetReplay(String replayID) async {
|
Future<List<dynamic>> szyGetReplay(String replayID) async {
|
||||||
// Trying to get it from cache first
|
try{ // read from cache
|
||||||
RawReplay? cached = _cache.get(replayID, RawReplay);
|
var cached = _replaysCache.entries.firstWhere((element) => element.key == replayID);
|
||||||
if (cached != null) return cached;
|
return cached.value;
|
||||||
|
}catch (e){
|
||||||
// If failed, trying to obtain replay from download directory
|
// actually going to obtain
|
||||||
if (!kIsWeb){ // can't obtain download directory on web
|
|
||||||
var downloadPath = await getDownloadsDirectory();
|
|
||||||
downloadPath ??= Platform.isAndroid ? Directory("/storage/emulated/0/Download") : await getApplicationDocumentsDirectory();
|
|
||||||
var replayFile = File("${downloadPath.path}/$replayID.ttrm");
|
|
||||||
if (replayFile.existsSync()) return RawReplay(replayID, replayFile.readAsBytesSync(), replayFile.readAsStringSync());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If failed, actually trying to retrieve
|
|
||||||
Uri url;
|
Uri url;
|
||||||
if (kIsWeb) { // Web version sends every request through my php script at the same domain, where Tetra Stats located because of CORS
|
if (kIsWeb) { // Web version sends every request through my php script at the same domain, where Tetra Stats located because of CORS
|
||||||
url = Uri.https('ts.dan63.by', 'oskware_bridge.php', {"endpoint": "tetrioReplay", "replayid": replayID});
|
url = Uri.https('ts.dan63.by', 'oskware_bridge.php', {"endpoint": "tetrioReplay", "replayid": replayID});
|
||||||
|
@ -243,16 +172,22 @@ class TetrioService extends DB {
|
||||||
url = Uri.https('inoue.szy.lol', '/api/replay/$replayID');
|
url = Uri.https('inoue.szy.lol', '/api/replay/$replayID');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Trying to obtain replay from download directory first
|
||||||
|
if (!kIsWeb){ // can't obtain download directory on web
|
||||||
|
var downloadPath = await getDownloadsDirectory();
|
||||||
|
downloadPath ??= Platform.isAndroid ? Directory("/storage/emulated/0/Download") : await getApplicationDocumentsDirectory();
|
||||||
|
var replayFile = File("${downloadPath.path}/$replayID.ttrm");
|
||||||
|
if (replayFile.existsSync()) return [replayFile.readAsStringSync(), replayFile.readAsBytesSync()];
|
||||||
|
}
|
||||||
|
|
||||||
try{
|
try{
|
||||||
final response = await client.get(url);
|
final response = await client.get(url);
|
||||||
|
|
||||||
switch (response.statusCode) {
|
switch (response.statusCode) {
|
||||||
case 200:
|
case 200:
|
||||||
developer.log("szyDownload: Replay $replayID downloaded", name: "services/tetrio_crud");
|
developer.log("szyDownload: Replay downloaded", name: "services/tetrio_crud", error: response.statusCode);
|
||||||
RawReplay replay = RawReplay(replayID, response.bodyBytes, response.body);
|
_replaysCache[replayID] = [response.body, response.bodyBytes]; // Puts results into the cache
|
||||||
DateTime now = DateTime.now();
|
return [response.body, response.bodyBytes];
|
||||||
_cache.store(replay, now.millisecondsSinceEpoch + 3600000);
|
|
||||||
return replay;
|
|
||||||
// if not 200 - throw a unique for each code exception
|
// if not 200 - throw a unique for each code exception
|
||||||
case 404:
|
case 404:
|
||||||
throw SzyNotFound();
|
throw SzyNotFound();
|
||||||
|
@ -268,7 +203,7 @@ class TetrioService extends DB {
|
||||||
case 504:
|
case 504:
|
||||||
throw SzyInternalProblem();
|
throw SzyInternalProblem();
|
||||||
default:
|
default:
|
||||||
developer.log("szyDownload: Failed to download a replay $replayID", name: "services/tetrio_crud", error: response.statusCode);
|
developer.log("szyDownload: Failed to download a replay", name: "services/tetrio_crud", error: response.statusCode);
|
||||||
throw ConnectionIssue(response.statusCode, response.reasonPhrase??"No reason");
|
throw ConnectionIssue(response.statusCode, response.reasonPhrase??"No reason");
|
||||||
}
|
}
|
||||||
} on http.ClientException catch (e, s) { // If local http client fails
|
} on http.ClientException catch (e, s) { // If local http client fails
|
||||||
|
@ -284,8 +219,8 @@ class TetrioService extends DB {
|
||||||
downloadPath ??= Platform.isAndroid ? Directory("/storage/emulated/0/Download") : await getApplicationDocumentsDirectory();
|
downloadPath ??= Platform.isAndroid ? Directory("/storage/emulated/0/Download") : await getApplicationDocumentsDirectory();
|
||||||
var replayFile = File("${downloadPath.path}/$replayID.ttrm");
|
var replayFile = File("${downloadPath.path}/$replayID.ttrm");
|
||||||
if (replayFile.existsSync()) throw TetrioReplayAlreadyExist();
|
if (replayFile.existsSync()) throw TetrioReplayAlreadyExist();
|
||||||
RawReplay replay = await szyGetReplay(replayID);
|
var replay = await szyGetReplay(replayID);
|
||||||
await replayFile.writeAsBytes(replay.asBytes);
|
await replayFile.writeAsBytes(replay[1]);
|
||||||
return replayFile.path;
|
return replayFile.path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -300,66 +235,28 @@ class TetrioService extends DB {
|
||||||
if (!isAvailable) throw ReplayNotAvalable(); // if replay too old
|
if (!isAvailable) throw ReplayNotAvalable(); // if replay too old
|
||||||
|
|
||||||
// otherwise, actually going to download a replay and analyze it
|
// otherwise, actually going to download a replay and analyze it
|
||||||
String replay = (await szyGetReplay(replayID)).asString;
|
String replay = (await szyGetReplay(replayID))[0];
|
||||||
Map<String, dynamic> toAnalyze = jsonDecode(replay);
|
Map<String, dynamic> toAnalyze = jsonDecode(replay);
|
||||||
ReplayData data = ReplayData.fromJson(toAnalyze);
|
ReplayData data = ReplayData.fromJson(toAnalyze);
|
||||||
saveReplayStats(data); // saving to DB for later
|
saveReplayStats(data); // saving to DB for later
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieves avaliable Tetra League matches from Tetra Channel api. Returns stream object (fake stream).
|
|
||||||
/// Throws an exception if fails to retrieve.
|
|
||||||
Future<SingleplayerStream> fetchSingleplayerStream(String userID, String stream) async {
|
|
||||||
SingleplayerStream? cached = _cache.get(userID, SingleplayerStream);
|
|
||||||
if (cached != null) return cached;
|
|
||||||
|
|
||||||
Uri url;
|
|
||||||
if (kIsWeb) {
|
|
||||||
url = Uri.https('ts.dan63.by', 'oskware_bridge.php', {"endpoint": "singleplayerStream", "user": userID.toLowerCase().trim(), "stream": stream});
|
|
||||||
} else {
|
|
||||||
url = Uri.https('ch.tetr.io', 'api/streams/${stream}_${userID.toLowerCase().trim()}');
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
final response = await client.get(url);
|
|
||||||
|
|
||||||
switch (response.statusCode) {
|
|
||||||
case 200:
|
|
||||||
if (jsonDecode(response.body)['success']) {
|
|
||||||
SingleplayerStream records = SingleplayerStream.fromJson(jsonDecode(response.body)['data']['records'], userID, stream);
|
|
||||||
_cache.store(records, jsonDecode(response.body)['cache']['cached_until']);
|
|
||||||
developer.log("fetchSingleplayerStream: $stream $userID stream retrieved and cached", name: "services/tetrio_crud");
|
|
||||||
return records;
|
|
||||||
} else {
|
|
||||||
developer.log("fetchSingleplayerStream: User dosen't exist", name: "services/tetrio_crud", error: response.body);
|
|
||||||
throw TetrioPlayerNotExist();
|
|
||||||
}
|
|
||||||
case 403:
|
|
||||||
throw TetrioForbidden();
|
|
||||||
case 429:
|
|
||||||
throw TetrioTooManyRequests();
|
|
||||||
case 418:
|
|
||||||
throw TetrioOskwareBridgeProblem();
|
|
||||||
case 500:
|
|
||||||
case 502:
|
|
||||||
case 503:
|
|
||||||
case 504:
|
|
||||||
throw TetrioInternalProblem();
|
|
||||||
default:
|
|
||||||
developer.log("fetchSingleplayerStream: Failed to fetch stream $stream $userID", name: "services/tetrio_crud", error: response.statusCode);
|
|
||||||
throw ConnectionIssue(response.statusCode, response.reasonPhrase??"No reason");
|
|
||||||
}
|
|
||||||
} on http.ClientException catch (e, s) {
|
|
||||||
developer.log("$e, $s");
|
|
||||||
throw http.ClientException(e.message, e.uri);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets and returns Top TR for a player with given [id]. May return null if player top tr is unknown
|
/// Gets and returns Top TR for a player with given [id]. May return null if player top tr is unknown
|
||||||
/// or api is unavaliable (404). May throw an exception, if something else happens.
|
/// or api is unavaliable (404). May throw an exception, if something else happens.
|
||||||
Future<TopTr?> fetchTopTR(String id) async {
|
Future<double?> fetchTopTR(String id) async {
|
||||||
// Trying to get it from cache first
|
try{ // read from cache
|
||||||
TopTr? cached = _cache.get(id, TopTr);
|
var cached = _topTRcache.entries.firstWhere((element) => element.value.keys.first == id);
|
||||||
if (cached != null) return cached;
|
if (DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true).isAfter(DateTime.now())){ // if not expired
|
||||||
|
developer.log("fetchTopTR: Top TR retrieved from cache, that expires ${DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true)}", name: "services/tetrio_crud");
|
||||||
|
return cached.value.values.first;
|
||||||
|
}else{ // if cache expired
|
||||||
|
_topTRcache.remove(cached.key);
|
||||||
|
developer.log("fetchTopTR: Top TR expired (${DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true)})", name: "services/tetrio_crud");
|
||||||
|
}
|
||||||
|
}catch(e){ // actually going to obtain
|
||||||
|
developer.log("fetchTopTR: Trying to retrieve Top TR", name: "services/tetrio_crud");
|
||||||
|
}
|
||||||
|
|
||||||
Uri url;
|
Uri url;
|
||||||
if (kIsWeb) { // Web version sends every request through my php script at the same domain, where Tetra Stats located because of CORS
|
if (kIsWeb) { // Web version sends every request through my php script at the same domain, where Tetra Stats located because of CORS
|
||||||
|
@ -372,15 +269,12 @@ class TetrioService extends DB {
|
||||||
|
|
||||||
switch (response.statusCode) {
|
switch (response.statusCode) {
|
||||||
case 200: // ok - return the value
|
case 200: // ok - return the value
|
||||||
TopTr result = TopTr(id, double.tryParse(response.body));
|
_topTRcache[(DateTime.now().millisecondsSinceEpoch + 300000).toString()] = {id: double.tryParse(response.body)};
|
||||||
_cache.store(result, DateTime.now().millisecondsSinceEpoch + 300000);
|
return double.tryParse(response.body);
|
||||||
return result;
|
|
||||||
case 404: // not found - return null
|
case 404: // not found - return null
|
||||||
TopTr result = TopTr(id, null);
|
|
||||||
developer.log("fetchTopTR: Probably, player doesn't have top TR", name: "services/tetrio_crud", error: response.statusCode);
|
developer.log("fetchTopTR: Probably, player doesn't have top TR", name: "services/tetrio_crud", error: response.statusCode);
|
||||||
_cache.store(result, DateTime.now().millisecondsSinceEpoch + 300000);
|
_topTRcache[(DateTime.now().millisecondsSinceEpoch + 300000).toString()] = {id: null};
|
||||||
//_topTRcache[(DateTime.now().millisecondsSinceEpoch + 300000).toString()] = {id: null};
|
return null;
|
||||||
return result;
|
|
||||||
// if not 200 or 404 - throw a unique for each code exception
|
// if not 200 or 404 - throw a unique for each code exception
|
||||||
case 403:
|
case 403:
|
||||||
throw P1nkl0bst3rForbidden();
|
throw P1nkl0bst3rForbidden();
|
||||||
|
@ -406,9 +300,19 @@ class TetrioService extends DB {
|
||||||
// Sidenote: as you can see, fetch functions looks and works pretty much same way, as described above,
|
// Sidenote: as you can see, fetch functions looks and works pretty much same way, as described above,
|
||||||
// so i'm going to document only unique differences between them
|
// so i'm going to document only unique differences between them
|
||||||
|
|
||||||
Future<Cutoffs?> fetchCutoffs() async {
|
Future<List<Map<String, double>>> fetchCutoffs() async {
|
||||||
Cutoffs? cached = _cache.get("", Cutoffs);
|
try{
|
||||||
if (cached != null) return cached;
|
var cached = _cutoffsCache.entries.first;
|
||||||
|
if (DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true).isAfter(DateTime.now())){ // if not expired
|
||||||
|
developer.log("fetchCutoffs: Cutoffs 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("fetchCutoffs: Cutoffs expired (${DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true)})", name: "services/tetrio_crud");
|
||||||
|
}
|
||||||
|
}catch(e){ // actually going to obtain
|
||||||
|
developer.log("fetchCutoffs: Trying to retrieve Cutoffs", name: "services/tetrio_crud");
|
||||||
|
}
|
||||||
|
|
||||||
Uri url;
|
Uri url;
|
||||||
if (kIsWeb) {
|
if (kIsWeb) {
|
||||||
|
@ -424,16 +328,17 @@ class TetrioService extends DB {
|
||||||
case 200:
|
case 200:
|
||||||
Map<String, dynamic> rawData = jsonDecode(response.body);
|
Map<String, dynamic> rawData = jsonDecode(response.body);
|
||||||
Map<String, dynamic> data = rawData["cutoffs"] as Map<String, dynamic>;
|
Map<String, dynamic> data = rawData["cutoffs"] as Map<String, dynamic>;
|
||||||
Cutoffs result = Cutoffs({}, {});
|
Map<String, double> trCutoffs = {};
|
||||||
|
Map<String, double> glickoCutoffs = {};
|
||||||
for (String rank in data.keys){
|
for (String rank in data.keys){
|
||||||
result.tr[rank] = data[rank]["rating"];
|
trCutoffs[rank] = data[rank]["rating"];
|
||||||
result.glicko[rank] = data[rank]["glicko"];
|
glickoCutoffs[rank] = data[rank]["glicko"];
|
||||||
}
|
}
|
||||||
_cache.store(result, rawData["ts"] + 300000);
|
_cutoffsCache[(rawData["ts"] + 300000).toString()] = [trCutoffs, glickoCutoffs];
|
||||||
return result;
|
return [trCutoffs, glickoCutoffs];
|
||||||
case 404:
|
case 404:
|
||||||
developer.log("fetchCutoffs: Cutoffs are gone", name: "services/tetrio_crud", error: response.statusCode);
|
developer.log("fetchCutoffs: Cutoffs are gone", name: "services/tetrio_crud", error: response.statusCode);
|
||||||
return null;
|
return [];
|
||||||
// if not 200 or 404 - throw a unique for each code exception
|
// if not 200 or 404 - throw a unique for each code exception
|
||||||
case 403:
|
case 403:
|
||||||
throw P1nkl0bst3rForbidden();
|
throw P1nkl0bst3rForbidden();
|
||||||
|
@ -457,8 +362,18 @@ class TetrioService extends DB {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<TetrioPlayerFromLeaderboard> fetchTopOneFromTheLeaderboard() async {
|
Future<TetrioPlayerFromLeaderboard> fetchTopOneFromTheLeaderboard() async {
|
||||||
TetrioPlayerFromLeaderboard? cached = _cache.get("topone", TetrioPlayerFromLeaderboard);
|
try{
|
||||||
if (cached != null) return cached;
|
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;
|
Uri url;
|
||||||
if (kIsWeb) {
|
if (kIsWeb) {
|
||||||
|
@ -473,9 +388,7 @@ class TetrioService extends DB {
|
||||||
switch (response.statusCode) {
|
switch (response.statusCode) {
|
||||||
case 200:
|
case 200:
|
||||||
var rawJson = jsonDecode(response.body);
|
var rawJson = jsonDecode(response.body);
|
||||||
TetrioPlayerFromLeaderboard result = TetrioPlayerFromLeaderboard.fromJson(rawJson["data"]["users"][0], DateTime.fromMillisecondsSinceEpoch(rawJson["cache"]["cached_at"]));
|
return TetrioPlayerFromLeaderboard.fromJson(rawJson["data"]["users"][0], DateTime.fromMillisecondsSinceEpoch(rawJson["cache"]["cached_at"]));
|
||||||
_cache.store(result, rawJson["cache"]["cached_until"]);
|
|
||||||
return result;
|
|
||||||
case 404:
|
case 404:
|
||||||
throw TetrioPlayerNotExist();
|
throw TetrioPlayerNotExist();
|
||||||
// if not 200 or 404 - throw a unique for each code exception
|
// if not 200 or 404 - throw a unique for each code exception
|
||||||
|
@ -693,9 +606,18 @@ class TetrioService extends DB {
|
||||||
|
|
||||||
/// Retrieves full Tetra League leaderboard from Tetra Channel api. Returns a leaderboard object. Throws an exception if fails to retrieve.
|
/// Retrieves full Tetra League leaderboard from Tetra Channel api. Returns a leaderboard object. Throws an exception if fails to retrieve.
|
||||||
Future<TetrioPlayersLeaderboard> fetchTLLeaderboard() async {
|
Future<TetrioPlayersLeaderboard> fetchTLLeaderboard() async {
|
||||||
TetrioPlayersLeaderboard? cached = _cache.get("league", TetrioPlayersLeaderboard);
|
try{
|
||||||
if (cached != null) return cached;
|
var cached = _leaderboardsCache.entries.firstWhere((element) => element.value.type == "league");
|
||||||
|
if (DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true).isAfter(DateTime.now())){
|
||||||
|
developer.log("fetchTLLeaderboard: Leaderboard retrieved from cache, that expires ${DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true)}", name: "services/tetrio_crud");
|
||||||
|
return cached.value;
|
||||||
|
}else{
|
||||||
|
_leaderboardsCache.remove(cached.key);
|
||||||
|
developer.log("fetchTLLeaderboard: Leaderboard expired (${DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true)})", name: "services/tetrio_crud");
|
||||||
|
}
|
||||||
|
}catch(e){
|
||||||
|
developer.log("fetchTLLeaderboard: Trying to retrieve leaderboard", name: "services/tetrio_crud");
|
||||||
|
}
|
||||||
Uri url;
|
Uri url;
|
||||||
if (kIsWeb) {
|
if (kIsWeb) {
|
||||||
url = Uri.https('ts.dan63.by', 'oskware_bridge.php', {"endpoint": "TLLeaderboard"});
|
url = Uri.https('ts.dan63.by', 'oskware_bridge.php', {"endpoint": "TLLeaderboard"});
|
||||||
|
@ -712,8 +634,7 @@ class TetrioService extends DB {
|
||||||
if (rawJson['success']) { // if api confirmed that everything ok
|
if (rawJson['success']) { // if api confirmed that everything ok
|
||||||
TetrioPlayersLeaderboard leaderboard = TetrioPlayersLeaderboard.fromJson(rawJson['data']['users'], "league", DateTime.fromMillisecondsSinceEpoch(rawJson['cache']['cached_at']));
|
TetrioPlayersLeaderboard leaderboard = TetrioPlayersLeaderboard.fromJson(rawJson['data']['users'], "league", DateTime.fromMillisecondsSinceEpoch(rawJson['cache']['cached_at']));
|
||||||
developer.log("fetchTLLeaderboard: Leaderboard retrieved and cached", name: "services/tetrio_crud");
|
developer.log("fetchTLLeaderboard: Leaderboard retrieved and cached", name: "services/tetrio_crud");
|
||||||
//_leaderboardsCache[rawJson['cache']['cached_until'].toString()] = leaderboard;
|
_leaderboardsCache[rawJson['cache']['cached_until'].toString()] = leaderboard;
|
||||||
_cache.store(leaderboard, rawJson['cache']['cached_until']);
|
|
||||||
return leaderboard;
|
return leaderboard;
|
||||||
} else { // idk how to hit that one
|
} else { // idk how to hit that one
|
||||||
developer.log("fetchTLLeaderboard: Bruh", name: "services/tetrio_crud", error: rawJson);
|
developer.log("fetchTLLeaderboard: Bruh", name: "services/tetrio_crud", error: rawJson);
|
||||||
|
@ -740,29 +661,26 @@ class TetrioService extends DB {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// i want to know progress, so i trying to figure out this thing:
|
|
||||||
// Stream<TetrioPlayersLeaderboard> fetchTLLeaderboardAsStream() async {
|
|
||||||
// TetrioPlayersLeaderboard? cached = _cache.get("league", TetrioPlayersLeaderboard);
|
|
||||||
// if (cached != null) return cached;
|
|
||||||
|
|
||||||
// Uri url;
|
|
||||||
// if (kIsWeb) {
|
|
||||||
// url = Uri.https('ts.dan63.by', 'oskware_bridge.php', {"endpoint": "TLLeaderboard"});
|
|
||||||
// } else {
|
|
||||||
// url = Uri.https('ch.tetr.io', 'api/users/lists/league/all');
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Stream<TetrioPlayersLeaderboard> stream = http.StreamedRequest("GET", url);
|
|
||||||
// }
|
|
||||||
|
|
||||||
TetrioPlayersLeaderboard? getCachedLeaderboard(){
|
TetrioPlayersLeaderboard? getCachedLeaderboard(){
|
||||||
return _cache.get("league", TetrioPlayersLeaderboard);
|
return _leaderboardsCache.entries.firstOrNull?.value;
|
||||||
|
// That function will break if i decide to recive other leaderboards
|
||||||
|
// TODO: Think about better solution
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieves and returns 100 latest news entries from Tetra Channel api for given [userID]. Throws an exception if fails to retrieve.
|
/// Retrieves and returns 100 latest news entries from Tetra Channel api for given [userID]. Throws an exception if fails to retrieve.
|
||||||
Future<News> fetchNews(String userID) async{
|
Future<List<News>> fetchNews(String userID) async{
|
||||||
News? cached = _cache.get(userID, News);
|
try{
|
||||||
if (cached != null) return cached;
|
var cached = _newsCache.entries.firstWhere((element) => element.value[0].stream == "user_$userID");
|
||||||
|
if (DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true).isAfter(DateTime.now())){
|
||||||
|
developer.log("fetchNews: News for $userID retrieved from cache, that expires ${DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true)}", name: "services/tetrio_crud");
|
||||||
|
return cached.value;
|
||||||
|
}else{
|
||||||
|
_newsCache.remove(cached.key);
|
||||||
|
developer.log("fetchNews: Cached news for $userID expired (${DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true)})", name: "services/tetrio_crud");
|
||||||
|
}
|
||||||
|
}catch(e){
|
||||||
|
developer.log("fetchNews: Trying to retrieve news for $userID", name: "services/tetrio_crud");
|
||||||
|
}
|
||||||
|
|
||||||
Uri url;
|
Uri url;
|
||||||
if (kIsWeb) {
|
if (kIsWeb) {
|
||||||
|
@ -777,8 +695,8 @@ class TetrioService extends DB {
|
||||||
case 200:
|
case 200:
|
||||||
var payload = jsonDecode(response.body);
|
var payload = jsonDecode(response.body);
|
||||||
if (payload['success']) { // if api confirmed that everything ok
|
if (payload['success']) { // if api confirmed that everything ok
|
||||||
News news = News.fromJson(payload['data'], userID);
|
List<News> news = [for (var entry in payload['data']['news']) News.fromJson(entry)];
|
||||||
_cache.store(news, payload['cache']['cached_until']);
|
_newsCache[payload['cache']['cached_until'].toString()] = news;
|
||||||
developer.log("fetchNews: $userID news retrieved and cached", name: "services/tetrio_crud");
|
developer.log("fetchNews: $userID news retrieved and cached", name: "services/tetrio_crud");
|
||||||
return news;
|
return news;
|
||||||
} else {
|
} else {
|
||||||
|
@ -809,8 +727,18 @@ class TetrioService extends DB {
|
||||||
/// Retrieves avaliable Tetra League matches from Tetra Channel api. Returns stream object (fake stream).
|
/// Retrieves avaliable Tetra League matches from Tetra Channel api. Returns stream object (fake stream).
|
||||||
/// Throws an exception if fails to retrieve.
|
/// Throws an exception if fails to retrieve.
|
||||||
Future<TetraLeagueAlphaStream> fetchTLStream(String userID) async {
|
Future<TetraLeagueAlphaStream> fetchTLStream(String userID) async {
|
||||||
TetraLeagueAlphaStream? cached = _cache.get(userID, TetraLeagueAlphaStream);
|
try{
|
||||||
if (cached != null) return cached;
|
var cached = _tlStreamsCache.entries.firstWhere((element) => element.value.userId == userID);
|
||||||
|
if (DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true).isAfter(DateTime.now())){
|
||||||
|
developer.log("fetchTLStream: Stream $userID retrieved from cache, that expires ${DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true)}", name: "services/tetrio_crud");
|
||||||
|
return cached.value;
|
||||||
|
}else{
|
||||||
|
_tlStreamsCache.remove(cached.key);
|
||||||
|
developer.log("fetchTLStream: Cached stream $userID expired (${DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true)})", name: "services/tetrio_crud");
|
||||||
|
}
|
||||||
|
}catch(e){
|
||||||
|
developer.log("fetchTLStream: Trying to retrieve stream $userID", name: "services/tetrio_crud");
|
||||||
|
}
|
||||||
|
|
||||||
Uri url;
|
Uri url;
|
||||||
if (kIsWeb) {
|
if (kIsWeb) {
|
||||||
|
@ -825,7 +753,7 @@ class TetrioService extends DB {
|
||||||
case 200:
|
case 200:
|
||||||
if (jsonDecode(response.body)['success']) {
|
if (jsonDecode(response.body)['success']) {
|
||||||
TetraLeagueAlphaStream stream = TetraLeagueAlphaStream.fromJson(jsonDecode(response.body)['data']['records'], userID);
|
TetraLeagueAlphaStream stream = TetraLeagueAlphaStream.fromJson(jsonDecode(response.body)['data']['records'], userID);
|
||||||
_cache.store(stream, jsonDecode(response.body)['cache']['cached_until']);
|
_tlStreamsCache[jsonDecode(response.body)['cache']['cached_until'].toString()] = stream;
|
||||||
developer.log("fetchTLStream: $userID stream retrieved and cached", name: "services/tetrio_crud");
|
developer.log("fetchTLStream: $userID stream retrieved and cached", name: "services/tetrio_crud");
|
||||||
return stream;
|
return stream;
|
||||||
} else {
|
} else {
|
||||||
|
@ -936,11 +864,21 @@ class TetrioService extends DB {
|
||||||
await db.delete(tetrioTLReplayStatsTable, where: '$idCol = ?', whereArgs: [rID]);
|
await db.delete(tetrioTLReplayStatsTable, where: '$idCol = ?', whereArgs: [rID]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieves Blitz, 40 Lines and Zen records for a given [userID] from Tetra Channel api. Returns `UserRecords`.
|
/// Retrieves Blitz, 40 Lines and Zen records for a given [userID] from Tetra Channel api. Returns Map, which contains user id (`user`),
|
||||||
/// Throws an exception if fails to retrieve.
|
/// Blitz (`blitz`) and 40 Lines (`sprint`) record objects and Zen object (`zen`). Throws an exception if fails to retrieve.
|
||||||
Future<UserRecords> fetchRecords(String userID) async {
|
Future<Map<String, dynamic>> fetchRecords(String userID) async {
|
||||||
UserRecords? cached = _cache.get(userID, UserRecords);
|
try{
|
||||||
if (cached != null) return cached;
|
var cached = _recordsCache.entries.firstWhere((element) => element.value['user'] == userID);
|
||||||
|
if (DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true).isAfter(DateTime.now())){
|
||||||
|
developer.log("fetchRecords: $userID records retrieved from cache, that expires ${DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true)}", name: "services/tetrio_crud");
|
||||||
|
return cached.value;
|
||||||
|
}else{
|
||||||
|
_recordsCache.remove(cached.key);
|
||||||
|
developer.log("fetchRecords: $userID records expired (${DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true)})", name: "services/tetrio_crud");
|
||||||
|
}
|
||||||
|
}catch(e){
|
||||||
|
developer.log("fetchRecords: Trying to retrieve $userID records", name: "services/tetrio_crud");
|
||||||
|
}
|
||||||
|
|
||||||
Uri url;
|
Uri url;
|
||||||
if (kIsWeb) {
|
if (kIsWeb) {
|
||||||
|
@ -962,10 +900,10 @@ class TetrioService extends DB {
|
||||||
? RecordSingle.fromJson(jsonRecords['data']['records']['blitz']['record'], jsonRecords['data']['records']['blitz']['rank'])
|
? RecordSingle.fromJson(jsonRecords['data']['records']['blitz']['record'], jsonRecords['data']['records']['blitz']['rank'])
|
||||||
: null;
|
: null;
|
||||||
var zen = TetrioZen.fromJson(jsonRecords['data']['zen']);
|
var zen = TetrioZen.fromJson(jsonRecords['data']['zen']);
|
||||||
UserRecords result = UserRecords(userID, sprint, blitz, zen);
|
Map<String, dynamic> map = {"user": userID.toLowerCase().trim(), "sprint": sprint, "blitz": blitz, "zen": zen};
|
||||||
_cache.store(result, jsonDecode(response.body)['cache']['cached_until']);
|
_recordsCache[jsonDecode(response.body)['cache']['cached_until'].toString()] = map;
|
||||||
developer.log("fetchRecords: $userID records retrieved and cached", name: "services/tetrio_crud");
|
developer.log("fetchRecords: $userID records retrieved and cached", name: "services/tetrio_crud");
|
||||||
return result;
|
return map;
|
||||||
} else {
|
} else {
|
||||||
developer.log("fetchRecords User dosen't exist", name: "services/tetrio_crud", error: response.body);
|
developer.log("fetchRecords User dosen't exist", name: "services/tetrio_crud", error: response.body);
|
||||||
throw TetrioPlayerNotExist();
|
throw TetrioPlayerNotExist();
|
||||||
|
@ -1059,7 +997,8 @@ class TetrioService extends DB {
|
||||||
}
|
}
|
||||||
|
|
||||||
// we not going to add state, that is same, as the previous
|
// we not going to add state, that is same, as the previous
|
||||||
if (!states.last.isSameState(tetrioPlayer)) states.add(tetrioPlayer);
|
bool test = states.last.isSameState(tetrioPlayer);
|
||||||
|
if (test == false) states.add(tetrioPlayer);
|
||||||
|
|
||||||
// Making map of the states
|
// Making map of the states
|
||||||
final Map<String, dynamic> statesJson = {};
|
final Map<String, dynamic> statesJson = {};
|
||||||
|
@ -1119,8 +1058,18 @@ class TetrioService extends DB {
|
||||||
/// Retrieves general stats of [user] (nickname or id) from Tetra Channel api. Returns [TetrioPlayer] object of this user.
|
/// Retrieves general stats of [user] (nickname or id) from Tetra Channel api. Returns [TetrioPlayer] object of this user.
|
||||||
/// If [isItDiscordID] is true, function expects [user] to be a discord user id. Throws an exception if fails to retrieve.
|
/// If [isItDiscordID] is true, function expects [user] to be a discord user id. Throws an exception if fails to retrieve.
|
||||||
Future<TetrioPlayer> fetchPlayer(String user, {bool isItDiscordID = false}) async {
|
Future<TetrioPlayer> fetchPlayer(String user, {bool isItDiscordID = false}) async {
|
||||||
TetrioPlayer? cached = _cache.get(user, TetrioPlayer);
|
try{
|
||||||
if (cached != null) return cached;
|
var cached = _playersCache.entries.firstWhere((element) => element.value.userId == user || element.value.username == user);
|
||||||
|
if (DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true).isAfter(DateTime.now())){
|
||||||
|
developer.log("fetchPlayer: User $user retrieved from cache, that expires ${DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true)}", name: "services/tetrio_crud");
|
||||||
|
return cached.value;
|
||||||
|
}else{
|
||||||
|
_playersCache.remove(cached.key);
|
||||||
|
developer.log("fetchPlayer: Cached user $user expired (${DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true)})", name: "services/tetrio_crud");
|
||||||
|
}
|
||||||
|
}catch(e){
|
||||||
|
developer.log("fetchPlayer: Trying to retrieve $user", name: "services/tetrio_crud");
|
||||||
|
}
|
||||||
|
|
||||||
if (isItDiscordID){
|
if (isItDiscordID){
|
||||||
// trying to find player with given discord id
|
// trying to find player with given discord id
|
||||||
|
@ -1181,8 +1130,8 @@ class TetrioService extends DB {
|
||||||
var json = jsonDecode(response.body);
|
var json = jsonDecode(response.body);
|
||||||
if (json['success']) {
|
if (json['success']) {
|
||||||
// parse and count stats
|
// parse and count stats
|
||||||
TetrioPlayer player = TetrioPlayer.fromJson(json['data']['user'], DateTime.fromMillisecondsSinceEpoch(json['cache']['cached_at'], isUtc: true), json['data']['user']['_id'], json['data']['user']['username'], DateTime.fromMillisecondsSinceEpoch(json['cache']['cached_until'], isUtc: true));
|
TetrioPlayer player = TetrioPlayer.fromJson(json['data']['user'], DateTime.fromMillisecondsSinceEpoch(json['cache']['cached_at'], isUtc: true), json['data']['user']['_id'], json['data']['user']['username']);
|
||||||
_cache.store(player, json['cache']['cached_until']);
|
_playersCache[jsonDecode(response.body)['cache']['cached_until'].toString()] = player;
|
||||||
developer.log("fetchPlayer: $user retrieved and cached", name: "services/tetrio_crud");
|
developer.log("fetchPlayer: $user retrieved and cached", name: "services/tetrio_crud");
|
||||||
return player;
|
return player;
|
||||||
} else {
|
} else {
|
||||||
|
@ -1224,15 +1173,4 @@ class TetrioService extends DB {
|
||||||
}
|
}
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> fetchTracked() async {
|
|
||||||
for (String userID in (await getAllPlayerToTrack())) {
|
|
||||||
TetrioPlayer player = await fetchPlayer(userID);
|
|
||||||
storeState(player);
|
|
||||||
sleep(Durations.extralong4);
|
|
||||||
TetraLeagueAlphaStream matches = await fetchTLStream(userID);
|
|
||||||
saveTLMatchesFromStream(matches);
|
|
||||||
sleep(Durations.extralong4);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,13 +8,5 @@ final NumberFormat f4 = NumberFormat.decimalPatternDigits(locale: LocaleSettings
|
||||||
final NumberFormat f3 = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 3);
|
final NumberFormat f3 = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 3);
|
||||||
final NumberFormat f2 = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 2);
|
final NumberFormat f2 = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 2);
|
||||||
final NumberFormat f2l = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 2)..minimumFractionDigits = 0;
|
final NumberFormat f2l = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 2)..minimumFractionDigits = 0;
|
||||||
final NumberFormat f1 = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 1);
|
|
||||||
final NumberFormat f0 = NumberFormat.decimalPattern(LocaleSettings.currentLocale.languageCode);
|
final NumberFormat f0 = NumberFormat.decimalPattern(LocaleSettings.currentLocale.languageCode);
|
||||||
final NumberFormat percentage = NumberFormat.percentPattern(LocaleSettings.currentLocale.languageCode)..maximumFractionDigits = 2;
|
final NumberFormat percentage = NumberFormat.percentPattern(LocaleSettings.currentLocale.languageCode)..maximumFractionDigits = 2;
|
||||||
|
|
||||||
/// Readable [a] - [b], without sign
|
|
||||||
String readableIntDifference(int a, int b){
|
|
||||||
int result = a - b;
|
|
||||||
|
|
||||||
return NumberFormat("#,###;#,###", LocaleSettings.currentLocale.languageCode).format(result);
|
|
||||||
}
|
|
|
@ -1,76 +0,0 @@
|
||||||
import 'package:intl/intl.dart';
|
|
||||||
import 'package:tetra_stats/gen/strings.g.dart';
|
|
||||||
import 'package:tetra_stats/utils/numers_formats.dart';
|
|
||||||
|
|
||||||
final NumberFormat secs = NumberFormat("00.###", LocaleSettings.currentLocale.languageCode);
|
|
||||||
final NumberFormat _timeInSec = NumberFormat("#,###.###s.", LocaleSettings.currentLocale.languageCode);
|
|
||||||
|
|
||||||
/// Returns string, that represents time difference between [dateTime] and now
|
|
||||||
String relativeDateTime(DateTime dateTime){
|
|
||||||
Duration difference = dateTime.difference(DateTime.now());
|
|
||||||
bool inPast = difference.isNegative;
|
|
||||||
Duration absDifference = difference.abs();
|
|
||||||
double timeInterval;
|
|
||||||
|
|
||||||
// years
|
|
||||||
timeInterval = absDifference.inSeconds / 31536000;
|
|
||||||
if (timeInterval >= 100.0) {
|
|
||||||
return inPast ? "${timeInterval.truncate()} years ago" : "in ${timeInterval.truncate()} years";
|
|
||||||
} else if (timeInterval >= 10.0) {
|
|
||||||
return inPast ? "${f1.format(timeInterval)} years ago" : "in ${f1.format(timeInterval)} years";
|
|
||||||
} else if (timeInterval >= 1.0) {
|
|
||||||
return inPast ? "${f2.format(timeInterval)} years ago" : "in ${f2.format(timeInterval)} years";
|
|
||||||
}
|
|
||||||
|
|
||||||
// months
|
|
||||||
timeInterval = absDifference.inSeconds / 2592000;
|
|
||||||
if (timeInterval >= 10.0) {
|
|
||||||
return inPast ? "${timeInterval.truncate()} months ago" : "in ${timeInterval.truncate()} months";
|
|
||||||
} else if (timeInterval >= 1.0) {
|
|
||||||
return inPast ? "${f1.format(timeInterval)} months ago" : "in ${f1.format(timeInterval)} months";
|
|
||||||
}
|
|
||||||
|
|
||||||
// days
|
|
||||||
timeInterval = absDifference.inSeconds / 86400;
|
|
||||||
if (timeInterval >= 10.0) {
|
|
||||||
return inPast ? "${timeInterval.truncate()} days ago" : "in ${timeInterval.truncate()} days";
|
|
||||||
} else if (timeInterval >= 1.0) {
|
|
||||||
return inPast ? "${f1.format(timeInterval)} days ago" : "in ${f1.format(timeInterval)} days";
|
|
||||||
}
|
|
||||||
|
|
||||||
// hours
|
|
||||||
timeInterval = absDifference.inSeconds / 3600;
|
|
||||||
if (timeInterval >= 10.0) {
|
|
||||||
return inPast ? "${timeInterval.truncate()} hours ago" : "in ${timeInterval.truncate()} hours";
|
|
||||||
} else if (timeInterval >= 1.0) {
|
|
||||||
return inPast ? "${f1.format(timeInterval)} hours ago" : "in ${f1.format(timeInterval)} hours";
|
|
||||||
}
|
|
||||||
|
|
||||||
// minutes
|
|
||||||
timeInterval = absDifference.inSeconds / 60;
|
|
||||||
if (timeInterval >= 10.0) {
|
|
||||||
return inPast ? "${timeInterval.truncate()} minutes ago" : "in ${timeInterval.truncate()} minutes";
|
|
||||||
} else if (timeInterval >= 1.0) {
|
|
||||||
return inPast ? "${f1.format(timeInterval)} minutes ago" : "in ${f1.format(timeInterval)} minutes";
|
|
||||||
}
|
|
||||||
|
|
||||||
// seconds
|
|
||||||
timeInterval = absDifference.inMilliseconds / 1000;
|
|
||||||
if (timeInterval >= 10.0) {
|
|
||||||
return inPast ? "${timeInterval.truncate()} seconds ago" : "in ${timeInterval.truncate()} seconds";
|
|
||||||
} else {
|
|
||||||
return inPast ? "${f1.format(timeInterval)} seconds ago" : "in ${f1.format(timeInterval)} seconds";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Takes number of [microseconds] and returns readable 40 lines time
|
|
||||||
String get40lTime(int microseconds){
|
|
||||||
return microseconds > 60000000 ? "${(microseconds/1000000/60).floor()}:${(secs.format(microseconds /1000000 % 60))}" : _timeInSec.format(microseconds / 1000000);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Readable [a] - [b], without sign
|
|
||||||
String readableTimeDifference(Duration a, Duration b){
|
|
||||||
Duration result = a - b;
|
|
||||||
|
|
||||||
return NumberFormat("0.000s;0.000s", LocaleSettings.currentLocale.languageCode).format(result.inMilliseconds/1000);
|
|
||||||
}
|
|
|
@ -1,5 +1,3 @@
|
||||||
// ignore_for_file: use_build_context_synchronously
|
|
||||||
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
@ -7,7 +5,7 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:intl/intl.dart';
|
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/main.dart' show teto;
|
import 'package:tetra_stats/services/tetrio_crud.dart';
|
||||||
import 'package:tetra_stats/widgets/vs_graphs.dart';
|
import 'package:tetra_stats/widgets/vs_graphs.dart';
|
||||||
import 'package:window_manager/window_manager.dart';
|
import 'package:window_manager/window_manager.dart';
|
||||||
|
|
||||||
|
@ -20,6 +18,7 @@ Mode greenSideMode = Mode.player;
|
||||||
List<dynamic> theGreenSide = [null, null, null]; // TetrioPlayer?, List<DropdownMenuItem<TetrioPlayer>>?, TetraLeagueAlpha?
|
List<dynamic> theGreenSide = [null, null, null]; // TetrioPlayer?, List<DropdownMenuItem<TetrioPlayer>>?, TetraLeagueAlpha?
|
||||||
Mode redSideMode = Mode.player;
|
Mode redSideMode = Mode.player;
|
||||||
List<dynamic> theRedSide = [null, null, null];
|
List<dynamic> theRedSide = [null, null, null];
|
||||||
|
final TetrioService teto = TetrioService();
|
||||||
final DateFormat dateFormat = DateFormat.yMd(LocaleSettings.currentLocale.languageCode).add_Hm();
|
final DateFormat dateFormat = DateFormat.yMd(LocaleSettings.currentLocale.languageCode).add_Hm();
|
||||||
var numbersReg = RegExp(r'\d+(\.\d*)*');
|
var numbersReg = RegExp(r'\d+(\.\d*)*');
|
||||||
late String oldWindowTitle;
|
late String oldWindowTitle;
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_colorpicker/flutter_colorpicker.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:tetra_stats/views/settings_view.dart' show subtitleStyle;
|
|
||||||
import 'package:tetra_stats/main.dart' show MyAppState, prefs;
|
|
||||||
import 'package:tetra_stats/gen/strings.g.dart';
|
import 'package:tetra_stats/gen/strings.g.dart';
|
||||||
import 'package:window_manager/window_manager.dart';
|
import 'package:window_manager/window_manager.dart';
|
||||||
|
|
||||||
|
@ -19,10 +17,8 @@ class CustomizationView extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class CustomizationState extends State<CustomizationView> {
|
class CustomizationState extends State<CustomizationView> {
|
||||||
|
late SharedPreferences prefs;
|
||||||
late bool oskKagariGimmick;
|
late bool oskKagariGimmick;
|
||||||
late bool sheetbotRadarGraphs;
|
|
||||||
late int ratingMode;
|
|
||||||
late int timestampMode;
|
|
||||||
|
|
||||||
void changeColor(Color color) {
|
void changeColor(Color color) {
|
||||||
setState(() => pickerColor = color);
|
setState(() => pickerColor = color);
|
||||||
|
@ -34,7 +30,7 @@ class CustomizationState extends State<CustomizationView> {
|
||||||
windowManager.getTitle().then((value) => oldWindowTitle = value);
|
windowManager.getTitle().then((value) => oldWindowTitle = value);
|
||||||
windowManager.setTitle("Tetra Stats: ${t.settings}");
|
windowManager.setTitle("Tetra Stats: ${t.settings}");
|
||||||
}
|
}
|
||||||
_getPreferences();
|
_getPreferences().then((value) => setState((){}));
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,27 +40,13 @@ class CustomizationState extends State<CustomizationView> {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _getPreferences() {
|
Future<void> _getPreferences() async {
|
||||||
|
prefs = await SharedPreferences.getInstance();
|
||||||
if (prefs.getBool("oskKagariGimmick") != null) {
|
if (prefs.getBool("oskKagariGimmick") != null) {
|
||||||
oskKagariGimmick = prefs.getBool("oskKagariGimmick")!;
|
oskKagariGimmick = prefs.getBool("oskKagariGimmick")!;
|
||||||
} else {
|
} else {
|
||||||
oskKagariGimmick = true;
|
oskKagariGimmick = true;
|
||||||
}
|
}
|
||||||
if (prefs.getBool("sheetbotRadarGraphs") != null) {
|
|
||||||
sheetbotRadarGraphs = prefs.getBool("sheetbotRadarGraphs")!;
|
|
||||||
} else {
|
|
||||||
sheetbotRadarGraphs = false;
|
|
||||||
}
|
|
||||||
if (prefs.getInt("ratingMode") != null) {
|
|
||||||
ratingMode = prefs.getInt("ratingMode")!;
|
|
||||||
} else {
|
|
||||||
ratingMode = 0;
|
|
||||||
}
|
|
||||||
if (prefs.getInt("timestampMode") != null) {
|
|
||||||
timestampMode = prefs.getInt("timestampMode")!;
|
|
||||||
} else {
|
|
||||||
timestampMode = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ThemeData getTheme(BuildContext context, Color color){
|
ThemeData getTheme(BuildContext context, Color color){
|
||||||
|
@ -82,89 +64,48 @@ class CustomizationState extends State<CustomizationView> {
|
||||||
}
|
}
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(t.customization),
|
title: Text(t.settings),
|
||||||
),
|
),
|
||||||
backgroundColor: Colors.black,
|
backgroundColor: Colors.black,
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
child: ListView(
|
child: ListView(
|
||||||
children: [
|
children: [
|
||||||
ListTile(
|
// ListTile(
|
||||||
title: Text(t.AccentColor),
|
// title: const Text("Accent color"),
|
||||||
subtitle: Text(t.AccentColorDescription, style: subtitleStyle),
|
// trailing: ColorIndicator(HSVColor.fromColor(Theme.of(context).colorScheme.primary)),
|
||||||
trailing: ColorIndicator(HSVColor.fromColor(Theme.of(context).colorScheme.primary), width: 25, height: 25),
|
// onTap: () {
|
||||||
onTap: () {
|
// showDialog(
|
||||||
showDialog(
|
// context: context,
|
||||||
context: context,
|
// builder: (BuildContext context) => AlertDialog(
|
||||||
builder: (BuildContext context) => AlertDialog(
|
// title: const Text('Pick an accent color'),
|
||||||
title: const Text('Pick an accent color'),
|
// content: SingleChildScrollView(
|
||||||
content: SingleChildScrollView(
|
// child: ColorPicker(
|
||||||
child: ColorPicker(
|
// pickerColor: pickerColor,
|
||||||
pickerColor: pickerColor,
|
// onColorChanged: changeColor,
|
||||||
onColorChanged: changeColor,
|
// ),
|
||||||
),
|
// ),
|
||||||
),
|
// actions: <Widget>[
|
||||||
actions: <Widget>[
|
// ElevatedButton(
|
||||||
ElevatedButton(
|
// child: const Text('Set'),
|
||||||
child: const Text('Set'),
|
// onPressed: () {
|
||||||
onPressed: () {
|
// setState(() {
|
||||||
setState(() {
|
// setAccentColor(pickerColor);
|
||||||
context.findAncestorStateOfType<MyAppState>()?.setAccentColor(pickerColor);
|
// });
|
||||||
prefs.setInt("accentColor", pickerColor.value);
|
// Navigator.of(context).pop();
|
||||||
});
|
// },
|
||||||
Navigator.of(context).pop();
|
// ),
|
||||||
},
|
// ]));
|
||||||
),
|
// }),
|
||||||
]));
|
// const ListTile(
|
||||||
}
|
// title: Text("Font"),
|
||||||
),
|
// subtitle: Text("Not implemented"),
|
||||||
|
// ),
|
||||||
// const ListTile(
|
// const ListTile(
|
||||||
// title: Text("Stats Table in TL mathes list"),
|
// title: Text("Stats Table in TL mathes list"),
|
||||||
// subtitle: Text("Not implemented"),
|
// subtitle: Text("Not implemented"),
|
||||||
// ),
|
// ),
|
||||||
ListTile(title: Text(t.timestamps),
|
|
||||||
subtitle: Text(t.timestampsDescription, style: subtitleStyle),
|
|
||||||
trailing: DropdownButton(
|
|
||||||
value: timestampMode,
|
|
||||||
items: <DropdownMenuItem>[
|
|
||||||
DropdownMenuItem(value: 0, child: Text(t.timestampsAbsoluteGMT)),
|
|
||||||
DropdownMenuItem(value: 1, child: Text(t.timestampsAbsoluteLocalTime)),
|
|
||||||
DropdownMenuItem(value: 2, child: Text(t.timestampsRelative))
|
|
||||||
],
|
|
||||||
onChanged: (dynamic value){
|
|
||||||
prefs.setInt("timestampMode", value);
|
|
||||||
setState(() {
|
|
||||||
timestampMode = value;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
ListTile(title: Text(t.rating),
|
|
||||||
subtitle: Text(t.ratingDescription, style: subtitleStyle),
|
|
||||||
trailing: DropdownButton(
|
|
||||||
value: ratingMode,
|
|
||||||
items: <DropdownMenuItem>[
|
|
||||||
const DropdownMenuItem(value: 0, child: Text("TR")),
|
|
||||||
const DropdownMenuItem(value: 1, child: Text("Glicko")),
|
|
||||||
DropdownMenuItem(value: 2, child: Text(t.ratingLBposition))
|
|
||||||
],
|
|
||||||
onChanged: (dynamic value){
|
|
||||||
prefs.setInt("ratingMode", value);
|
|
||||||
setState(() {
|
|
||||||
ratingMode = value;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
ListTile(title: Text(t.sheetbotGraphs),
|
|
||||||
subtitle: Text(t.sheetbotGraphsDescription, style: subtitleStyle),
|
|
||||||
trailing: Switch(value: sheetbotRadarGraphs, onChanged: (bool value){
|
|
||||||
prefs.setBool("sheetbotRadarGraphs", value);
|
|
||||||
setState(() {
|
|
||||||
sheetbotRadarGraphs = value;
|
|
||||||
});
|
|
||||||
}),),
|
|
||||||
ListTile(title: Text(t.oskKagari),
|
ListTile(title: Text(t.oskKagari),
|
||||||
subtitle: Text(t.oskKagariDescription, style: subtitleStyle),
|
subtitle: Text(t.oskKagariDescription),
|
||||||
trailing: Switch(value: oskKagariGimmick, onChanged: (bool value){
|
trailing: Switch(value: oskKagariGimmick, onChanged: (bool value){
|
||||||
prefs.setBool("oskKagariGimmick", value);
|
prefs.setBool("oskKagariGimmick", value);
|
||||||
setState(() {
|
setState(() {
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
// ignore_for_file: type_literal_in_constant_pattern, use_build_context_synchronously
|
// ignore_for_file: type_literal_in_constant_pattern
|
||||||
|
|
||||||
import 'dart:async';
|
|
||||||
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';
|
||||||
|
@ -11,38 +10,39 @@ import 'package:intl/intl.dart';
|
||||||
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';
|
||||||
import 'package:tetra_stats/data_objects/tetra_stats.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/main.dart' show prefs, teto;
|
import 'package:tetra_stats/services/tetrio_crud.dart';
|
||||||
|
import 'package:tetra_stats/main.dart' show prefs;
|
||||||
import 'package:tetra_stats/services/crud_exceptions.dart';
|
import 'package:tetra_stats/services/crud_exceptions.dart';
|
||||||
import 'package:tetra_stats/utils/numers_formats.dart';
|
import 'package:tetra_stats/utils/numers_formats.dart';
|
||||||
import 'package:tetra_stats/utils/open_in_browser.dart';
|
|
||||||
import 'package:tetra_stats/utils/relative_timestamps.dart';
|
|
||||||
import 'package:tetra_stats/utils/text_shadow.dart';
|
import 'package:tetra_stats/utils/text_shadow.dart';
|
||||||
import 'package:tetra_stats/views/singleplayer_record_view.dart';
|
import 'package:tetra_stats/views/ranks_averages_view.dart' show RankAveragesView;
|
||||||
|
import 'package:tetra_stats/views/sprint_and_blitz_averages.dart';
|
||||||
|
import 'package:tetra_stats/views/tl_leaderboard_view.dart' show TLLeaderboardView;
|
||||||
import 'package:tetra_stats/views/tl_match_view.dart' show TlMatchResultView;
|
import 'package:tetra_stats/views/tl_match_view.dart' show TlMatchResultView;
|
||||||
import 'package:tetra_stats/widgets/finesse_thingy.dart';
|
import 'package:tetra_stats/widgets/finesse_thingy.dart';
|
||||||
import 'package:tetra_stats/widgets/lineclears_thingy.dart';
|
import 'package:tetra_stats/widgets/lineclears_thingy.dart';
|
||||||
import 'package:tetra_stats/widgets/list_tile_trailing_stats.dart';
|
import 'package:tetra_stats/widgets/list_tile_trailing_stats.dart';
|
||||||
import 'package:tetra_stats/widgets/recent_sp_games.dart';
|
|
||||||
import 'package:tetra_stats/widgets/search_box.dart';
|
import 'package:tetra_stats/widgets/search_box.dart';
|
||||||
import 'package:tetra_stats/widgets/singleplayer_record.dart';
|
|
||||||
import 'package:tetra_stats/widgets/sp_trailing_stats.dart';
|
|
||||||
import 'package:tetra_stats/widgets/stat_sell_num.dart';
|
import 'package:tetra_stats/widgets/stat_sell_num.dart';
|
||||||
import 'package:tetra_stats/widgets/text_timestamp.dart';
|
|
||||||
import 'package:tetra_stats/widgets/tl_thingy.dart';
|
import 'package:tetra_stats/widgets/tl_thingy.dart';
|
||||||
import 'package:tetra_stats/widgets/user_thingy.dart';
|
import 'package:tetra_stats/widgets/user_thingy.dart';
|
||||||
import 'package:window_manager/window_manager.dart';
|
import 'package:window_manager/window_manager.dart';
|
||||||
import 'package:flutter_markdown/flutter_markdown.dart';
|
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
|
final TetrioService teto = TetrioService(); // thing, that manadge our local DB
|
||||||
int _chartsIndex = 0;
|
int _chartsIndex = 0;
|
||||||
bool _gamesPlayedInsteadOfDateAndTime = false;
|
bool _gamesPlayedInsteadOfDateAndTime = false;
|
||||||
late ZoomPanBehavior _zoomPanBehavior;
|
late ZoomPanBehavior _zoomPanBehavior;
|
||||||
bool _smooth = false;
|
bool _smooth = false;
|
||||||
List _historyShortTitles = ["TR", "Glicko", "RD", "APM", "PPS", "VS", "APP", "DS/S", "DS/P", "APP + DS/P", "VS/APM", "Cheese", "GbE", "wAPP", "Area", "eTR", "±eTR", "Opener", "Plonk", "Inf. DS", "Stride"];
|
List _historyShortTitles = ["TR", "Glicko", "RD", "APM", "PPS", "VS", "APP", "DS/S", "DS/P", "APP + DS/P", "VS/APM", "Cheese", "GbE", "wAPP", "Area", "eTR", "±eTR", "Opener", "Plonk", "Inf. DS", "Stride"];
|
||||||
late ScrollController _scrollController;
|
late ScrollController _scrollController;
|
||||||
|
final NumberFormat _timeInSec = NumberFormat("#,###.###s.", LocaleSettings.currentLocale.languageCode);
|
||||||
|
final NumberFormat secs = NumberFormat("00.###", LocaleSettings.currentLocale.languageCode);
|
||||||
|
final DateFormat _dateFormat = DateFormat.yMMMd(LocaleSettings.currentLocale.languageCode).add_Hms();
|
||||||
|
|
||||||
|
|
||||||
class MainView extends StatefulWidget {
|
class MainView extends StatefulWidget {
|
||||||
final String? player;
|
final String? player;
|
||||||
|
@ -59,6 +59,25 @@ Future<void> copyToClipboard(String text) async {
|
||||||
await Clipboard.setData(ClipboardData(text: text));
|
await Clipboard.setData(ClipboardData(text: text));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Takes number of [microseconds] and returns readable 40 lines time
|
||||||
|
String get40lTime(int microseconds){
|
||||||
|
return microseconds > 60000000 ? "${(microseconds/1000000/60).floor()}:${(secs.format(microseconds /1000000 % 60))}" : _timeInSec.format(microseconds / 1000000);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Readable [a] - [b], without sign
|
||||||
|
String readableTimeDifference(Duration a, Duration b){
|
||||||
|
Duration result = a - b;
|
||||||
|
|
||||||
|
return NumberFormat("0.000s;0.000s", LocaleSettings.currentLocale.languageCode).format(result.inMilliseconds/1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Readable [a] - [b], without sign
|
||||||
|
String readableIntDifference(int a, int b){
|
||||||
|
int result = a - b;
|
||||||
|
|
||||||
|
return NumberFormat("#,###;#,###", LocaleSettings.currentLocale.languageCode).format(result);
|
||||||
|
}
|
||||||
|
|
||||||
class _MainState extends State<MainView> with TickerProviderStateMixin {
|
class _MainState extends State<MainView> with TickerProviderStateMixin {
|
||||||
Future<List> me = Future.delayed(const Duration(seconds: 60), () => [null, null, null, null, null, null]); // I love lists shut up
|
Future<List> me = Future.delayed(const Duration(seconds: 60), () => [null, null, null, null, null, null]); // I love lists shut up
|
||||||
TetrioPlayersLeaderboard? everyone;
|
TetrioPlayersLeaderboard? everyone;
|
||||||
|
@ -75,10 +94,10 @@ class _MainState extends State<MainView> with TickerProviderStateMixin {
|
||||||
//var tableData = <TableRow>[];
|
//var tableData = <TableRow>[];
|
||||||
final bodyGlobalKey = GlobalKey();
|
final bodyGlobalKey = GlobalKey();
|
||||||
bool _showSearchBar = false;
|
bool _showSearchBar = false;
|
||||||
Timer backgroundUpdate = Timer(const Duration(days: 365), (){});
|
|
||||||
bool _TLHistoryWasFetched = false;
|
bool _TLHistoryWasFetched = false;
|
||||||
late TabController _tabController;
|
late TabController _tabController;
|
||||||
late TabController _wideScreenTabController;
|
late TabController _wideScreenTabController;
|
||||||
|
late bool fixedScroll;
|
||||||
|
|
||||||
String get title => "Tetra Stats: $_titleNickname";
|
String get title => "Tetra Stats: $_titleNickname";
|
||||||
|
|
||||||
|
@ -86,7 +105,7 @@ class _MainState extends State<MainView> with TickerProviderStateMixin {
|
||||||
void initState() {
|
void initState() {
|
||||||
initDB();
|
initDB();
|
||||||
_scrollController = ScrollController();
|
_scrollController = ScrollController();
|
||||||
_tabController = TabController(length: 7, vsync: this);
|
_tabController = TabController(length: 6, vsync: this);
|
||||||
_wideScreenTabController = TabController(length: 4, vsync: this);
|
_wideScreenTabController = TabController(length: 4, vsync: this);
|
||||||
_zoomPanBehavior = ZoomPanBehavior(
|
_zoomPanBehavior = ZoomPanBehavior(
|
||||||
enablePinching: true,
|
enablePinching: true,
|
||||||
|
@ -139,7 +158,6 @@ class _MainState extends State<MainView> with TickerProviderStateMixin {
|
||||||
Future<List> fetch(String nickOrID, {bool fetchHistory = false, bool fetchTLmatches = false}) async {
|
Future<List> fetch(String nickOrID, {bool fetchHistory = false, bool fetchTLmatches = false}) async {
|
||||||
TetrioPlayer me;
|
TetrioPlayer me;
|
||||||
_TLHistoryWasFetched = false;
|
_TLHistoryWasFetched = false;
|
||||||
backgroundUpdate.cancel();
|
|
||||||
|
|
||||||
// If user trying to search with discord id
|
// If user trying to search with discord id
|
||||||
if (nickOrID.startsWith("ds:")){
|
if (nickOrID.startsWith("ds:")){
|
||||||
|
@ -156,32 +174,23 @@ class _MainState extends State<MainView> with TickerProviderStateMixin {
|
||||||
// Requesting Tetra League (alpha), records, news and top TR of player
|
// Requesting Tetra League (alpha), records, news and top TR of player
|
||||||
late List<dynamic> requests;
|
late List<dynamic> requests;
|
||||||
late TetraLeagueAlphaStream tlStream;
|
late TetraLeagueAlphaStream tlStream;
|
||||||
late UserRecords records;
|
late Map<String, dynamic> records;
|
||||||
late News news;
|
late List<News> news;
|
||||||
late SingleplayerStream recent;
|
|
||||||
late SingleplayerStream sprint;
|
|
||||||
late SingleplayerStream blitz;
|
|
||||||
late TetrioPlayerFromLeaderboard? topOne;
|
late TetrioPlayerFromLeaderboard? topOne;
|
||||||
late TopTr? topTR;
|
late double? topTR;
|
||||||
requests = await Future.wait([ // all at once (7 requests to oskware lmao)
|
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),
|
||||||
teto.fetchSingleplayerStream(_searchFor, "any_userrecent"),
|
|
||||||
teto.fetchSingleplayerStream(_searchFor, "40l_userbest"),
|
|
||||||
teto.fetchSingleplayerStream(_searchFor, "blitz_userbest"),
|
|
||||||
prefs.getBool("showPositions") != true ? teto.fetchCutoffs() : Future.delayed(Duration.zero, ()=><Map<String, double>>[]),
|
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),
|
(me.tlSeason1.rank != "z" ? me.tlSeason1.rank == "x" : me.tlSeason1.percentileRank == "x") ? teto.fetchTopOneFromTheLeaderboard() : Future.delayed(Duration.zero, ()=>null),
|
||||||
(me.tlSeason1.gamesPlayed > 9) ? teto.fetchTopTR(_searchFor) : Future.delayed(Duration.zero, () => null) // 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 UserRecords;
|
records = requests[1] as Map<String, dynamic>;
|
||||||
news = requests[2] as News;
|
news = requests[2] as List<News>;
|
||||||
recent = requests[3] as SingleplayerStream;
|
topOne = requests[4] as TetrioPlayerFromLeaderboard?;
|
||||||
sprint = requests[4] as SingleplayerStream;
|
topTR = requests.elementAtOrNull(5) as double?; // No TR - no Top TR
|
||||||
blitz = requests[5] as SingleplayerStream;
|
|
||||||
topOne = requests[7] as TetrioPlayerFromLeaderboard?;
|
|
||||||
topTR = requests[8] as TopTr?; // 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){
|
||||||
|
@ -193,8 +202,8 @@ 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[6] as Cutoffs?)?.tr;
|
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[6] as Cutoffs?)?.glicko;
|
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];
|
||||||
|
@ -299,14 +308,7 @@ class _MainState extends State<MainView> with TickerProviderStateMixin {
|
||||||
compareWith = null;
|
compareWith = null;
|
||||||
chartsData = [];
|
chartsData = [];
|
||||||
}
|
}
|
||||||
|
return [me, records, states, tlMatches, compareWith, isTracking, news, topTR];
|
||||||
if (prefs.getBool("updateInBG") == true) {
|
|
||||||
backgroundUpdate = Timer(me.cachedUntil!.difference(DateTime.now()), () {
|
|
||||||
changePlayer(me.userId);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return [me, records, states, tlMatches, compareWith, isTracking, news, topTR, recent, sprint, blitz];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Triggers widgets rebuild
|
/// Triggers widgets rebuild
|
||||||
|
@ -438,7 +440,6 @@ class _MainState extends State<MainView> with TickerProviderStateMixin {
|
||||||
Tab(text: t.history),
|
Tab(text: t.history),
|
||||||
Tab(text: t.sprint),
|
Tab(text: t.sprint),
|
||||||
Tab(text: t.blitz),
|
Tab(text: t.blitz),
|
||||||
Tab(text: t.recentRuns),
|
|
||||||
Tab(text: t.other),
|
Tab(text: t.other),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -458,7 +459,7 @@ class _MainState extends State<MainView> with TickerProviderStateMixin {
|
||||||
tl: snapshot.data![0].tlSeason1,
|
tl: snapshot.data![0].tlSeason1,
|
||||||
userID: snapshot.data![0].userId,
|
userID: snapshot.data![0].userId,
|
||||||
states: snapshot.data![2],
|
states: snapshot.data![2],
|
||||||
topTR: snapshot.data![7]?.tr,
|
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,
|
thatRankCutoff: thatRankCutoff,
|
||||||
|
@ -477,14 +478,14 @@ class _MainState extends State<MainView> with TickerProviderStateMixin {
|
||||||
),
|
),
|
||||||
],),
|
],),
|
||||||
_History(chartsData: chartsData, changePlayer: changePlayer, userID: _searchFor, update: _justUpdate, wasActiveInTL: snapshot.data![0].tlSeason1.gamesPlayed > 0),
|
_History(chartsData: chartsData, changePlayer: changePlayer, userID: _searchFor, update: _justUpdate, wasActiveInTL: snapshot.data![0].tlSeason1.gamesPlayed > 0),
|
||||||
_TwoRecordsThingy(sprint: snapshot.data![1].sprint, blitz: snapshot.data![1].blitz, rank: snapshot.data![0].tlSeason1.percentileRank, recent: snapshot.data![8], sprintStream: snapshot.data![9], blitzStream: snapshot.data![10]),
|
_TwoRecordsThingy(sprint: snapshot.data![1]['sprint'], blitz: snapshot.data![1]['blitz'], rank: snapshot.data![0].tlSeason1.percentileRank,),
|
||||||
_OtherThingy(zen: snapshot.data![1].zen, bio: snapshot.data![0].bio, distinguishment: snapshot.data![0].distinguishment, newsletter: snapshot.data![6],)
|
_OtherThingy(zen: snapshot.data![1]['zen'], bio: snapshot.data![0].bio, distinguishment: snapshot.data![0].distinguishment, newsletter: snapshot.data![6],)
|
||||||
] : [
|
] : [
|
||||||
TLThingy(
|
TLThingy(
|
||||||
tl: snapshot.data![0].tlSeason1,
|
tl: snapshot.data![0].tlSeason1,
|
||||||
userID: snapshot.data![0].userId,
|
userID: snapshot.data![0].userId,
|
||||||
states: snapshot.data![2],
|
states: snapshot.data![2],
|
||||||
topTR: snapshot.data![7]?.tr,
|
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,
|
thatRankCutoff: thatRankCutoff,
|
||||||
|
@ -498,10 +499,9 @@ class _MainState extends State<MainView> with TickerProviderStateMixin {
|
||||||
),
|
),
|
||||||
_TLRecords(userID: snapshot.data![0].userId, changePlayer: changePlayer, data: snapshot.data![3], wasActiveInTL: snapshot.data![0].tlSeason1.gamesPlayed > 0, oldMathcesHere: _TLHistoryWasFetched),
|
_TLRecords(userID: snapshot.data![0].userId, changePlayer: changePlayer, data: snapshot.data![3], wasActiveInTL: snapshot.data![0].tlSeason1.gamesPlayed > 0, oldMathcesHere: _TLHistoryWasFetched),
|
||||||
_History(chartsData: chartsData, changePlayer: changePlayer, userID: _searchFor, update: _justUpdate, wasActiveInTL: snapshot.data![0].tlSeason1.gamesPlayed > 0),
|
_History(chartsData: chartsData, changePlayer: changePlayer, userID: _searchFor, update: _justUpdate, wasActiveInTL: snapshot.data![0].tlSeason1.gamesPlayed > 0),
|
||||||
SingleplayerRecord(record: snapshot.data![1].sprint, rank: snapshot.data![0].tlSeason1.percentileRank, stream: snapshot.data![9]),
|
_RecordThingy(record: snapshot.data![1]['sprint'], rank: snapshot.data![0].tlSeason1.percentileRank),
|
||||||
SingleplayerRecord(record: snapshot.data![1].blitz, rank: snapshot.data![0].tlSeason1.percentileRank, stream: snapshot.data![10]),
|
_RecordThingy(record: snapshot.data![1]['blitz'], rank: snapshot.data![0].tlSeason1.percentileRank),
|
||||||
_RecentSingleplayersThingy(snapshot.data![8]),
|
_OtherThingy(zen: snapshot.data![1]['zen'], bio: snapshot.data![0].bio, distinguishment: snapshot.data![0].distinguishment, newsletter: snapshot.data![6],)
|
||||||
_OtherThingy(zen: snapshot.data![1].zen, bio: snapshot.data![0].bio, distinguishment: snapshot.data![0].distinguishment, newsletter: snapshot.data![6])
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -643,7 +643,12 @@ class _NavDrawerState extends State<NavDrawer> {
|
||||||
leading: const Icon(Icons.leaderboard),
|
leading: const Icon(Icons.leaderboard),
|
||||||
title: Text(t.tlLeaderboard),
|
title: Text(t.tlLeaderboard),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.go("/leaderboard");
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => const TLLeaderboardView(),
|
||||||
|
),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -652,7 +657,12 @@ class _NavDrawerState extends State<NavDrawer> {
|
||||||
leading: const Icon(Icons.compress),
|
leading: const Icon(Icons.compress),
|
||||||
title: Text(t.rankAveragesViewTitle),
|
title: Text(t.rankAveragesViewTitle),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.go("/LBvalues");
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => const RankAveragesView(),
|
||||||
|
),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -661,7 +671,12 @@ class _NavDrawerState extends State<NavDrawer> {
|
||||||
leading: const Icon(Icons.bar_chart),
|
leading: const Icon(Icons.bar_chart),
|
||||||
title: Text(t.sprintAndBlitsViewTitle),
|
title: Text(t.sprintAndBlitsViewTitle),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.go("/sprintAndBlitzAverages");
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => const SprintAndBlitzView(),
|
||||||
|
),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -743,7 +758,7 @@ class _TLRecords extends StatelessWidget {
|
||||||
leading: Text("${data[index].endContext.firstWhere((element) => element.userId == userID).points} : ${data[index].endContext.firstWhere((element) => element.userId != userID).points}",
|
leading: Text("${data[index].endContext.firstWhere((element) => element.userId == userID).points} : ${data[index].endContext.firstWhere((element) => element.userId != userID).points}",
|
||||||
style: bigScreen ? const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 28, shadows: textShadow) : const TextStyle(fontSize: 28, shadows: textShadow)),
|
style: bigScreen ? const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 28, shadows: textShadow) : const TextStyle(fontSize: 28, shadows: textShadow)),
|
||||||
title: Text("vs. ${data[index].endContext.firstWhere((element) => element.userId != userID).username}"),
|
title: Text("vs. ${data[index].endContext.firstWhere((element) => element.userId != userID).username}"),
|
||||||
subtitle: Text(timestamp(data[index].timestamp), style: const TextStyle(color: Colors.grey)),
|
subtitle: Text(_dateFormat.format(data[index].timestamp)),
|
||||||
trailing: TrailingStats(
|
trailing: TrailingStats(
|
||||||
data[index].endContext.firstWhere((element) => element.userId == userID).secondary,
|
data[index].endContext.firstWhere((element) => element.userId == userID).secondary,
|
||||||
data[index].endContext.firstWhere((element) => element.userId == userID).tertiary,
|
data[index].endContext.firstWhere((element) => element.userId == userID).tertiary,
|
||||||
|
@ -906,7 +921,7 @@ class _HistoryChartThigyState extends State<_HistoryChartThigy> {
|
||||||
style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 20),
|
style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 20),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(_gamesPlayedInsteadOfDateAndTime ? t.gamesPlayed(games: t.games(n: data.gamesPlayed)) : timestamp(data.timestamp))
|
Text(_gamesPlayedInsteadOfDateAndTime ? t.gamesPlayed(games: t.games(n: data.gamesPlayed)) : _dateFormat.format(data.timestamp))
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -949,38 +964,39 @@ class _HistoryChartThigyState extends State<_HistoryChartThigy> {
|
||||||
primaryYAxis: const NumericAxis(
|
primaryYAxis: const NumericAxis(
|
||||||
rangePadding: ChartRangePadding.additional,
|
rangePadding: ChartRangePadding.additional,
|
||||||
),
|
),
|
||||||
margin: const EdgeInsets.all(0),
|
|
||||||
series: <CartesianSeries>[
|
series: <CartesianSeries>[
|
||||||
if (_gamesPlayedInsteadOfDateAndTime) StepLineSeries<_HistoryChartSpot, int>(
|
if (_gamesPlayedInsteadOfDateAndTime) StepLineSeries<_HistoryChartSpot, int>(
|
||||||
enableTooltip: true,
|
enableTooltip: true,
|
||||||
|
// splineType: SplineType.cardinal,
|
||||||
|
// cardinalSplineTension: 0.2,
|
||||||
dataSource: widget.data,
|
dataSource: widget.data,
|
||||||
animationDuration: 0,
|
animationDuration: 0,
|
||||||
opacity: _smooth ? 0 : 1,
|
opacity: _smooth ? 0 : 1,
|
||||||
xValueMapper: (_HistoryChartSpot data, _) => data.gamesPlayed,
|
xValueMapper: (_HistoryChartSpot data, _) => data.gamesPlayed,
|
||||||
yValueMapper: (_HistoryChartSpot data, _) => data.stat,
|
yValueMapper: (_HistoryChartSpot data, _) => data.stat,
|
||||||
color: Theme.of(context).colorScheme.primary,
|
|
||||||
trendlines:<Trendline>[
|
trendlines:<Trendline>[
|
||||||
Trendline(
|
Trendline(
|
||||||
isVisible: _smooth,
|
isVisible: _smooth,
|
||||||
period: (widget.data.length/175).floor(),
|
period: (widget.data.length/175).floor(),
|
||||||
type: TrendlineType.movingAverage,
|
type: TrendlineType.movingAverage,
|
||||||
color: Theme.of(context).colorScheme.primary)
|
color: Colors.blue)
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
else StepLineSeries<_HistoryChartSpot, DateTime>(
|
else StepLineSeries<_HistoryChartSpot, DateTime>(
|
||||||
enableTooltip: true,
|
enableTooltip: true,
|
||||||
|
// splineType: SplineType.cardinal,
|
||||||
|
// cardinalSplineTension: 0.2,
|
||||||
dataSource: widget.data,
|
dataSource: widget.data,
|
||||||
animationDuration: 0,
|
animationDuration: 0,
|
||||||
opacity: _smooth ? 0 : 1,
|
opacity: _smooth ? 0 : 1,
|
||||||
xValueMapper: (_HistoryChartSpot data, _) => data.timestamp,
|
xValueMapper: (_HistoryChartSpot data, _) => data.timestamp,
|
||||||
yValueMapper: (_HistoryChartSpot data, _) => data.stat,
|
yValueMapper: (_HistoryChartSpot data, _) => data.stat,
|
||||||
color: Theme.of(context).colorScheme.primary,
|
|
||||||
trendlines:<Trendline>[
|
trendlines:<Trendline>[
|
||||||
Trendline(
|
Trendline(
|
||||||
isVisible: _smooth,
|
isVisible: _smooth,
|
||||||
period: (widget.data.length/175).floor(),
|
period: (widget.data.length/175).floor(),
|
||||||
type: TrendlineType.movingAverage,
|
type: TrendlineType.movingAverage,
|
||||||
color: Theme.of(context).colorScheme.primary)
|
color: Colors.blue)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -994,12 +1010,9 @@ class _HistoryChartThigyState extends State<_HistoryChartThigy> {
|
||||||
class _TwoRecordsThingy extends StatelessWidget {
|
class _TwoRecordsThingy extends StatelessWidget {
|
||||||
final RecordSingle? sprint;
|
final RecordSingle? sprint;
|
||||||
final RecordSingle? blitz;
|
final RecordSingle? blitz;
|
||||||
final SingleplayerStream recent;
|
|
||||||
final SingleplayerStream sprintStream;
|
|
||||||
final SingleplayerStream blitzStream;
|
|
||||||
final String? rank;
|
final String? rank;
|
||||||
|
|
||||||
const _TwoRecordsThingy({required this.sprint, required this.blitz, this.rank, required this.recent, required this.sprintStream, required this.blitzStream});
|
const _TwoRecordsThingy({required this.sprint, required this.blitz, this.rank});
|
||||||
|
|
||||||
Color getColorOfRank(int rank){
|
Color getColorOfRank(int rank){
|
||||||
if (rank == 1) return Colors.yellowAccent;
|
if (rank == 1) return Colors.yellowAccent;
|
||||||
|
@ -1015,23 +1028,23 @@ class _TwoRecordsThingy extends StatelessWidget {
|
||||||
//if (record == null) return Center(child: Text(t.noRecord, textAlign: TextAlign.center, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 28)));
|
//if (record == null) return Center(child: Text(t.noRecord, textAlign: TextAlign.center, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 28)));
|
||||||
late MapEntry closestAverageBlitz;
|
late MapEntry closestAverageBlitz;
|
||||||
late bool blitzBetterThanClosestAverage;
|
late bool blitzBetterThanClosestAverage;
|
||||||
bool? blitzBetterThanRankAverage = (rank != null && rank != "z" && blitz != null) ? blitz!.endContext.score > blitzAverages[rank]! : null;
|
bool? blitzBetterThanRankAverage = (rank != null && rank != "z" && blitz != null) ? blitz!.endContext!.score > blitzAverages[rank]! : null;
|
||||||
late MapEntry closestAverageSprint;
|
late MapEntry closestAverageSprint;
|
||||||
late bool sprintBetterThanClosestAverage;
|
late bool sprintBetterThanClosestAverage;
|
||||||
bool? sprintBetterThanRankAverage = (rank != null && rank != "z" && sprint != null) ? sprint!.endContext.finalTime < sprintAverages[rank]! : null;
|
bool? sprintBetterThanRankAverage = (rank != null && rank != "z" && sprint != null) ? sprint!.endContext!.finalTime < sprintAverages[rank]! : null;
|
||||||
if (sprint != null) {
|
if (sprint != null) {
|
||||||
closestAverageSprint = sprintAverages.entries.singleWhere((element) => element.value == sprintAverages.values.reduce((a, b) => (a-sprint!.endContext.finalTime).abs() < (b -sprint!.endContext.finalTime).abs() ? a : b));
|
closestAverageSprint = sprintAverages.entries.singleWhere((element) => element.value == sprintAverages.values.reduce((a, b) => (a-sprint!.endContext!.finalTime).abs() < (b -sprint!.endContext!.finalTime).abs() ? a : b));
|
||||||
sprintBetterThanClosestAverage = sprint!.endContext.finalTime < closestAverageSprint.value;
|
sprintBetterThanClosestAverage = sprint!.endContext!.finalTime < closestAverageSprint.value;
|
||||||
}
|
}
|
||||||
if (blitz != null){
|
if (blitz != null){
|
||||||
closestAverageBlitz = blitzAverages.entries.singleWhere((element) => element.value == blitzAverages.values.reduce((a, b) => (a-blitz!.endContext.score).abs() < (b -blitz!.endContext.score).abs() ? a : b));
|
closestAverageBlitz = blitzAverages.entries.singleWhere((element) => element.value == blitzAverages.values.reduce((a, b) => (a-blitz!.endContext!.score).abs() < (b -blitz!.endContext!.score).abs() ? a : b));
|
||||||
blitzBetterThanClosestAverage = blitz!.endContext.score > closestAverageBlitz.value;
|
blitzBetterThanClosestAverage = blitz!.endContext!.score > closestAverageBlitz.value;
|
||||||
}
|
}
|
||||||
return SingleChildScrollView(child: Padding(
|
return SingleChildScrollView(child: Padding(
|
||||||
padding: const EdgeInsets.only(top: 20.0),
|
padding: const EdgeInsets.only(top: 20.0),
|
||||||
child: Wrap(
|
child: Row(
|
||||||
alignment: WrapAlignment.spaceEvenly,
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
crossAxisAlignment: WrapCrossAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Column(
|
Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
@ -1047,24 +1060,24 @@ class _TwoRecordsThingy extends StatelessWidget {
|
||||||
children: [
|
children: [
|
||||||
Text(t.sprint, style: const TextStyle(height: 0.1, fontFamily: "Eurostile Round Extended", fontSize: 18)),
|
Text(t.sprint, style: const TextStyle(height: 0.1, fontFamily: "Eurostile Round Extended", fontSize: 18)),
|
||||||
RichText(text: TextSpan(
|
RichText(text: TextSpan(
|
||||||
text: sprint != null ? get40lTime(sprint!.endContext.finalTime.inMicroseconds) : "---",
|
text: sprint != null ? get40lTime(sprint!.endContext!.finalTime.inMicroseconds) : "---",
|
||||||
style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 36, fontWeight: FontWeight.w500, color: sprint != null ? Colors.white : Colors.grey),
|
style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 36, fontWeight: FontWeight.w500, color: sprint != null ? Colors.white : Colors.grey),
|
||||||
//children: [TextSpan(text: get40lTime(record!.endContext.finalTime.inMicroseconds), style: TextStyle(fontFamily: "Eurostile Round", fontSize: 14, fontWeight: FontWeight.w100))]
|
//children: [TextSpan(text: get40lTime(record!.endContext!.finalTime.inMicroseconds), style: TextStyle(fontFamily: "Eurostile Round", fontSize: 14, fontWeight: FontWeight.w100))]
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (sprint != null) RichText(text: TextSpan(
|
if (sprint != null) RichText(text: TextSpan(
|
||||||
text: "",
|
text: "",
|
||||||
style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, color: Colors.grey),
|
style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, color: Colors.grey),
|
||||||
children: [
|
children: [
|
||||||
if (rank != null && rank != "z") TextSpan(text: "${t.verdictGeneral(n: readableTimeDifference(sprint!.endContext.finalTime, sprintAverages[rank]!), verdict: sprintBetterThanRankAverage??false ? t.verdictBetter : t.verdictWorse, rank: rank!.toUpperCase())}\n", style: TextStyle(
|
if (rank != null && rank != "z") TextSpan(text: "${t.verdictGeneral(n: readableTimeDifference(sprint!.endContext!.finalTime, sprintAverages[rank]!), verdict: sprintBetterThanRankAverage??false ? t.verdictBetter : t.verdictWorse, rank: rank!.toUpperCase())}\n", style: TextStyle(
|
||||||
color: sprintBetterThanRankAverage??false ? Colors.greenAccent : Colors.redAccent
|
color: sprintBetterThanRankAverage??false ? Colors.greenAccent : Colors.redAccent
|
||||||
))
|
))
|
||||||
else TextSpan(text: "${t.verdictGeneral(n: readableTimeDifference(sprint!.endContext.finalTime, closestAverageSprint.value), verdict: sprintBetterThanClosestAverage ? t.verdictBetter : t.verdictWorse, rank: closestAverageSprint.key.toUpperCase())}\n", style: TextStyle(
|
else TextSpan(text: "${t.verdictGeneral(n: readableTimeDifference(sprint!.endContext!.finalTime, closestAverageSprint.value), verdict: sprintBetterThanClosestAverage ? t.verdictBetter : t.verdictWorse, rank: closestAverageSprint.key.toUpperCase())}\n", style: TextStyle(
|
||||||
color: sprintBetterThanClosestAverage ? Colors.greenAccent : Colors.redAccent
|
color: sprintBetterThanClosestAverage ? Colors.greenAccent : Colors.redAccent
|
||||||
)),
|
)),
|
||||||
if (sprint!.rank != null) TextSpan(text: "№${sprint!.rank}", style: TextStyle(color: getColorOfRank(sprint!.rank!))),
|
if (sprint!.rank != null) TextSpan(text: "№${sprint!.rank}", style: TextStyle(color: getColorOfRank(sprint!.rank!))),
|
||||||
if (sprint!.rank != null) const TextSpan(text: " • "),
|
if (sprint!.rank != null) const TextSpan(text: " • "),
|
||||||
TextSpan(text: timestamp(sprint!.timestamp)),
|
TextSpan(text: _dateFormat.format(sprint!.timestamp!)),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -1076,39 +1089,14 @@ class _TwoRecordsThingy extends StatelessWidget {
|
||||||
alignment: WrapAlignment.spaceBetween,
|
alignment: WrapAlignment.spaceBetween,
|
||||||
spacing: 20,
|
spacing: 20,
|
||||||
children: [
|
children: [
|
||||||
StatCellNum(playerStat: sprint!.endContext.piecesPlaced, playerStatLabel: t.statCellNum.pieces, isScreenBig: true, higherIsBetter: true, smallDecimal: false),
|
StatCellNum(playerStat: sprint!.endContext!.piecesPlaced, playerStatLabel: t.statCellNum.pieces, isScreenBig: true, higherIsBetter: true, smallDecimal: false),
|
||||||
StatCellNum(playerStat: sprint!.endContext.pps, playerStatLabel: t.statCellNum.pps, fractionDigits: 2, isScreenBig: true, higherIsBetter: true, smallDecimal: false),
|
StatCellNum(playerStat: sprint!.endContext!.pps, playerStatLabel: t.statCellNum.pps, fractionDigits: 2, isScreenBig: true, higherIsBetter: true, smallDecimal: false),
|
||||||
StatCellNum(playerStat: sprint!.endContext.kpp, playerStatLabel: t.statCellNum.kpp, fractionDigits: 2, isScreenBig: true, higherIsBetter: true, smallDecimal: false),
|
StatCellNum(playerStat: sprint!.endContext!.kpp, playerStatLabel: t.statCellNum.kpp, fractionDigits: 2, isScreenBig: true, higherIsBetter: true, smallDecimal: false),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
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")
|
||||||
Wrap(
|
|
||||||
alignment: WrapAlignment.spaceBetween,
|
|
||||||
crossAxisAlignment: WrapCrossAlignment.start,
|
|
||||||
spacing: 20,
|
|
||||||
children: [
|
|
||||||
TextButton(onPressed: (){launchInBrowser(Uri.parse("https://tetr.io/#r:${sprint!.replayId}"));}, child: Text(t.openSPreplay)),
|
|
||||||
TextButton(onPressed: (){launchInBrowser(Uri.parse("https://inoue.szy.lol/api/replay/${sprint!.replayId}"));}, child: Text(t.downloadSPreplay)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
if (sprintStream.records.length > 1) SizedBox(
|
|
||||||
width: 400,
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
for (int i = 1; i < sprintStream.records.length; i++) ListTile(
|
|
||||||
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (context) => SingleplayerRecordView(record: sprintStream.records[i]))),
|
|
||||||
leading: Text("#${i+1}", style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 28, shadows: textShadow, height: 0.9) ),
|
|
||||||
title: Text(get40lTime(sprintStream.records[i].endContext.finalTime.inMicroseconds),
|
|
||||||
style: const TextStyle(fontSize: 18)),
|
|
||||||
subtitle: Text(timestamp(sprintStream.records[i].timestamp), style: const TextStyle(color: Colors.grey, height: 0.85)),
|
|
||||||
trailing: SpTrailingStats(sprintStream.records[i].endContext)
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
Column(
|
Column(
|
||||||
|
@ -1128,7 +1116,7 @@ class _TwoRecordsThingy extends StatelessWidget {
|
||||||
text: "",
|
text: "",
|
||||||
style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 36, fontWeight: FontWeight.w500, color: Colors.white),
|
style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 36, fontWeight: FontWeight.w500, color: Colors.white),
|
||||||
children: [
|
children: [
|
||||||
TextSpan(text: blitz != null ? NumberFormat.decimalPattern().format(blitz!.endContext.score) : "---"),
|
TextSpan(text: blitz != null ? NumberFormat.decimalPattern().format(blitz!.endContext!.score) : "---"),
|
||||||
//WidgetSpan(child: Image.asset("res/icons/kagari.png", height: 48))
|
//WidgetSpan(child: Image.asset("res/icons/kagari.png", height: 48))
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
|
@ -1139,13 +1127,13 @@ class _TwoRecordsThingy extends StatelessWidget {
|
||||||
text: "",
|
text: "",
|
||||||
style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, color: Colors.grey),
|
style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, color: Colors.grey),
|
||||||
children: [
|
children: [
|
||||||
if (rank != null && rank != "z") TextSpan(text: "${t.verdictGeneral(n: readableIntDifference(blitz!.endContext.score, blitzAverages[rank]!), verdict: blitzBetterThanRankAverage??false ? t.verdictBetter : t.verdictWorse, rank: rank!.toUpperCase())}\n", style: TextStyle(
|
if (rank != null && rank != "z") TextSpan(text: "${t.verdictGeneral(n: readableIntDifference(blitz!.endContext!.score, blitzAverages[rank]!), verdict: blitzBetterThanRankAverage??false ? t.verdictBetter : t.verdictWorse, rank: rank!.toUpperCase())}\n", style: TextStyle(
|
||||||
color: blitzBetterThanRankAverage??false ? Colors.greenAccent : Colors.redAccent
|
color: blitzBetterThanRankAverage??false ? Colors.greenAccent : Colors.redAccent
|
||||||
))
|
))
|
||||||
else TextSpan(text: "${t.verdictGeneral(n: readableIntDifference(blitz!.endContext.score, closestAverageBlitz.value), verdict: blitzBetterThanClosestAverage ? t.verdictBetter : t.verdictWorse, rank: closestAverageBlitz.key.toUpperCase())}\n", style: TextStyle(
|
else TextSpan(text: "${t.verdictGeneral(n: readableIntDifference(blitz!.endContext!.score, closestAverageBlitz.value), verdict: blitzBetterThanClosestAverage ? t.verdictBetter : t.verdictWorse, rank: closestAverageBlitz.key.toUpperCase())}\n", style: TextStyle(
|
||||||
color: blitzBetterThanClosestAverage ? Colors.greenAccent : Colors.redAccent
|
color: blitzBetterThanClosestAverage ? Colors.greenAccent : Colors.redAccent
|
||||||
)),
|
)),
|
||||||
TextSpan(text: timestamp(blitz!.timestamp)),
|
TextSpan(text: _dateFormat.format(blitz!.timestamp!)),
|
||||||
if (blitz!.rank != null) const TextSpan(text: " • "),
|
if (blitz!.rank != null) const TextSpan(text: " • "),
|
||||||
if (blitz!.rank != null) TextSpan(text: "№${blitz!.rank}", style: TextStyle(color: getColorOfRank(blitz!.rank!))),
|
if (blitz!.rank != null) TextSpan(text: "№${blitz!.rank}", style: TextStyle(color: getColorOfRank(blitz!.rank!))),
|
||||||
]
|
]
|
||||||
|
@ -1162,69 +1150,145 @@ class _TwoRecordsThingy extends StatelessWidget {
|
||||||
crossAxisAlignment: WrapCrossAlignment.start,
|
crossAxisAlignment: WrapCrossAlignment.start,
|
||||||
spacing: 20,
|
spacing: 20,
|
||||||
children: [
|
children: [
|
||||||
StatCellNum(playerStat: blitz!.endContext.level, playerStatLabel: t.statCellNum.level, isScreenBig: true, higherIsBetter: true, smallDecimal: false),
|
StatCellNum(playerStat: blitz!.endContext!.level, playerStatLabel: t.statCellNum.level, isScreenBig: true, higherIsBetter: true, smallDecimal: false),
|
||||||
StatCellNum(playerStat: blitz!.endContext.pps, playerStatLabel: t.statCellNum.pps, fractionDigits: 2, isScreenBig: true, higherIsBetter: true, smallDecimal: false),
|
StatCellNum(playerStat: blitz!.endContext!.pps, playerStatLabel: t.statCellNum.pps, fractionDigits: 2, isScreenBig: true, higherIsBetter: true, smallDecimal: false),
|
||||||
StatCellNum(playerStat: blitz!.endContext.spp, playerStatLabel: t.statCellNum.spp, fractionDigits: 2, isScreenBig: true, higherIsBetter: true)
|
StatCellNum(playerStat: blitz!.endContext!.spp, playerStatLabel: t.statCellNum.spp, fractionDigits: 2, isScreenBig: true, higherIsBetter: true)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
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")
|
||||||
Wrap(
|
|
||||||
alignment: WrapAlignment.spaceBetween,
|
|
||||||
crossAxisAlignment: WrapCrossAlignment.start,
|
|
||||||
spacing: 20,
|
|
||||||
children: [
|
|
||||||
TextButton(onPressed: (){launchInBrowser(Uri.parse("https://tetr.io/#r:${blitz!.replayId}"));}, child: Text(t.openSPreplay)),
|
|
||||||
TextButton(onPressed: (){launchInBrowser(Uri.parse("https://inoue.szy.lol/api/replay/${blitz!.replayId}"));}, child: Text(t.downloadSPreplay)),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (blitzStream.records.length > 1) SizedBox(
|
|
||||||
width: 400,
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
for (int i = 1; i < sprintStream.records.length; i++) ListTile(
|
|
||||||
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (context) => SingleplayerRecordView(record: blitzStream.records[i]))),
|
|
||||||
leading: Text("#${i+1}", style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 28, shadows: textShadow, height: 0.9) ),
|
|
||||||
title: Text("${NumberFormat.decimalPattern().format(blitzStream.records[i].endContext.score)} points",
|
|
||||||
style: const TextStyle(fontSize: 18)),
|
|
||||||
subtitle: Text(timestamp(blitzStream.records[i].timestamp), style: const TextStyle(color: Colors.grey, height: 0.85)),
|
|
||||||
trailing: SpTrailingStats(blitzStream.records[i].endContext)
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
SizedBox(
|
|
||||||
width: 400,
|
|
||||||
child: RecentSingleplayerGames(recent: recent),
|
|
||||||
)
|
|
||||||
]),
|
]),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _RecentSingleplayersThingy extends StatelessWidget {
|
class _RecordThingy extends StatelessWidget {
|
||||||
final SingleplayerStream recent;
|
final RecordSingle? record;
|
||||||
|
final String? rank;
|
||||||
|
|
||||||
const _RecentSingleplayersThingy(this.recent);
|
/// Widget that displays data from [record]
|
||||||
|
const _RecordThingy({required this.record, this.rank});
|
||||||
|
|
||||||
|
Color getColorOfRank(int rank){
|
||||||
|
if (rank == 1) return Colors.yellowAccent;
|
||||||
|
if (rank == 2) return Colors.blueGrey;
|
||||||
|
if (rank == 3) return Colors.brown[400]!;
|
||||||
|
if (rank <= 9) return Colors.blueAccent;
|
||||||
|
if (rank <= 99) return Colors.greenAccent;
|
||||||
|
return Colors.grey;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SingleChildScrollView(
|
if (record == null) return Center(child: Text(t.noRecord, textAlign: TextAlign.center, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 28)));
|
||||||
child: RecentSingleplayerGames(recent: recent, hideTitle: true)
|
late MapEntry closestAverageBlitz;
|
||||||
);
|
late bool blitzBetterThanClosestAverage;
|
||||||
|
bool? blitzBetterThanRankAverage = (rank != null && rank != "z") ? record!.endContext!.score > blitzAverages[rank]! : null;
|
||||||
|
late MapEntry closestAverageSprint;
|
||||||
|
late bool sprintBetterThanClosestAverage;
|
||||||
|
bool? sprintBetterThanRankAverage = (rank != null && rank != "z") ? record!.endContext!.finalTime < sprintAverages[rank]! : null;
|
||||||
|
if (record!.stream.contains("40l")) {
|
||||||
|
closestAverageSprint = sprintAverages.entries.singleWhere((element) => element.value == sprintAverages.values.reduce((a, b) => (a-record!.endContext!.finalTime).abs() < (b -record!.endContext!.finalTime).abs() ? a : b));
|
||||||
|
sprintBetterThanClosestAverage = record!.endContext!.finalTime < closestAverageSprint.value;
|
||||||
|
}else if (record!.stream.contains("blitz")){
|
||||||
|
closestAverageBlitz = blitzAverages.entries.singleWhere((element) => element.value == blitzAverages.values.reduce((a, b) => (a-record!.endContext!.score).abs() < (b -record!.endContext!.score).abs() ? a : b));
|
||||||
|
blitzBetterThanClosestAverage = record!.endContext!.score > closestAverageBlitz.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return LayoutBuilder(
|
||||||
|
builder: (context, constraints) {
|
||||||
|
bool bigScreen = constraints.maxWidth > 768;
|
||||||
|
return SingleChildScrollView(
|
||||||
|
controller: _scrollController,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 8.0),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
if (record!.stream.contains("40l")) Padding(padding: const EdgeInsets.only(right: 8.0),
|
||||||
|
child: Image.asset("res/tetrio_tl_alpha_ranks/${closestAverageSprint.key}.png", height: 96)
|
||||||
|
),
|
||||||
|
if (record!.stream.contains("blitz")) Padding(padding: const EdgeInsets.only(right: 8.0),
|
||||||
|
child: Image.asset("res/tetrio_tl_alpha_ranks/${closestAverageBlitz.key}.png", height: 96)
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
if (record!.stream.contains("40l")) Text(t.sprint, style: const TextStyle(height: 0.1, fontFamily: "Eurostile Round Extended", fontSize: 18)),
|
||||||
|
if (record!.stream.contains("blitz")) Text(t.blitz, style: const TextStyle(height: 0.1, fontFamily: "Eurostile Round Extended", fontSize: 18)),
|
||||||
|
RichText(text: TextSpan(
|
||||||
|
text: record!.stream.contains("40l") ? get40lTime(record!.endContext!.finalTime.inMicroseconds) : NumberFormat.decimalPattern().format(record!.endContext!.score),
|
||||||
|
style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 36 : 32, fontWeight: FontWeight.w500, color: Colors.white),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
RichText(text: TextSpan(
|
||||||
|
text: "",
|
||||||
|
style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, color: Colors.grey),
|
||||||
|
children: [
|
||||||
|
if (record!.stream.contains("40l") && (rank != null && rank != "z")) TextSpan(text: "${t.verdictGeneral(n: readableTimeDifference(record!.endContext!.finalTime, sprintAverages[rank]!), verdict: sprintBetterThanRankAverage??false ? t.verdictBetter : t.verdictWorse, rank: rank!.toUpperCase())}\n", style: TextStyle(
|
||||||
|
color: sprintBetterThanRankAverage??false ? Colors.greenAccent : Colors.redAccent
|
||||||
|
))
|
||||||
|
else if (record!.stream.contains("40l") && (rank == null || rank == "z")) TextSpan(text: "${t.verdictGeneral(n: readableTimeDifference(record!.endContext!.finalTime, closestAverageSprint.value), verdict: sprintBetterThanClosestAverage ? t.verdictBetter : t.verdictWorse, rank: closestAverageSprint.key.toUpperCase())}\n", style: TextStyle(
|
||||||
|
color: sprintBetterThanClosestAverage ? Colors.greenAccent : Colors.redAccent
|
||||||
|
))
|
||||||
|
else if (record!.stream.contains("blitz") && (rank != null && rank != "z")) TextSpan(text: "${t.verdictGeneral(n: readableIntDifference(record!.endContext!.score, blitzAverages[rank]!), verdict: blitzBetterThanRankAverage??false ? t.verdictBetter : t.verdictWorse, rank: rank!.toUpperCase())}\n", style: TextStyle(
|
||||||
|
color: blitzBetterThanRankAverage??false ? Colors.greenAccent : Colors.redAccent
|
||||||
|
))
|
||||||
|
else if (record!.stream.contains("blitz") && (rank == null || rank == "z")) TextSpan(text: "${t.verdictGeneral(n: readableIntDifference(record!.endContext!.score, closestAverageBlitz.value), verdict: blitzBetterThanClosestAverage ? t.verdictBetter : t.verdictWorse, rank: closestAverageBlitz.key.toUpperCase())}\n", style: TextStyle(
|
||||||
|
color: blitzBetterThanClosestAverage ? Colors.greenAccent : Colors.redAccent
|
||||||
|
)),
|
||||||
|
if (record!.rank != null) TextSpan(text: "№${record!.rank}", style: TextStyle(color: getColorOfRank(record!.rank!))),
|
||||||
|
if (record!.rank != null) const TextSpan(text: " • "),
|
||||||
|
TextSpan(text: _dateFormat.format(record!.timestamp!)),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (record!.stream.contains("40l")) Wrap(
|
||||||
|
alignment: WrapAlignment.spaceBetween,
|
||||||
|
spacing: 20,
|
||||||
|
children: [
|
||||||
|
StatCellNum(playerStat: record!.endContext!.piecesPlaced, playerStatLabel: t.statCellNum.pieces, isScreenBig: bigScreen, higherIsBetter: true, smallDecimal: false),
|
||||||
|
StatCellNum(playerStat: record!.endContext!.pps, playerStatLabel: t.statCellNum.pps, fractionDigits: 2, isScreenBig: bigScreen, higherIsBetter: true, smallDecimal: false),
|
||||||
|
StatCellNum(playerStat: record!.endContext!.kpp, playerStatLabel: t.statCellNum.kpp, fractionDigits: 2, isScreenBig: bigScreen, higherIsBetter: true, smallDecimal: false),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (record!.stream.contains("blitz")) Wrap(
|
||||||
|
alignment: WrapAlignment.spaceBetween,
|
||||||
|
crossAxisAlignment: WrapCrossAlignment.start,
|
||||||
|
spacing: 20,
|
||||||
|
children: [
|
||||||
|
StatCellNum(playerStat: record!.endContext!.level, playerStatLabel: t.statCellNum.level, isScreenBig: bigScreen, higherIsBetter: true, smallDecimal: false),
|
||||||
|
StatCellNum(playerStat: record!.endContext!.pps, playerStatLabel: t.statCellNum.pps, fractionDigits: 2, isScreenBig: bigScreen, higherIsBetter: true, smallDecimal: false),
|
||||||
|
StatCellNum(playerStat: record!.endContext!.spp, playerStatLabel: t.statCellNum.spp, fractionDigits: 2, isScreenBig: bigScreen, higherIsBetter: true)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
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")
|
||||||
|
]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _OtherThingy extends StatelessWidget {
|
class _OtherThingy extends StatelessWidget {
|
||||||
final TetrioZen? zen;
|
final TetrioZen? zen;
|
||||||
final String? bio;
|
final String? bio;
|
||||||
final Distinguishment? distinguishment;
|
final Distinguishment? distinguishment;
|
||||||
final News? newsletter;
|
final List<News>? newsletter;
|
||||||
|
|
||||||
/// Widget, that shows players [distinguishment], [bio], [zen] and [newsletter]
|
/// Widget, that shows players [distinguishment], [bio], [zen] and [newsletter]
|
||||||
const _OtherThingy({required this.zen, required this.bio, required this.distinguishment, this.newsletter});
|
const _OtherThingy({required this.zen, required this.bio, required this.distinguishment, this.newsletter});
|
||||||
|
@ -1273,7 +1337,7 @@ class _OtherThingy extends StatelessWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handles [news] entry and returns widget that contains this entry
|
/// Handles [news] entry and returns widget that contains this entry
|
||||||
ListTile getNewsTile(NewsEntry news){
|
ListTile getNewsTile(News news){
|
||||||
Map<String, String> gametypes = {
|
Map<String, String> gametypes = {
|
||||||
"40l": t.sprint,
|
"40l": t.sprint,
|
||||||
"blitz": t.blitz,
|
"blitz": t.blitz,
|
||||||
|
@ -1295,7 +1359,7 @@ class _OtherThingy extends StatelessWidget {
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
subtitle: Text(timestamp(news.timestamp)),
|
subtitle: Text(_dateFormat.format(news.timestamp)),
|
||||||
);
|
);
|
||||||
case "personalbest":
|
case "personalbest":
|
||||||
return ListTile(
|
return ListTile(
|
||||||
|
@ -1310,7 +1374,7 @@ class _OtherThingy extends StatelessWidget {
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
subtitle: Text(timestamp(news.timestamp)),
|
subtitle: Text(_dateFormat.format(news.timestamp)),
|
||||||
leading: Image.asset(
|
leading: Image.asset(
|
||||||
"res/icons/improvement-local.png",
|
"res/icons/improvement-local.png",
|
||||||
height: 48,
|
height: 48,
|
||||||
|
@ -1332,7 +1396,7 @@ class _OtherThingy extends StatelessWidget {
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
subtitle: Text(timestamp(news.timestamp)),
|
subtitle: Text(_dateFormat.format(news.timestamp)),
|
||||||
leading: Image.asset(
|
leading: Image.asset(
|
||||||
"res/tetrio_badges/${news.data["type"]}.png",
|
"res/tetrio_badges/${news.data["type"]}.png",
|
||||||
height: 48,
|
height: 48,
|
||||||
|
@ -1354,7 +1418,7 @@ class _OtherThingy extends StatelessWidget {
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
subtitle: Text(timestamp(news.timestamp)),
|
subtitle: Text(_dateFormat.format(news.timestamp)),
|
||||||
leading: Image.asset(
|
leading: Image.asset(
|
||||||
"res/tetrio_tl_alpha_ranks/${news.data["rank"]}.png",
|
"res/tetrio_tl_alpha_ranks/${news.data["rank"]}.png",
|
||||||
height: 48,
|
height: 48,
|
||||||
|
@ -1375,7 +1439,7 @@ class _OtherThingy extends StatelessWidget {
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
subtitle: Text(timestamp(news.timestamp)),
|
subtitle: Text(_dateFormat.format(news.timestamp)),
|
||||||
leading: Image.asset(
|
leading: Image.asset(
|
||||||
"res/icons/supporter-tag.png",
|
"res/icons/supporter-tag.png",
|
||||||
height: 48,
|
height: 48,
|
||||||
|
@ -1396,7 +1460,7 @@ class _OtherThingy extends StatelessWidget {
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
subtitle: Text(timestamp(news.timestamp)),
|
subtitle: Text(_dateFormat.format(news.timestamp)),
|
||||||
leading: Image.asset(
|
leading: Image.asset(
|
||||||
"res/icons/supporter-tag.png",
|
"res/icons/supporter-tag.png",
|
||||||
height: 48,
|
height: 48,
|
||||||
|
@ -1409,7 +1473,7 @@ class _OtherThingy extends StatelessWidget {
|
||||||
default: // if type is unknown
|
default: // if type is unknown
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: Text(t.newsParts.unknownNews(type: news.type)),
|
title: Text(t.newsParts.unknownNews(type: news.type)),
|
||||||
subtitle: Text(timestamp(news.timestamp)),
|
subtitle: Text(_dateFormat.format(news.timestamp)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1455,7 +1519,7 @@ class _OtherThingy extends StatelessWidget {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (newsletter != null && newsletter!.news.isNotEmpty && showNewsTitle)
|
if (newsletter != null && newsletter!.isNotEmpty && showNewsTitle)
|
||||||
Text(t.news, style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)),
|
Text(t.news, style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
@ -1471,9 +1535,9 @@ class _OtherThingy extends StatelessWidget {
|
||||||
SizedBox(width: 450, child: getShit(context, true, false)),
|
SizedBox(width: 450, child: getShit(context, true, false)),
|
||||||
SizedBox(width: constraints.maxWidth - 450, child: ListView.builder(
|
SizedBox(width: constraints.maxWidth - 450, child: ListView.builder(
|
||||||
physics: const AlwaysScrollableScrollPhysics(),
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
itemCount: newsletter!.news.length+1,
|
itemCount: newsletter!.length+1,
|
||||||
itemBuilder: (BuildContext context, int index) {
|
itemBuilder: (BuildContext context, int index) {
|
||||||
return index == 0 ? Center(child: Text(t.news, style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 42))) : getNewsTile(newsletter!.news[index-1]);
|
return index == 0 ? Center(child: Text(t.news, style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 42))) : getNewsTile(newsletter![index-1]);
|
||||||
}
|
}
|
||||||
))
|
))
|
||||||
]
|
]
|
||||||
|
@ -1482,9 +1546,9 @@ class _OtherThingy extends StatelessWidget {
|
||||||
else {
|
else {
|
||||||
return ListView.builder(
|
return ListView.builder(
|
||||||
physics: const AlwaysScrollableScrollPhysics(),
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
itemCount: newsletter!.news.length+1,
|
itemCount: newsletter!.length+1,
|
||||||
itemBuilder: (BuildContext context, int index) {
|
itemBuilder: (BuildContext context, int index) {
|
||||||
return index == 0 ? getShit(context, bigScreen, true) : getNewsTile(newsletter!.news[index-1]);
|
return index == 0 ? getShit(context, bigScreen, true) : getNewsTile(newsletter![index-1]);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,11 +2,12 @@ import 'dart:io';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:tetra_stats/main.dart' show teto;
|
import 'package:tetra_stats/services/tetrio_crud.dart';
|
||||||
import 'package:tetra_stats/gen/strings.g.dart';
|
import 'package:tetra_stats/gen/strings.g.dart';
|
||||||
import 'package:tetra_stats/views/tl_match_view.dart';
|
import 'package:tetra_stats/views/tl_match_view.dart';
|
||||||
import 'package:window_manager/window_manager.dart';
|
import 'package:window_manager/window_manager.dart';
|
||||||
|
|
||||||
|
final TetrioService teto = TetrioService();
|
||||||
late String oldWindowTitle;
|
late String oldWindowTitle;
|
||||||
|
|
||||||
class MatchesView extends StatefulWidget {
|
class MatchesView extends StatefulWidget {
|
||||||
|
|
|
@ -5,7 +5,7 @@ import 'package:tetra_stats/gen/strings.g.dart';
|
||||||
import 'package:tetra_stats/utils/numers_formats.dart';
|
import 'package:tetra_stats/utils/numers_formats.dart';
|
||||||
import 'package:tetra_stats/views/rank_averages_view.dart';
|
import 'package:tetra_stats/views/rank_averages_view.dart';
|
||||||
import 'package:window_manager/window_manager.dart';
|
import 'package:window_manager/window_manager.dart';
|
||||||
import 'package:tetra_stats/main.dart' show teto;
|
import 'main_view.dart'; // lol
|
||||||
|
|
||||||
class RankAveragesView extends StatefulWidget {
|
class RankAveragesView extends StatefulWidget {
|
||||||
const RankAveragesView({super.key});
|
const RankAveragesView({super.key});
|
||||||
|
|
|
@ -1,19 +1,20 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:tetra_stats/data_objects/tetrio.dart';
|
import 'package:tetra_stats/data_objects/tetrio.dart';
|
||||||
import 'package:tetra_stats/main.dart' show packageInfo, teto, prefs;
|
import 'package:tetra_stats/main.dart' show packageInfo;
|
||||||
import 'package:file_selector/file_selector.dart';
|
import 'package:file_selector/file_selector.dart';
|
||||||
import 'package:file_picker/file_picker.dart';
|
import 'package:file_picker/file_picker.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.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/services/crud_exceptions.dart';
|
import 'package:tetra_stats/services/crud_exceptions.dart';
|
||||||
|
import 'package:tetra_stats/services/tetrio_crud.dart';
|
||||||
import 'package:tetra_stats/utils/open_in_browser.dart';
|
import 'package:tetra_stats/utils/open_in_browser.dart';
|
||||||
import 'package:window_manager/window_manager.dart';
|
import 'package:window_manager/window_manager.dart';
|
||||||
|
|
||||||
late String oldWindowTitle;
|
late String oldWindowTitle;
|
||||||
TextStyle subtitleStyle = const TextStyle(fontFamily: "Eurostile Round Condensed", color: Colors.grey);
|
|
||||||
|
|
||||||
class SettingsView extends StatefulWidget {
|
class SettingsView extends StatefulWidget {
|
||||||
const SettingsView({super.key});
|
const SettingsView({super.key});
|
||||||
|
@ -23,9 +24,10 @@ class SettingsView extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class SettingsState extends State<SettingsView> {
|
class SettingsState extends State<SettingsView> {
|
||||||
|
late SharedPreferences prefs;
|
||||||
|
final TetrioService teto = TetrioService();
|
||||||
String defaultNickname = "Checking...";
|
String defaultNickname = "Checking...";
|
||||||
late bool showPositions;
|
late bool showPositions;
|
||||||
late bool updateInBG;
|
|
||||||
final TextEditingController _playertext = TextEditingController();
|
final TextEditingController _playertext = TextEditingController();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -44,9 +46,9 @@ class SettingsState extends State<SettingsView> {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _getPreferences() {
|
Future<void> _getPreferences() async {
|
||||||
|
prefs = await SharedPreferences.getInstance();
|
||||||
showPositions = prefs.getBool("showPositions") ?? false;
|
showPositions = prefs.getBool("showPositions") ?? false;
|
||||||
updateInBG = prefs.getBool("updateInBG") ?? false;
|
|
||||||
_setDefaultNickname(prefs.getString("player"));
|
_setDefaultNickname(prefs.getString("player"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,7 +93,7 @@ class SettingsState extends State<SettingsView> {
|
||||||
children: [
|
children: [
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(t.exportDB),
|
title: Text(t.exportDB),
|
||||||
subtitle: Text(t.exportDBDescription, style: subtitleStyle),
|
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)));
|
||||||
|
@ -145,7 +147,7 @@ class SettingsState extends State<SettingsView> {
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(t.importDB),
|
title: Text(t.importDB),
|
||||||
subtitle: Text(t.importDBDescription, style: subtitleStyle),
|
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)));
|
||||||
|
@ -197,7 +199,6 @@ class SettingsState extends State<SettingsView> {
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(t.yourID),
|
title: Text(t.yourID),
|
||||||
subtitle: Text(t.yourIDText, style: subtitleStyle),
|
|
||||||
trailing: Text(defaultNickname),
|
trailing: Text(defaultNickname),
|
||||||
onTap: () => showDialog(
|
onTap: () => showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
|
@ -243,7 +244,6 @@ class SettingsState extends State<SettingsView> {
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(t.language),
|
title: Text(t.language),
|
||||||
subtitle: Text("By default, the system language will be selected (if available among Tetra Stats locales, otherwise English)", style: subtitleStyle),
|
|
||||||
trailing: DropdownButton(
|
trailing: DropdownButton(
|
||||||
items: locales,
|
items: locales,
|
||||||
value: LocaleSettings.currentLocale,
|
value: LocaleSettings.currentLocale,
|
||||||
|
@ -261,16 +261,8 @@ class SettingsState extends State<SettingsView> {
|
||||||
subtitle: Text(t.customizationDescription, style: const TextStyle(fontFamily: "Eurostile Round Condensed", color: Colors.grey)),
|
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("/settings/customization");
|
context.go("/customization");
|
||||||
},),
|
},),
|
||||||
ListTile(title: Text(t.updateInBackground),
|
|
||||||
subtitle: Text(t.updateInBackgroundDescription, style: const TextStyle(fontFamily: "Eurostile Round Condensed", color: Colors.grey)),
|
|
||||||
trailing: Switch(value: updateInBG, onChanged: (bool value){
|
|
||||||
prefs.setBool("updateInBG", value);
|
|
||||||
setState(() {
|
|
||||||
updateInBG = value;
|
|
||||||
});
|
|
||||||
}),),
|
|
||||||
ListTile(title: Text(t.lbStats),
|
ListTile(title: Text(t.lbStats),
|
||||||
subtitle: Text(t.lbStatsDescription, style: const TextStyle(fontFamily: "Eurostile Round Condensed", color: Colors.grey)),
|
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){
|
||||||
|
|
|
@ -1,46 +0,0 @@
|
||||||
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/widgets/singleplayer_record.dart';
|
|
||||||
import 'package:tetra_stats/widgets/text_timestamp.dart';
|
|
||||||
|
|
||||||
class SingleplayerRecordView extends StatelessWidget {
|
|
||||||
final RecordSingle record;
|
|
||||||
|
|
||||||
const SingleplayerRecordView({super.key, required this.record});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final t = Translations.of(context);
|
|
||||||
//bool bigScreen = MediaQuery.of(context).size.width >= 368;
|
|
||||||
return Scaffold(
|
|
||||||
backgroundColor: Colors.black,
|
|
||||||
appBar: AppBar(
|
|
||||||
title: Text("${
|
|
||||||
switch (record.endContext.gameType){
|
|
||||||
"40l" => t.sprint,
|
|
||||||
"blitz" => t.blitz,
|
|
||||||
String() => "5000000 Blast",
|
|
||||||
}
|
|
||||||
} ${timestamp(record.timestamp)}"),
|
|
||||||
),
|
|
||||||
body: SafeArea(
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
child: Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Column(
|
|
||||||
children: [
|
|
||||||
SingleplayerRecord(record: record, hideTitle: true),
|
|
||||||
// TODO: Insert replay link here
|
|
||||||
]
|
|
||||||
)
|
|
||||||
],
|
|
||||||
)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,11 +1,12 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
import 'dart:ui';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:intl/intl.dart';
|
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/utils/relative_timestamps.dart';
|
|
||||||
import 'package:tetra_stats/utils/text_shadow.dart';
|
import 'package:tetra_stats/utils/text_shadow.dart';
|
||||||
|
import 'package:tetra_stats/views/main_view.dart';
|
||||||
import 'package:window_manager/window_manager.dart';
|
import 'package:window_manager/window_manager.dart';
|
||||||
|
|
||||||
late String oldWindowTitle;
|
late String oldWindowTitle;
|
||||||
|
@ -38,7 +39,6 @@ class SprintAndBlitzState extends State<SprintAndBlitzView> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final t = Translations.of(context);
|
final t = Translations.of(context);
|
||||||
bool bigScreen = MediaQuery.of(context).size.width >= 368;
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(t.sprintAndBlitsViewTitle),
|
title: Text(t.sprintAndBlitsViewTitle),
|
||||||
|
@ -50,7 +50,6 @@ class SprintAndBlitzState extends State<SprintAndBlitzView> {
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
width: MediaQuery.of(context).size.width,
|
|
||||||
constraints: const BoxConstraints(maxWidth: 600),
|
constraints: const BoxConstraints(maxWidth: 600),
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
|
@ -60,18 +59,18 @@ class SprintAndBlitzState extends State<SprintAndBlitzView> {
|
||||||
Table(
|
Table(
|
||||||
defaultVerticalAlignment: TableCellVerticalAlignment.middle,
|
defaultVerticalAlignment: TableCellVerticalAlignment.middle,
|
||||||
border: TableBorder.all(color: Colors.grey.shade900),
|
border: TableBorder.all(color: Colors.grey.shade900),
|
||||||
columnWidths: const {0: FixedColumnWidth(48)},
|
columnWidths: {0: const FixedColumnWidth(48)},
|
||||||
children: [
|
children: [
|
||||||
TableRow(
|
TableRow(
|
||||||
children: [
|
children: [
|
||||||
Text(t.rank, textAlign: TextAlign.center, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white)),
|
Text(t.rank, textAlign: TextAlign.center, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white)),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(right: 8.0),
|
padding: const EdgeInsets.only(right: 8.0),
|
||||||
child: Text(t.sprint, textAlign: TextAlign.right, style: TextStyle(fontFamily: bigScreen ? "Eurostile Round" : "Eurostile Round Condensed", fontSize: 28, fontWeight: FontWeight.w500, color: Colors.white)),
|
child: Text(t.sprint, textAlign: TextAlign.right, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 28, fontWeight: FontWeight.w500, color: Colors.white)),
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(right: 8.0),
|
padding: const EdgeInsets.only(right: 8.0),
|
||||||
child: Text(t.blitz, textAlign: TextAlign.right, style: TextStyle(fontFamily: bigScreen ? "Eurostile Round" : "Eurostile Round Condensed", fontSize: 28, fontWeight: FontWeight.w500, color: Colors.white)),
|
child: Text(t.blitz, textAlign: TextAlign.right, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 28, fontWeight: FontWeight.w500, color: Colors.white)),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
|
@ -81,11 +80,11 @@ class SprintAndBlitzState extends State<SprintAndBlitzView> {
|
||||||
Container(decoration: BoxDecoration(boxShadow: [BoxShadow(color: Colors.black.withAlpha(132), blurRadius: 32.0, blurStyle: BlurStyle.inner)]), child: Image.asset("res/tetrio_tl_alpha_ranks/${sprintEntry.key}.png", height: 48)),
|
Container(decoration: BoxDecoration(boxShadow: [BoxShadow(color: Colors.black.withAlpha(132), blurRadius: 32.0, blurStyle: BlurStyle.inner)]), child: Image.asset("res/tetrio_tl_alpha_ranks/${sprintEntry.key}.png", height: 48)),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(right: 8.0),
|
padding: const EdgeInsets.only(right: 8.0),
|
||||||
child: Text(get40lTime(sprintEntry.value.inMicroseconds), textAlign: TextAlign.right, style: TextStyle(fontFamily: bigScreen ? "Eurostile Round" : "Eurostile Round Condensed", fontSize: 28, fontWeight: FontWeight.w500, color: Colors.white, shadows: textShadow)),
|
child: Text(get40lTime(sprintEntry.value.inMicroseconds), textAlign: TextAlign.right, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 28, fontWeight: FontWeight.w500, color: Colors.white, shadows: textShadow)),
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(right: 8.0),
|
padding: const EdgeInsets.only(right: 8.0),
|
||||||
child: Text(NumberFormat.decimalPattern().format(blitzAverages[sprintEntry.key]), textAlign: TextAlign.right, style: TextStyle(fontFamily: bigScreen ? "Eurostile Round" : "Eurostile Round Condensed", fontSize: 28, fontWeight: FontWeight.w500, color: Colors.white, shadows: textShadow)),
|
child: Text(NumberFormat.decimalPattern().format(blitzAverages[sprintEntry.key]), textAlign: TextAlign.right, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 28, fontWeight: FontWeight.w500, color: Colors.white, shadows: textShadow)),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
|
@ -4,10 +4,8 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:intl/intl.dart';
|
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/main.dart' show teto;
|
|
||||||
import 'package:tetra_stats/views/mathes_view.dart';
|
import 'package:tetra_stats/views/mathes_view.dart';
|
||||||
import 'package:tetra_stats/views/state_view.dart';
|
import 'package:tetra_stats/views/state_view.dart';
|
||||||
import 'package:tetra_stats/widgets/text_timestamp.dart';
|
|
||||||
import 'package:window_manager/window_manager.dart';
|
import 'package:window_manager/window_manager.dart';
|
||||||
|
|
||||||
class StatesView extends StatefulWidget {
|
class StatesView extends StatefulWidget {
|
||||||
|
@ -39,6 +37,7 @@ class StatesState extends State<StatesView> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final t = Translations.of(context);
|
final t = Translations.of(context);
|
||||||
|
final DateFormat dateFormat = DateFormat.yMMMd(LocaleSettings.currentLocale.languageCode).add_Hms();
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(t.statesViewTitle(number: widget.states.length, nickname: widget.states.last.username.toUpperCase())),
|
title: Text(t.statesViewTitle(number: widget.states.length, nickname: widget.states.last.username.toUpperCase())),
|
||||||
|
@ -60,14 +59,14 @@ class StatesState extends State<StatesView> {
|
||||||
itemCount: widget.states.length,
|
itemCount: widget.states.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: Text(timestamp(widget.states[index].state)),
|
title: Text(dateFormat.format(widget.states[index].state)),
|
||||||
subtitle: Text(t.statesViewEntry(level: widget.states[index].level.toStringAsFixed(2), gameTime: widget.states[index].gameTime, friends: widget.states[index].friendCount, rd: NumberFormat.compact().format(widget.states[index].tlSeason1.rd))),
|
subtitle: Text(t.statesViewEntry(level: widget.states[index].level.toStringAsFixed(2), gameTime: widget.states[index].gameTime, friends: widget.states[index].friendCount, rd: NumberFormat.compact().format(widget.states[index].tlSeason1.rd))),
|
||||||
trailing: IconButton(
|
trailing: IconButton(
|
||||||
icon: const Icon(Icons.delete_forever),
|
icon: const Icon(Icons.delete_forever),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
DateTime nn = widget.states[index].state;
|
DateTime nn = widget.states[index].state;
|
||||||
teto.deleteState(widget.states[index]).then((value) => setState(() {
|
teto.deleteState(widget.states[index]).then((value) => setState(() {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.stateRemoved(date: timestamp(nn)))));
|
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.stateRemoved(date: dateFormat.format(nn)))));
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
|
@ -174,8 +174,8 @@ class TLLeaderboardState extends State<TLLeaderboardView> {
|
||||||
prototypeItem: ListTile(
|
prototypeItem: ListTile(
|
||||||
leading: Text("0", style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 28 : 24, height: 0.9)),
|
leading: Text("0", style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 28 : 24, height: 0.9)),
|
||||||
title: Text("ehhh...", style: TextStyle(fontFamily: bigScreen ? "Eurostile Round Extended" : "Eurostile Round", height: 0.9)),
|
title: Text("ehhh...", style: TextStyle(fontFamily: bigScreen ? "Eurostile Round Extended" : "Eurostile Round", height: 0.9)),
|
||||||
trailing: SizedBox(height: bigScreen ? 48 : 36, width: 1,),
|
trailing: Container(height: bigScreen ? 48 : 36, width: 1,),
|
||||||
subtitle: const Text("eh..."),
|
subtitle: Text("eh..."),
|
||||||
),
|
),
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
return ListTile(
|
return ListTile(
|
||||||
|
|
|
@ -1,14 +1,12 @@
|
||||||
// ignore_for_file: use_build_context_synchronously, type_literal_in_constant_pattern
|
// ignore_for_file: use_build_context_synchronously
|
||||||
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
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/utils/relative_timestamps.dart';
|
|
||||||
import 'package:tetra_stats/views/compare_view.dart' show CompareThingy, CompareBoolThingy;
|
import 'package:tetra_stats/views/compare_view.dart' show CompareThingy, CompareBoolThingy;
|
||||||
import 'package:tetra_stats/widgets/list_tile_trailing_stats.dart';
|
import 'package:tetra_stats/widgets/list_tile_trailing_stats.dart';
|
||||||
import 'package:tetra_stats/widgets/text_timestamp.dart';
|
|
||||||
import 'package:tetra_stats/widgets/vs_graphs.dart';
|
import 'package:tetra_stats/widgets/vs_graphs.dart';
|
||||||
import 'package:tetra_stats/main.dart' show teto;
|
import 'main_view.dart' show teto, secs;
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
@ -16,8 +14,11 @@ 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/utils/open_in_browser.dart';
|
import 'package:tetra_stats/utils/open_in_browser.dart';
|
||||||
import 'package:window_manager/window_manager.dart';
|
import 'package:window_manager/window_manager.dart';
|
||||||
|
// ignore: avoid_web_libraries_in_flutter
|
||||||
|
// import 'dart:html' show AnchorElement, document;
|
||||||
|
|
||||||
|
|
||||||
|
final DateFormat dateFormat = DateFormat.yMMMd(LocaleSettings.currentLocale.languageCode).add_Hms();
|
||||||
int roundSelector = -1; // -1 = match averages, otherwise round number-1
|
int roundSelector = -1; // -1 = match averages, otherwise round number-1
|
||||||
List<DropdownMenuItem> rounds = []; // index zero will be match stats
|
List<DropdownMenuItem> rounds = []; // index zero will be match stats
|
||||||
bool timeWeightedStatsAvaliable = true;
|
bool timeWeightedStatsAvaliable = true;
|
||||||
|
@ -48,7 +49,7 @@ class TlMatchResultState extends State<TlMatchResultView> {
|
||||||
replayData = teto.analyzeReplay(widget.record.replayId, widget.record.replayAvalable);
|
replayData = teto.analyzeReplay(widget.record.replayId, widget.record.replayAvalable);
|
||||||
if (!kIsWeb && !Platform.isAndroid && !Platform.isIOS){
|
if (!kIsWeb && !Platform.isAndroid && !Platform.isIOS){
|
||||||
windowManager.getTitle().then((value) => oldWindowTitle = value);
|
windowManager.getTitle().then((value) => oldWindowTitle = value);
|
||||||
windowManager.setTitle("Tetra Stats: ${widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).username.toUpperCase()} ${t.vs} ${widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).username.toUpperCase()} ${t.inTLmatch} ${timestamp(widget.record.timestamp)}");
|
windowManager.setTitle("Tetra Stats: ${widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).username.toUpperCase()} ${t.vs} ${widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).username.toUpperCase()} ${t.inTLmatch} ${dateFormat.format(widget.record.timestamp)}");
|
||||||
}
|
}
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
@ -707,7 +708,7 @@ class TlMatchResultState extends State<TlMatchResultView> {
|
||||||
final t = Translations.of(context);
|
final t = Translations.of(context);
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text("${widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).username.toUpperCase()} ${t.vs} ${widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).username.toUpperCase()} ${t.inTLmatch} ${timestamp(widget.record.timestamp)}"),
|
title: Text("${widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).username.toUpperCase()} ${t.vs} ${widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).username.toUpperCase()} ${t.inTLmatch} ${dateFormat.format(widget.record.timestamp)}"),
|
||||||
actions: [
|
actions: [
|
||||||
PopupMenuButton(
|
PopupMenuButton(
|
||||||
enabled: widget.record.replayAvalable,
|
enabled: widget.record.replayAvalable,
|
||||||
|
|
|
@ -4,12 +4,12 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:intl/intl.dart';
|
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/main.dart' show teto;
|
import 'package:tetra_stats/services/tetrio_crud.dart';
|
||||||
import 'package:tetra_stats/utils/filesizes_converter.dart';
|
import 'package:tetra_stats/utils/filesizes_converter.dart';
|
||||||
import 'package:tetra_stats/views/states_view.dart';
|
import 'package:tetra_stats/views/states_view.dart';
|
||||||
import 'package:tetra_stats/widgets/text_timestamp.dart';
|
|
||||||
import 'package:window_manager/window_manager.dart';
|
import 'package:window_manager/window_manager.dart';
|
||||||
|
|
||||||
|
final TetrioService teto = TetrioService();
|
||||||
late String oldWindowTitle;
|
late String oldWindowTitle;
|
||||||
|
|
||||||
class TrackedPlayersView extends StatefulWidget {
|
class TrackedPlayersView extends StatefulWidget {
|
||||||
|
@ -38,6 +38,7 @@ class TrackedPlayersState extends State<TrackedPlayersView> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final t = Translations.of(context);
|
final t = Translations.of(context);
|
||||||
|
final DateFormat dateFormat = DateFormat.yMMMd(LocaleSettings.currentLocale.languageCode).add_Hms();
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(t.trackedPlayersViewTitle),
|
title: Text(t.trackedPlayersViewTitle),
|
||||||
|
@ -109,7 +110,7 @@ class TrackedPlayersState extends State<TrackedPlayersView> {
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: Text(t.trackedPlayersEntry(nickname: allPlayers[keys[index]]!.last.username, numberOfStates: allPlayers[keys[index]]!.length)),
|
title: Text(t.trackedPlayersEntry(nickname: allPlayers[keys[index]]!.last.username, numberOfStates: allPlayers[keys[index]]!.length)),
|
||||||
subtitle: Text(t.trackedPlayersDescription(firstStateDate: timestamp(allPlayers[keys[index]]!.first.state), lastStateDate: timestamp(allPlayers[keys[index]]!.last.state))),
|
subtitle: Text(t.trackedPlayersDescription(firstStateDate: dateFormat.format(allPlayers[keys[index]]!.first.state), lastStateDate: dateFormat.format(allPlayers[keys[index]]!.last.state))),
|
||||||
trailing: IconButton(
|
trailing: IconButton(
|
||||||
icon: const Icon(Icons.delete_forever),
|
icon: const Icon(Icons.delete_forever),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
// ignore_for_file: curly_braces_in_flow_control_structures
|
|
||||||
|
|
||||||
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/utils/numers_formats.dart';
|
import 'package:tetra_stats/utils/numers_formats.dart';
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
// ignore_for_file: curly_braces_in_flow_control_structures
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:syncfusion_flutter_gauges/gauges.dart';
|
import 'package:syncfusion_flutter_gauges/gauges.dart';
|
||||||
import 'package:tetra_stats/data_objects/tetrio.dart';
|
import 'package:tetra_stats/data_objects/tetrio.dart';
|
||||||
|
|
|
@ -1,269 +1,10 @@
|
||||||
import 'dart:math';
|
|
||||||
|
|
||||||
import 'package:fl_chart/fl_chart.dart';
|
import 'package:fl_chart/fl_chart.dart';
|
||||||
import 'package:fl_chart/src/chart/radar_chart/radar_chart_painter.dart';
|
|
||||||
import 'package:fl_chart/src/chart/radar_chart/radar_chart_renderer.dart';
|
|
||||||
import 'package:fl_chart/src/chart/base/base_chart/base_chart_painter.dart';
|
|
||||||
import 'package:fl_chart/src/utils/canvas_wrapper.dart';
|
|
||||||
import 'package:fl_chart/src/utils/utils.dart';
|
|
||||||
import 'package:tetra_stats/main.dart' show prefs;
|
|
||||||
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/gen/strings.g.dart';
|
||||||
import 'package:tetra_stats/utils/numers_formats.dart';
|
import 'package:tetra_stats/utils/numers_formats.dart';
|
||||||
|
|
||||||
class MyRadarChartPainter extends RadarChartPainter{
|
|
||||||
MyRadarChartPainter() : super() {
|
|
||||||
_backgroundPaint = Paint()
|
|
||||||
..style = PaintingStyle.fill
|
|
||||||
..isAntiAlias = true;
|
|
||||||
|
|
||||||
_borderPaint = Paint()..style = PaintingStyle.stroke;
|
|
||||||
|
|
||||||
_gridPaint = Paint()..style = PaintingStyle.stroke;
|
|
||||||
|
|
||||||
_tickPaint = Paint()..style = PaintingStyle.stroke;
|
|
||||||
|
|
||||||
_graphPaint = Paint();
|
|
||||||
_graphBorderPaint = Paint();
|
|
||||||
_graphPointPaint = Paint();
|
|
||||||
_ticksTextPaint = TextPainter();
|
|
||||||
_titleTextPaint = TextPainter();
|
|
||||||
sheetbotRadarGraphs = prefs.getBool("sheetbotRadarGraphs")??false;
|
|
||||||
}
|
|
||||||
late Paint _borderPaint;
|
|
||||||
late Paint _backgroundPaint;
|
|
||||||
late Paint _gridPaint;
|
|
||||||
late Paint _tickPaint;
|
|
||||||
late Paint _graphPaint;
|
|
||||||
late Paint _graphBorderPaint;
|
|
||||||
late Paint _graphPointPaint;
|
|
||||||
|
|
||||||
late TextPainter _ticksTextPaint;
|
|
||||||
late TextPainter _titleTextPaint;
|
|
||||||
|
|
||||||
late bool sheetbotRadarGraphs;
|
|
||||||
|
|
||||||
@override
|
|
||||||
double getChartCenterValue(RadarChartData data) {
|
|
||||||
final dataSetMaxValue = sheetbotRadarGraphs ? max(data.maxEntry.value, data.minEntry.value.abs()) : data.maxEntry.value;
|
|
||||||
final dataSetMinValue = data.minEntry.value;
|
|
||||||
final tickSpace = getSpaceBetweenTicks(data);
|
|
||||||
final centerValue = (dataSetMinValue < 0 && sheetbotRadarGraphs) ? 0.0 : dataSetMinValue;
|
|
||||||
|
|
||||||
return dataSetMaxValue == dataSetMinValue
|
|
||||||
? getDefaultChartCenterValue()
|
|
||||||
: centerValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
double getSpaceBetweenTicks(RadarChartData data) {
|
|
||||||
final defaultCenterValue = getDefaultChartCenterValue();
|
|
||||||
final dataSetMaxValue = sheetbotRadarGraphs ? max(data.maxEntry.value, data.minEntry.value.abs()) : data.maxEntry.value;
|
|
||||||
final dataSetMinValue = (data.minEntry.value < 0 && sheetbotRadarGraphs) ? 0.0 : data.minEntry.value;
|
|
||||||
final tickSpace = sheetbotRadarGraphs ? dataSetMaxValue / data.tickCount : (dataSetMaxValue - dataSetMinValue) / data.tickCount;
|
|
||||||
final defaultTickSpace =
|
|
||||||
(dataSetMaxValue - defaultCenterValue) / (data.tickCount + 1);
|
|
||||||
|
|
||||||
return dataSetMaxValue == dataSetMinValue ? defaultTickSpace : tickSpace;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
double getScaledPoint(RadarEntry point, double radius, RadarChartData data) {
|
|
||||||
final centerValue = getChartCenterValue(data);
|
|
||||||
final distanceFromPointToCenter = point.value - centerValue;
|
|
||||||
final distanceFromMaxToCenter = max(data.maxEntry.value, data.minEntry.value.abs()) - centerValue;
|
|
||||||
|
|
||||||
if (distanceFromMaxToCenter == 0) {
|
|
||||||
return radius * distanceFromPointToCenter / 0.001;
|
|
||||||
}
|
|
||||||
|
|
||||||
return radius * distanceFromPointToCenter / distanceFromMaxToCenter;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
double getFirstTickValue(RadarChartData data) {
|
|
||||||
final defaultCenterValue = getDefaultChartCenterValue();
|
|
||||||
final dataSetMaxValue = sheetbotRadarGraphs ? max(data.maxEntry.value, data.minEntry.value.abs()) : data.maxEntry.value;
|
|
||||||
final dataSetMinValue = (data.minEntry.value < 0 && sheetbotRadarGraphs) ? 0.0 : data.minEntry.value;
|
|
||||||
|
|
||||||
return dataSetMaxValue == dataSetMinValue
|
|
||||||
? (dataSetMaxValue - defaultCenterValue) / (data.tickCount + 1) +
|
|
||||||
defaultCenterValue
|
|
||||||
: dataSetMinValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void drawTicks(
|
|
||||||
BuildContext context,
|
|
||||||
CanvasWrapper canvasWrapper,
|
|
||||||
PaintHolder<RadarChartData> holder,
|
|
||||||
) {
|
|
||||||
final data = holder.data;
|
|
||||||
final size = canvasWrapper.size;
|
|
||||||
|
|
||||||
final centerX = radarCenterX(size);
|
|
||||||
final centerY = radarCenterY(size);
|
|
||||||
final centerOffset = Offset(centerX, centerY);
|
|
||||||
|
|
||||||
/// controls Radar chart size
|
|
||||||
final radius = radarRadius(size);
|
|
||||||
|
|
||||||
_backgroundPaint.color = data.radarBackgroundColor;
|
|
||||||
|
|
||||||
_borderPaint
|
|
||||||
..color = data.radarBorderData.color
|
|
||||||
..strokeWidth = data.radarBorderData.width;
|
|
||||||
|
|
||||||
if (data.radarShape == RadarShape.circle) {
|
|
||||||
/// draw radar background
|
|
||||||
canvasWrapper
|
|
||||||
..drawCircle(centerOffset, radius, _backgroundPaint)
|
|
||||||
|
|
||||||
/// draw radar border
|
|
||||||
..drawCircle(centerOffset, radius, _borderPaint);
|
|
||||||
} else {
|
|
||||||
final path =
|
|
||||||
_generatePolygonPath(centerX, centerY, radius, data.titleCount);
|
|
||||||
|
|
||||||
/// draw radar background
|
|
||||||
canvasWrapper
|
|
||||||
..drawPath(path, _backgroundPaint)
|
|
||||||
|
|
||||||
/// draw radar border
|
|
||||||
..drawPath(path, _borderPaint);
|
|
||||||
}
|
|
||||||
|
|
||||||
final tickSpace = getSpaceBetweenTicks(data);
|
|
||||||
final ticks = <double>[];
|
|
||||||
var tickValue = getFirstTickValue(data);
|
|
||||||
|
|
||||||
for (var i = 0; i <= data.tickCount; i++) {
|
|
||||||
ticks.add(tickValue);
|
|
||||||
tickValue += tickSpace;
|
|
||||||
}
|
|
||||||
|
|
||||||
final tickDistance = radius / (ticks.length-1);
|
|
||||||
|
|
||||||
_tickPaint
|
|
||||||
..color = data.tickBorderData.color
|
|
||||||
..strokeWidth = data.tickBorderData.width;
|
|
||||||
|
|
||||||
/// draw radar ticks
|
|
||||||
ticks.sublist(1, ticks.length).asMap().forEach(
|
|
||||||
(index, tick) {
|
|
||||||
final tickRadius = tickDistance * (index + 1);
|
|
||||||
if (data.radarShape == RadarShape.circle) {
|
|
||||||
canvasWrapper.drawCircle(centerOffset, tickRadius, _tickPaint);
|
|
||||||
} else {
|
|
||||||
canvasWrapper.drawPath(
|
|
||||||
_generatePolygonPath(centerX, centerY, tickRadius, data.titleCount),
|
|
||||||
_tickPaint,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
_ticksTextPaint
|
|
||||||
..text = TextSpan(
|
|
||||||
text: percentage.format(tick),
|
|
||||||
style: Utils().getThemeAwareTextStyle(context, data.ticksTextStyle),
|
|
||||||
)
|
|
||||||
..textDirection = TextDirection.ltr
|
|
||||||
..layout(maxWidth: size.width);
|
|
||||||
canvasWrapper.drawText(
|
|
||||||
_ticksTextPaint,
|
|
||||||
Offset(centerX + 5, centerY - tickRadius - _ticksTextPaint.height/2),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Path _generatePolygonPath(
|
|
||||||
double centerX,
|
|
||||||
double centerY,
|
|
||||||
double radius,
|
|
||||||
int count,
|
|
||||||
) {
|
|
||||||
final path = Path()..moveTo(centerX, centerY - radius);
|
|
||||||
final angle = (2 * pi) / count;
|
|
||||||
for (var index = 0; index < count; index++) {
|
|
||||||
final xAngle = cos(angle * index - pi / 2);
|
|
||||||
final yAngle = sin(angle * index - pi / 2);
|
|
||||||
path.lineTo(centerX + radius * xAngle, centerY + radius * yAngle);
|
|
||||||
}
|
|
||||||
path.lineTo(centerX, centerY - radius);
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class MyRadarChartLeaf extends RadarChartLeaf{
|
|
||||||
MyRadarChartLeaf({required super.data, required super.targetData});
|
|
||||||
|
|
||||||
@override
|
|
||||||
RenderRadarChart createRenderObject(BuildContext context) => MyRenderRadarChart(
|
|
||||||
context,
|
|
||||||
data,
|
|
||||||
targetData,
|
|
||||||
MediaQuery.of(context).textScaler,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class MyRenderRadarChart extends RenderRadarChart{
|
|
||||||
MyRenderRadarChart(super.context, super.data, super.targetData, super.textScaler);
|
|
||||||
|
|
||||||
@override
|
|
||||||
RadarChartPainter painter = MyRadarChartPainter();
|
|
||||||
}
|
|
||||||
|
|
||||||
class MyRadarChart extends ImplicitlyAnimatedWidget {
|
|
||||||
const MyRadarChart(
|
|
||||||
this.data, {
|
|
||||||
super.key,
|
|
||||||
Duration swapAnimationDuration = const Duration(milliseconds: 150),
|
|
||||||
Curve swapAnimationCurve = Curves.linear,
|
|
||||||
}) : super(
|
|
||||||
duration: swapAnimationDuration,
|
|
||||||
curve: swapAnimationCurve,
|
|
||||||
);
|
|
||||||
|
|
||||||
/// Determines how the [RadarChart] should be look like.
|
|
||||||
final RadarChartData data;
|
|
||||||
|
|
||||||
@override
|
|
||||||
RadarChartState createState() => RadarChartState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class RadarChartState extends AnimatedWidgetBaseState<MyRadarChart> {
|
|
||||||
/// we handle under the hood animations (implicit animations) via this tween,
|
|
||||||
/// it lerps between the old [RadarChartData] to the new one.
|
|
||||||
RadarChartDataTween? _radarChartDataTween;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final showingData = _getDate();
|
|
||||||
|
|
||||||
return MyRadarChartLeaf(
|
|
||||||
data: _radarChartDataTween!.evaluate(animation),
|
|
||||||
targetData: showingData,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
RadarChartData _getDate() {
|
|
||||||
return widget.data;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void forEachTween(TweenVisitor<dynamic> visitor) {
|
|
||||||
_radarChartDataTween = visitor(
|
|
||||||
_radarChartDataTween,
|
|
||||||
widget.data,
|
|
||||||
(dynamic value) =>
|
|
||||||
RadarChartDataTween(begin: value as RadarChartData, end: widget.data),
|
|
||||||
) as RadarChartDataTween?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Graphs extends StatelessWidget{
|
class Graphs extends StatelessWidget{
|
||||||
|
|
||||||
const Graphs(
|
const Graphs(
|
||||||
this.apm,
|
this.apm,
|
||||||
this.pps,
|
this.pps,
|
||||||
|
@ -296,7 +37,7 @@ class Graphs extends StatelessWidget{
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: 310,
|
height: 310,
|
||||||
width: 310,
|
width: 310,
|
||||||
child: MyRadarChart(
|
child: RadarChart(
|
||||||
RadarChartData(
|
RadarChartData(
|
||||||
radarShape: RadarShape.polygon,
|
radarShape: RadarShape.polygon,
|
||||||
tickCount: 4,
|
tickCount: 4,
|
||||||
|
@ -332,8 +73,6 @@ class Graphs extends StatelessWidget{
|
||||||
},
|
},
|
||||||
dataSets: [
|
dataSets: [
|
||||||
RadarDataSet(
|
RadarDataSet(
|
||||||
fillColor: Theme.of(context).colorScheme.primary.withAlpha(100),
|
|
||||||
borderColor: Theme.of(context).colorScheme.primary,
|
|
||||||
dataEntries: [
|
dataEntries: [
|
||||||
RadarEntry(value: apm * apmWeight),
|
RadarEntry(value: apm * apmWeight),
|
||||||
RadarEntry(value: pps * ppsWeight),
|
RadarEntry(value: pps * ppsWeight),
|
||||||
|
@ -375,7 +114,7 @@ class Graphs extends StatelessWidget{
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: 310,
|
height: 310,
|
||||||
width: 310,
|
width: 310,
|
||||||
child: MyRadarChart(
|
child: RadarChart(
|
||||||
RadarChartData(
|
RadarChartData(
|
||||||
radarShape: RadarShape.polygon,
|
radarShape: RadarShape.polygon,
|
||||||
tickCount: 4,
|
tickCount: 4,
|
||||||
|
@ -401,8 +140,6 @@ class Graphs extends StatelessWidget{
|
||||||
},
|
},
|
||||||
dataSets: [
|
dataSets: [
|
||||||
RadarDataSet(
|
RadarDataSet(
|
||||||
fillColor: Theme.of(context).colorScheme.primary.withAlpha(100),
|
|
||||||
borderColor: Theme.of(context).colorScheme.primary,
|
|
||||||
dataEntries: [
|
dataEntries: [
|
||||||
RadarEntry(value: playstyle.opener),
|
RadarEntry(value: playstyle.opener),
|
||||||
RadarEntry(value: playstyle.stride),
|
RadarEntry(value: playstyle.stride),
|
||||||
|
@ -432,7 +169,7 @@ class Graphs extends StatelessWidget{
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: 310,
|
height: 310,
|
||||||
width: 310,
|
width: 310,
|
||||||
child: MyRadarChart(
|
child: RadarChart(
|
||||||
RadarChartData(
|
RadarChartData(
|
||||||
radarShape: RadarShape.polygon,
|
radarShape: RadarShape.polygon,
|
||||||
tickCount: 4,
|
tickCount: 4,
|
||||||
|
@ -458,8 +195,6 @@ class Graphs extends StatelessWidget{
|
||||||
},
|
},
|
||||||
dataSets: [
|
dataSets: [
|
||||||
RadarDataSet(
|
RadarDataSet(
|
||||||
fillColor: Theme.of(context).colorScheme.primary.withAlpha(100),
|
|
||||||
borderColor: Theme.of(context).colorScheme.primary,
|
|
||||||
dataEntries: [
|
dataEntries: [
|
||||||
RadarEntry(value: attack),
|
RadarEntry(value: attack),
|
||||||
RadarEntry(value: speed),
|
RadarEntry(value: speed),
|
||||||
|
|
|
@ -13,14 +13,14 @@ class TrailingStats extends StatelessWidget{
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
const TextStyle style = TextStyle(height: 1.1, fontWeight: FontWeight.w100, fontSize: 13);
|
const TextStyle style = TextStyle(height: 1.1, fontWeight: FontWeight.w100);
|
||||||
return Table(
|
return Table(
|
||||||
defaultColumnWidth: const IntrinsicColumnWidth(),
|
defaultColumnWidth: const IntrinsicColumnWidth(),
|
||||||
defaultVerticalAlignment: TableCellVerticalAlignment.baseline,
|
defaultVerticalAlignment: TableCellVerticalAlignment.baseline,
|
||||||
textBaseline: TextBaseline.alphabetic,
|
textBaseline: TextBaseline.alphabetic,
|
||||||
columnWidths: const {
|
columnWidths: const {
|
||||||
0: FixedColumnWidth(48),
|
0: FixedColumnWidth(42),
|
||||||
2: FixedColumnWidth(48),
|
2: FixedColumnWidth(42),
|
||||||
},
|
},
|
||||||
children: [
|
children: [
|
||||||
TableRow(children: [Text(f2.format(yourAPM), textAlign: TextAlign.right, style: style), const Text(" :", style: style), Text(f2.format(notyourAPM), textAlign: TextAlign.right, style: style), const Text(" APM", textAlign: TextAlign.right, style: style)]),
|
TableRow(children: [Text(f2.format(yourAPM), textAlign: TextAlign.right, style: style), const Text(" :", style: style), Text(f2.format(notyourAPM), textAlign: TextAlign.right, style: style), const Text(" APM", textAlign: TextAlign.right, style: style)]),
|
||||||
|
|
|
@ -1,50 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:intl/intl.dart';
|
|
||||||
import 'package:tetra_stats/data_objects/tetrio.dart';
|
|
||||||
import 'package:tetra_stats/gen/strings.g.dart';
|
|
||||||
import 'package:tetra_stats/utils/relative_timestamps.dart';
|
|
||||||
import 'package:tetra_stats/utils/text_shadow.dart';
|
|
||||||
import 'package:tetra_stats/views/singleplayer_record_view.dart';
|
|
||||||
import 'package:tetra_stats/widgets/sp_trailing_stats.dart';
|
|
||||||
import 'package:tetra_stats/widgets/text_timestamp.dart';
|
|
||||||
|
|
||||||
class RecentSingleplayerGames extends StatelessWidget{
|
|
||||||
final SingleplayerStream recent;
|
|
||||||
final bool hideTitle;
|
|
||||||
|
|
||||||
const RecentSingleplayerGames({required this.recent, this.hideTitle = false, super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Column(
|
|
||||||
children: [
|
|
||||||
if (!hideTitle) Padding(
|
|
||||||
padding: const EdgeInsets.only(bottom: 8.0),
|
|
||||||
child: Text(t.recent, style: const TextStyle(height: 0.1, fontFamily: "Eurostile Round Extended", fontSize: 18)),
|
|
||||||
),
|
|
||||||
for(RecordSingle record in recent.records) ListTile(
|
|
||||||
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (context) => SingleplayerRecordView(record: record))),
|
|
||||||
leading: Text(
|
|
||||||
switch (record.endContext.gameType){
|
|
||||||
"40l" => "40L",
|
|
||||||
"blitz" => "BLZ",
|
|
||||||
"5mblast" => "5MB",
|
|
||||||
String() => "huh",
|
|
||||||
},
|
|
||||||
style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 28, shadows: textShadow, height: 0.9)
|
|
||||||
),
|
|
||||||
title: Text(
|
|
||||||
switch (record.endContext.gameType){
|
|
||||||
"40l" => get40lTime(record.endContext.finalTime.inMicroseconds),
|
|
||||||
"blitz" => t.blitzScore(p: NumberFormat.decimalPattern().format(record.endContext.score)),
|
|
||||||
"5mblast" => get40lTime(record.endContext.finalTime.inMicroseconds),
|
|
||||||
String() => "huh",
|
|
||||||
},
|
|
||||||
style: const TextStyle(fontSize: 18)),
|
|
||||||
subtitle: Text(timestamp(record.timestamp), style: const TextStyle(color: Colors.grey, height: 0.85)),
|
|
||||||
trailing: SpTrailingStats(record.endContext)
|
|
||||||
)
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,161 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:intl/intl.dart';
|
|
||||||
import 'package:tetra_stats/data_objects/tetrio.dart';
|
|
||||||
import 'package:tetra_stats/gen/strings.g.dart';
|
|
||||||
import 'package:tetra_stats/utils/numers_formats.dart';
|
|
||||||
import 'package:tetra_stats/utils/open_in_browser.dart';
|
|
||||||
import 'package:tetra_stats/utils/relative_timestamps.dart';
|
|
||||||
import 'package:tetra_stats/utils/text_shadow.dart';
|
|
||||||
import 'package:tetra_stats/views/singleplayer_record_view.dart';
|
|
||||||
import 'package:tetra_stats/widgets/finesse_thingy.dart';
|
|
||||||
import 'package:tetra_stats/widgets/lineclears_thingy.dart';
|
|
||||||
import 'package:tetra_stats/widgets/sp_trailing_stats.dart';
|
|
||||||
import 'package:tetra_stats/widgets/stat_sell_num.dart';
|
|
||||||
import 'package:tetra_stats/widgets/text_timestamp.dart';
|
|
||||||
|
|
||||||
class SingleplayerRecord extends StatelessWidget {
|
|
||||||
final RecordSingle? record;
|
|
||||||
final SingleplayerStream? stream;
|
|
||||||
final String? rank;
|
|
||||||
final bool hideTitle;
|
|
||||||
|
|
||||||
/// Widget that displays data from [record]
|
|
||||||
const SingleplayerRecord({super.key, required this.record, this.stream, this.rank, this.hideTitle = false});
|
|
||||||
|
|
||||||
Color getColorOfRank(int rank){
|
|
||||||
if (rank == 1) return Colors.yellowAccent;
|
|
||||||
if (rank == 2) return Colors.blueGrey;
|
|
||||||
if (rank == 3) return Colors.brown[400]!;
|
|
||||||
if (rank <= 9) return Colors.blueAccent;
|
|
||||||
if (rank <= 99) return Colors.greenAccent;
|
|
||||||
return Colors.grey;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
if (record == null) return Center(child: Text(t.noRecord, textAlign: TextAlign.center, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 28)));
|
|
||||||
late MapEntry closestAverageBlitz;
|
|
||||||
late bool blitzBetterThanClosestAverage;
|
|
||||||
bool? blitzBetterThanRankAverage = (rank != null && rank != "z") ? record!.endContext.score > blitzAverages[rank]! : null;
|
|
||||||
late MapEntry closestAverageSprint;
|
|
||||||
late bool sprintBetterThanClosestAverage;
|
|
||||||
bool? sprintBetterThanRankAverage = (rank != null && rank != "z") ? record!.endContext.finalTime < sprintAverages[rank]! : null;
|
|
||||||
if (record!.endContext.gameType == "40l") {
|
|
||||||
closestAverageSprint = sprintAverages.entries.singleWhere((element) => element.value == sprintAverages.values.reduce((a, b) => (a-record!.endContext.finalTime).abs() < (b -record!.endContext.finalTime).abs() ? a : b));
|
|
||||||
sprintBetterThanClosestAverage = record!.endContext.finalTime < closestAverageSprint.value;
|
|
||||||
}else if (record!.endContext.gameType == "blitz"){
|
|
||||||
closestAverageBlitz = blitzAverages.entries.singleWhere((element) => element.value == blitzAverages.values.reduce((a, b) => (a-record!.endContext.score).abs() < (b -record!.endContext.score).abs() ? a : b));
|
|
||||||
blitzBetterThanClosestAverage = record!.endContext.score > closestAverageBlitz.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return LayoutBuilder(
|
|
||||||
builder: (context, constraints) {
|
|
||||||
bool bigScreen = constraints.maxWidth > 768;
|
|
||||||
return SingleChildScrollView(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.only(top: 8.0),
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
if (record!.endContext.gameType == "40l") Padding(padding: const EdgeInsets.only(right: 8.0),
|
|
||||||
child: Image.asset("res/tetrio_tl_alpha_ranks/${closestAverageSprint.key}.png", height: 96)
|
|
||||||
),
|
|
||||||
if (record!.endContext.gameType == "blitz") Padding(padding: const EdgeInsets.only(right: 8.0),
|
|
||||||
child: Image.asset("res/tetrio_tl_alpha_ranks/${closestAverageBlitz.key}.png", height: 96)
|
|
||||||
),
|
|
||||||
Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
if (record!.endContext.gameType == "40l" && !hideTitle) Text(t.sprint, style: const TextStyle(height: 0.1, fontFamily: "Eurostile Round Extended", fontSize: 18)),
|
|
||||||
if (record!.endContext.gameType == "blitz" && !hideTitle) Text(t.blitz, style: const TextStyle(height: 0.1, fontFamily: "Eurostile Round Extended", fontSize: 18)),
|
|
||||||
RichText(text: TextSpan(
|
|
||||||
text: record!.endContext.gameType == "40l" ? get40lTime(record!.endContext.finalTime.inMicroseconds) : NumberFormat.decimalPattern().format(record!.endContext.score),
|
|
||||||
style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 36 : 32, fontWeight: FontWeight.w500, color: Colors.white),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
RichText(text: TextSpan(
|
|
||||||
text: "",
|
|
||||||
style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, color: Colors.grey),
|
|
||||||
children: [
|
|
||||||
if (record!.endContext.gameType == "40l" && (rank != null && rank != "z")) TextSpan(text: "${t.verdictGeneral(n: readableTimeDifference(record!.endContext.finalTime, sprintAverages[rank]!), verdict: sprintBetterThanRankAverage??false ? t.verdictBetter : t.verdictWorse, rank: rank!.toUpperCase())}\n", style: TextStyle(
|
|
||||||
color: sprintBetterThanRankAverage??false ? Colors.greenAccent : Colors.redAccent
|
|
||||||
))
|
|
||||||
else if (record!.endContext.gameType == "40l" && (rank == null || rank == "z")) TextSpan(text: "${t.verdictGeneral(n: readableTimeDifference(record!.endContext.finalTime, closestAverageSprint.value), verdict: sprintBetterThanClosestAverage ? t.verdictBetter : t.verdictWorse, rank: closestAverageSprint.key.toUpperCase())}\n", style: TextStyle(
|
|
||||||
color: sprintBetterThanClosestAverage ? Colors.greenAccent : Colors.redAccent
|
|
||||||
))
|
|
||||||
else if (record!.endContext.gameType == "blitz" && (rank != null && rank != "z")) TextSpan(text: "${t.verdictGeneral(n: readableIntDifference(record!.endContext.score, blitzAverages[rank]!), verdict: blitzBetterThanRankAverage??false ? t.verdictBetter : t.verdictWorse, rank: rank!.toUpperCase())}\n", style: TextStyle(
|
|
||||||
color: blitzBetterThanRankAverage??false ? Colors.greenAccent : Colors.redAccent
|
|
||||||
))
|
|
||||||
else if (record!.endContext.gameType == "blitz" && (rank == null || rank == "z")) TextSpan(text: "${t.verdictGeneral(n: readableIntDifference(record!.endContext.score, closestAverageBlitz.value), verdict: blitzBetterThanClosestAverage ? t.verdictBetter : t.verdictWorse, rank: closestAverageBlitz.key.toUpperCase())}\n", style: TextStyle(
|
|
||||||
color: blitzBetterThanClosestAverage ? Colors.greenAccent : Colors.redAccent
|
|
||||||
)),
|
|
||||||
if (record!.rank != null) TextSpan(text: "№${record!.rank}", style: TextStyle(color: getColorOfRank(record!.rank!))),
|
|
||||||
if (record!.rank != null) const TextSpan(text: " • "),
|
|
||||||
TextSpan(text: timestamp(record!.timestamp)),
|
|
||||||
]
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
if (record!.endContext.gameType == "40l") Wrap(
|
|
||||||
alignment: WrapAlignment.spaceBetween,
|
|
||||||
spacing: 20,
|
|
||||||
children: [
|
|
||||||
StatCellNum(playerStat: record!.endContext.piecesPlaced, playerStatLabel: t.statCellNum.pieces, isScreenBig: bigScreen, higherIsBetter: true, smallDecimal: false),
|
|
||||||
StatCellNum(playerStat: record!.endContext.pps, playerStatLabel: t.statCellNum.pps, fractionDigits: 2, isScreenBig: bigScreen, higherIsBetter: true, smallDecimal: false),
|
|
||||||
StatCellNum(playerStat: record!.endContext.kpp, playerStatLabel: t.statCellNum.kpp, fractionDigits: 2, isScreenBig: bigScreen, higherIsBetter: true, smallDecimal: false),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
if (record!.endContext.gameType == "blitz") Wrap(
|
|
||||||
alignment: WrapAlignment.spaceBetween,
|
|
||||||
crossAxisAlignment: WrapCrossAlignment.start,
|
|
||||||
spacing: 20,
|
|
||||||
children: [
|
|
||||||
StatCellNum(playerStat: record!.endContext.level, playerStatLabel: t.statCellNum.level, isScreenBig: bigScreen, higherIsBetter: true, smallDecimal: false),
|
|
||||||
StatCellNum(playerStat: record!.endContext.pps, playerStatLabel: t.statCellNum.pps, fractionDigits: 2, isScreenBig: bigScreen, higherIsBetter: true, smallDecimal: false),
|
|
||||||
StatCellNum(playerStat: record!.endContext.spp, playerStatLabel: t.statCellNum.spp, fractionDigits: 2, isScreenBig: bigScreen, higherIsBetter: true)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
FinesseThingy(record?.endContext.finesse, record?.endContext.finessePercentage),
|
|
||||||
LineclearsThingy(record!.endContext.clears, record!.endContext.lines, record!.endContext.holds, record!.endContext.tSpins),
|
|
||||||
if (record!.endContext.gameType == "40l") Text("${record!.endContext.inputs} KP • ${f2.format(record!.endContext.kps)} KPS"),
|
|
||||||
if (record!.endContext.gameType == "blitz") Text("${record!.endContext.piecesPlaced} P • ${record!.endContext.inputs} KP • ${f2.format(record!.endContext.kpp)} KPP • ${f2.format(record!.endContext.kps)} KPS"),
|
|
||||||
Wrap(
|
|
||||||
alignment: WrapAlignment.spaceBetween,
|
|
||||||
crossAxisAlignment: WrapCrossAlignment.start,
|
|
||||||
spacing: 20,
|
|
||||||
children: [
|
|
||||||
TextButton(onPressed: (){launchInBrowser(Uri.parse("https://tetr.io/#r:${record!.replayId}"));}, child: Text(t.openSPreplay)),
|
|
||||||
TextButton(onPressed: (){launchInBrowser(Uri.parse("https://inoue.szy.lol/api/replay/${record!.replayId}"));}, child: Text(t.downloadSPreplay)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
if (stream != null && stream!.records.length > 1) for(int i = 1; i < stream!.records.length; i++) ListTile(
|
|
||||||
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (context) => SingleplayerRecordView(record: stream!.records[i]))),
|
|
||||||
leading: Text("#${i+1}",
|
|
||||||
style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 28, shadows: textShadow, height: 0.9)
|
|
||||||
),
|
|
||||||
title: Text(
|
|
||||||
switch (stream!.records[i].endContext.gameType){
|
|
||||||
"40l" => get40lTime(stream!.records[i].endContext.finalTime.inMicroseconds),
|
|
||||||
"blitz" => t.blitzScore(p: NumberFormat.decimalPattern().format(stream!.records[i].endContext.score)),
|
|
||||||
"5mblast" => get40lTime(stream!.records[i].endContext.finalTime.inMicroseconds),
|
|
||||||
String() => "huh",
|
|
||||||
},
|
|
||||||
style: const TextStyle(fontSize: 18)),
|
|
||||||
subtitle: Text(timestamp(stream!.records[i].timestamp), style: const TextStyle(color: Colors.grey, height: 0.85)),
|
|
||||||
trailing: SpTrailingStats(stream!.records[i].endContext)
|
|
||||||
)
|
|
||||||
]
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,28 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:tetra_stats/data_objects/tetrio.dart';
|
|
||||||
import 'package:tetra_stats/utils/numers_formats.dart';
|
|
||||||
|
|
||||||
class SpTrailingStats extends StatelessWidget{
|
|
||||||
final EndContextSingle endContext;
|
|
||||||
|
|
||||||
const SpTrailingStats(this.endContext, {super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
const TextStyle style = TextStyle(height: 1.1, fontWeight: FontWeight.w100, fontSize: 13);
|
|
||||||
return Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
|
||||||
children: [
|
|
||||||
Text("${endContext.piecesPlaced} P, ${f2.format(endContext.pps)} PPS", style: style, textAlign: TextAlign.right),
|
|
||||||
Text("${intf.format(endContext.finessePercentage*100)}% F, ${endContext.finesse?.faults} FF", style: style, textAlign: TextAlign.right),
|
|
||||||
Text(switch(endContext.gameType){
|
|
||||||
"40l" => "${f2.format(endContext.kps)} KPS, ${f2.format(endContext.kpp)} KPP",
|
|
||||||
"blitz" => "${intf.format(endContext.spp)} SPP, lvl ${endContext.level}",
|
|
||||||
"5mblast" => "${intf.format(endContext.spp)} SPP, ${endContext.lines} L",
|
|
||||||
String() => "huh"
|
|
||||||
}, style: style, textAlign: TextAlign.right)
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -43,16 +43,16 @@ class StatCellNum extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
NumberFormat f = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: fractionDigits ?? 0);
|
|
||||||
NumberFormat comparef = NumberFormat("+#,###.###;-#,###.###")..maximumFractionDigits = fractionDigits ?? 0;
|
NumberFormat comparef = NumberFormat("+#,###.###;-#,###.###")..maximumFractionDigits = fractionDigits ?? 0;
|
||||||
String formated = f.format(playerStat);
|
NumberFormat fractionf = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: fractionDigits ?? 0)..maximumIntegerDigits = 0;
|
||||||
List<String> splited = formated.split(f.symbols.DECIMAL_SEP);
|
num fraction = playerStat.isNegative ? 1 - (playerStat - playerStat.floor()) : playerStat - playerStat.floor();
|
||||||
|
int integer = playerStat.isNegative ? (playerStat + fraction).toInt() : (playerStat - fraction).toInt();
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
RichText(
|
RichText(
|
||||||
text: TextSpan(text: splited[0],
|
text: TextSpan(text: intf.format(integer),
|
||||||
children: [
|
children: [
|
||||||
if ((fractionDigits??0) > 0) TextSpan(text: f.symbols.DECIMAL_SEP+splited[1], style: smallDecimal ? const TextStyle(fontFamily: "Eurostile Round", fontSize: 16) : null)
|
TextSpan(text: fractionf.format(fraction).substring(1), style: smallDecimal ? const TextStyle(fontSize: 16) : null)
|
||||||
],
|
],
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontFamily: "Eurostile Round Extended",
|
fontFamily: "Eurostile Round Extended",
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
import 'package:intl/intl.dart';
|
|
||||||
import 'package:tetra_stats/gen/strings.g.dart';
|
|
||||||
import 'package:tetra_stats/main.dart';
|
|
||||||
import 'package:tetra_stats/utils/relative_timestamps.dart';
|
|
||||||
|
|
||||||
final DateFormat dateFormat = DateFormat.yMMMd(LocaleSettings.currentLocale.languageCode).add_Hms();
|
|
||||||
|
|
||||||
String timestamp(DateTime dateTime){
|
|
||||||
int timestampMode = prefs.getInt("timestampMode")??0;
|
|
||||||
return timestampMode == 2 ? relativeDateTime(dateTime) : dateFormat.format(timestampMode == 1 ? dateTime.toLocal() : dateTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
// class TextTimestamp extends StatelessWidget{
|
|
||||||
// @override
|
|
||||||
// Widget build(BuildContext context) {
|
|
||||||
// // TODO: implement build
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// }
|
|
|
@ -70,7 +70,7 @@ class TLProgress extends StatelessWidget{
|
||||||
if (tlData.nextAt > 0 && nextRankTRcutoff != null) const TextSpan(text: "\n"),
|
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 (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 ((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 && ((tlData.rank != "x" && tlData.rank != "z") || tlData.percentileRank != "x"))) ? t.promotionOnNextWin : t.numOfVictories(wins: f2.format((nextRankGlickoCutoff!-tlData.glicko!)/glickoForWin)), style: TextStyle(color: (tlData.standing < tlData.nextAt || ((nextRankGlickoCutoff!-tlData.glicko!)/glickoForWin < 0.5 && tlData.percentileRank != "x")) ? Colors.greenAccent : null))
|
if (nextRankGlickoCutoff != null) TextSpan(text: (tlData.standing < tlData.nextAt || ((nextRankGlickoCutoff!-tlData.glicko!)/glickoForWin < 0.5 && tlData.percentileRank != "x")) ? t.promotionOnNextWin : t.numOfVictories(wins: f2.format((nextRankGlickoCutoff!-tlData.glicko!)/glickoForWin)), style: TextStyle(color: (tlData.standing < tlData.nextAt || ((nextRankGlickoCutoff!-tlData.glicko!)/glickoForWin < 0.5 && tlData.percentileRank != "x")) ? Colors.greenAccent : null))
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
@ -82,8 +82,8 @@ class TLProgress extends StatelessWidget{
|
||||||
maximum: 1,
|
maximum: 1,
|
||||||
interval: 1,
|
interval: 1,
|
||||||
ranges: [
|
ranges: [
|
||||||
if (previousRankTRcutoff != null && nextRankTRcutoff != null) LinearGaugeRange(endValue: getBarTR(tlData.rating)!, color: Theme.of(context).colorScheme.primary, 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: Theme.of(context).colorScheme.primary, 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 && previousRankTRcutoff != 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)
|
||||||
],
|
],
|
||||||
|
|
|
@ -1,97 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:intl/intl.dart';
|
|
||||||
import 'package:tetra_stats/data_objects/tetrio.dart';
|
|
||||||
import 'package:tetra_stats/gen/strings.g.dart';
|
|
||||||
import 'package:tetra_stats/main.dart' show prefs;
|
|
||||||
import 'package:tetra_stats/utils/numers_formats.dart';
|
|
||||||
|
|
||||||
var fDiff = NumberFormat("+#,###.####;-#,###.####");
|
|
||||||
|
|
||||||
class TLRatingThingy extends StatelessWidget{
|
|
||||||
final String userID;
|
|
||||||
final TetraLeagueAlpha tlData;
|
|
||||||
final TetraLeagueAlpha? oldTl;
|
|
||||||
final double? topTR;
|
|
||||||
|
|
||||||
const TLRatingThingy({super.key, required this.userID, required this.tlData, this.oldTl, this.topTR});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
bool oskKagariGimmick = prefs.getBool("oskKagariGimmick")??true;
|
|
||||||
bool bigScreen = MediaQuery.of(context).size.width >= 768;
|
|
||||||
String decimalSeparator = f4.symbols.DECIMAL_SEP;
|
|
||||||
List<String> formatedTR = f4.format(tlData.rating).split(decimalSeparator);
|
|
||||||
List<String> formatedGlicko = f4.format(tlData.glicko).split(decimalSeparator);
|
|
||||||
List<String> formatedPercentile = f4.format(tlData.percentile * 100).split(decimalSeparator);
|
|
||||||
return Wrap(
|
|
||||||
direction: Axis.horizontal,
|
|
||||||
alignment: WrapAlignment.spaceAround,
|
|
||||||
crossAxisAlignment: WrapCrossAlignment.center,
|
|
||||||
clipBehavior: Clip.hardEdge,
|
|
||||||
children: [
|
|
||||||
(userID == "5e32fc85ab319c2ab1beb07c" && oskKagariGimmick) // he love her so much, you can't even imagine
|
|
||||||
? Image.asset("res/icons/kagari.png", height: 128) // Btw why she wearing Kazamatsuri high school uniform?
|
|
||||||
: Image.asset("res/tetrio_tl_alpha_ranks/${tlData.rank}.png", height: 128),
|
|
||||||
Column(
|
|
||||||
children: [
|
|
||||||
RichText(
|
|
||||||
text: TextSpan(
|
|
||||||
style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 20, color: Colors.white),
|
|
||||||
children: switch(prefs.getInt("ratingMode")){
|
|
||||||
1 => [
|
|
||||||
TextSpan(text: formatedGlicko[0], style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)),
|
|
||||||
if (formatedGlicko.elementAtOrNull(1) != null) TextSpan(text: decimalSeparator + formatedGlicko[1]),
|
|
||||||
TextSpan(text: " Glicko", style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28))
|
|
||||||
],
|
|
||||||
2 => [
|
|
||||||
TextSpan(text: "${t.top} ${formatedPercentile[0]}", style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)),
|
|
||||||
if (formatedPercentile.elementAtOrNull(1) != null) TextSpan(text: decimalSeparator + formatedPercentile[1]),
|
|
||||||
TextSpan(text: " %", style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28))
|
|
||||||
],
|
|
||||||
_ => [
|
|
||||||
TextSpan(text: formatedTR[0], style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)),
|
|
||||||
if (formatedTR.elementAtOrNull(1) != null) TextSpan(text: decimalSeparator + formatedTR[1]),
|
|
||||||
TextSpan(text: " TR", style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28))
|
|
||||||
],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
),
|
|
||||||
if (oldTl != null) Text(
|
|
||||||
switch(prefs.getInt("ratingMode")){
|
|
||||||
1 => "${fDiff.format(tlData.glicko! - oldTl!.glicko!)} Glicko",
|
|
||||||
2 => "${fDiff.format(tlData.percentile * 100 - oldTl!.percentile * 100)} %",
|
|
||||||
_ => "${fDiff.format(tlData.rating - oldTl!.rating)} TR"
|
|
||||||
},
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: TextStyle(
|
|
||||||
color: tlData.rating - oldTl!.rating < 0 ?
|
|
||||||
Colors.red :
|
|
||||||
Colors.green
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Column(
|
|
||||||
children: [
|
|
||||||
RichText(
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
softWrap: true,
|
|
||||||
text: TextSpan(
|
|
||||||
style: DefaultTextStyle.of(context).style,
|
|
||||||
children: [
|
|
||||||
TextSpan(text: prefs.getInt("ratingMode") == 2 ? "${f2.format(tlData.rating)} TR • % ${t.rank}: ${tlData.percentileRank.toUpperCase()}" : "${t.top} ${f2.format(tlData.percentile * 100)}% (${tlData.percentileRank.toUpperCase()})"),
|
|
||||||
if (tlData.bestRank != "z") const TextSpan(text: " • "),
|
|
||||||
if (tlData.bestRank != "z") TextSpan(text: "${t.topRank}: ${tlData.bestRank.toUpperCase()}"),
|
|
||||||
if (topTR != null) TextSpan(text: " (${f2.format(topTR)} TR)"),
|
|
||||||
TextSpan(text: " • ${prefs.getInt("ratingMode") == 1 ? "${f2.format(tlData.rating)} TR • RD: " : "Glicko: ${f2.format(tlData.glicko!)}±"}"),
|
|
||||||
TextSpan(text: f2.format(tlData.rd!), style: tlData.decaying ? TextStyle(color: tlData.rd! > 98 ? Colors.red : Colors.yellow) : null),
|
|
||||||
if (tlData.decaying) WidgetSpan(child: Icon(Icons.trending_up, color: tlData.rd! > 98 ? Colors.red : Colors.yellow,), alignment: PlaceholderAlignment.middle, baseline: TextBaseline.alphabetic)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,17 +3,17 @@ import 'package:intl/intl.dart';
|
||||||
import 'package:tetra_stats/data_objects/tetrio.dart';
|
import 'package:tetra_stats/data_objects/tetrio.dart';
|
||||||
import 'package:syncfusion_flutter_gauges/gauges.dart';
|
import 'package:syncfusion_flutter_gauges/gauges.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:tetra_stats/utils/colors_functions.dart';
|
import 'package:tetra_stats/utils/colors_functions.dart';
|
||||||
import 'package:tetra_stats/utils/numers_formats.dart';
|
import 'package:tetra_stats/utils/numers_formats.dart';
|
||||||
import 'package:tetra_stats/widgets/gauget_num.dart';
|
import 'package:tetra_stats/widgets/gauget_num.dart';
|
||||||
import 'package:tetra_stats/widgets/graphs.dart';
|
import 'package:tetra_stats/widgets/graphs.dart';
|
||||||
import 'package:tetra_stats/widgets/stat_sell_num.dart';
|
import 'package:tetra_stats/widgets/stat_sell_num.dart';
|
||||||
import 'package:tetra_stats/widgets/text_timestamp.dart';
|
|
||||||
import 'package:tetra_stats/widgets/tl_progress_bar.dart';
|
import 'package:tetra_stats/widgets/tl_progress_bar.dart';
|
||||||
import 'package:tetra_stats/widgets/tl_rating_thingy.dart';
|
|
||||||
|
|
||||||
|
var fDiff = NumberFormat("+#,###.###;-#,###.###");
|
||||||
var intFDiff = NumberFormat("+#,###.000;-#,###.000");
|
var intFDiff = NumberFormat("+#,###;-#,###");
|
||||||
|
final DateFormat dateFormat = DateFormat.yMMMd(LocaleSettings.currentLocale.languageCode).add_Hms();
|
||||||
|
|
||||||
class TLThingy extends StatefulWidget {
|
class TLThingy extends StatefulWidget {
|
||||||
final TetraLeagueAlpha tl;
|
final TetraLeagueAlpha tl;
|
||||||
|
@ -48,6 +48,7 @@ class _TLThingyState extends State<TLThingy> {
|
||||||
void initState() {
|
void initState() {
|
||||||
_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;
|
||||||
oldTl = sortedStates.elementAtOrNull(1)?.tlSeason1;
|
oldTl = sortedStates.elementAtOrNull(1)?.tlSeason1;
|
||||||
currentTl = widget.tl;
|
currentTl = widget.tl;
|
||||||
super.initState();
|
super.initState();
|
||||||
|
@ -56,9 +57,8 @@ class _TLThingyState extends State<TLThingy> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final t = Translations.of(context);
|
final t = Translations.of(context);
|
||||||
String decimalSeparator = f2.symbols.DECIMAL_SEP;
|
NumberFormat fractionfEstTR = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 2)..maximumIntegerDigits = 0;
|
||||||
List<String> estTRformated = f2.format(currentTl.estTr!.esttr).split(decimalSeparator);
|
NumberFormat fractionfEstTRAcc = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 3)..maximumIntegerDigits = 0;
|
||||||
List<String> estTRaccFormated = intFDiff.format(currentTl.esttracc!).split(".");
|
|
||||||
if (currentTl.gamesPlayed == 0) return Center(child: Text(widget.guest ? t.anonTL : widget.bot ? t.botTL : t.neverPlayedTL, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 28), textAlign: TextAlign.center,));
|
if (currentTl.gamesPlayed == 0) return Center(child: Text(widget.guest ? t.anonTL : widget.bot ? t.botTL : t.neverPlayedTL, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 28), textAlign: TextAlign.center,));
|
||||||
return LayoutBuilder(builder: (context, constraints) {
|
return LayoutBuilder(builder: (context, constraints) {
|
||||||
bool bigScreen = constraints.maxWidth >= 768;
|
bool bigScreen = constraints.maxWidth >= 768;
|
||||||
|
@ -69,7 +69,7 @@ class _TLThingyState extends State<TLThingy> {
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
if (widget.showTitle) Text(t.tetraLeague, style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)),
|
if (widget.showTitle) Text(t.tetraLeague, style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)),
|
||||||
if (oldTl != null) Text(t.comparingWith(newDate: timestamp(currentTl.timestamp), oldDate: timestamp(oldTl!.timestamp)),
|
if (oldTl != null) Text(t.comparingWith(newDate: dateFormat.format(currentTl.timestamp), oldDate: dateFormat.format(oldTl!.timestamp)),
|
||||||
textAlign: TextAlign.center,),
|
textAlign: TextAlign.center,),
|
||||||
if (oldTl != null) RangeSlider(values: _currentRangeValues, max: widget.states.length.toDouble(),
|
if (oldTl != null) RangeSlider(values: _currentRangeValues, max: widget.states.length.toDouble(),
|
||||||
labels: RangeLabels(
|
labels: RangeLabels(
|
||||||
|
@ -92,7 +92,52 @@ class _TLThingyState extends State<TLThingy> {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
if (currentTl.gamesPlayed >= 10) TLRatingThingy(userID: widget.userID, tlData: currentTl, oldTl: oldTl, topTR: widget.topTR),
|
if (currentTl.gamesPlayed >= 10)
|
||||||
|
Wrap(
|
||||||
|
direction: Axis.horizontal,
|
||||||
|
alignment: WrapAlignment.spaceAround,
|
||||||
|
crossAxisAlignment: WrapCrossAlignment.center,
|
||||||
|
clipBehavior: Clip.hardEdge,
|
||||||
|
children: [
|
||||||
|
(widget.userID == "5e32fc85ab319c2ab1beb07c" && oskKagariGimmick) // he love her so much, you can't even imagine
|
||||||
|
? Image.asset("res/icons/kagari.png", height: 128) // Btw why she wearing Kazamatsuri high school uniform?
|
||||||
|
: Image.asset("res/tetrio_tl_alpha_ranks/${currentTl.rank}.png", height: 128),
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
Text("${f2.format(currentTl.rating)} TR", style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)),
|
||||||
|
if (oldTl != null) Text(
|
||||||
|
"${fDiff.format(currentTl.rating - oldTl!.rating)} TR",
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
color: currentTl.rating - oldTl!.rating < 0 ?
|
||||||
|
Colors.red :
|
||||||
|
Colors.green
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
RichText(
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
softWrap: true,
|
||||||
|
text: TextSpan(
|
||||||
|
style: DefaultTextStyle.of(context).style,
|
||||||
|
children: [
|
||||||
|
TextSpan(text: "${t.top} ${f2.format(currentTl.percentile * 100)}% (${currentTl.percentileRank.toUpperCase()})"),
|
||||||
|
if (currentTl.bestRank != "z") const TextSpan(text: " • "),
|
||||||
|
if (currentTl.bestRank != "z") TextSpan(text: "${t.topRank}: ${currentTl.bestRank.toUpperCase()}"),
|
||||||
|
if (widget.topTR != null) TextSpan(text: " (${f2.format(widget.topTR)} TR)"),
|
||||||
|
TextSpan(text: " • Glicko: ${f2.format(currentTl.glicko!)}±"),
|
||||||
|
TextSpan(text: f2.format(currentTl.rd!), style: currentTl.decaying ? TextStyle(color: currentTl.rd! > 98 ? Colors.red : Colors.yellow) : null),
|
||||||
|
if (currentTl.decaying) WidgetSpan(child: Icon(Icons.trending_up, color: currentTl.rd! > 98 ? Colors.red : Colors.yellow,), alignment: PlaceholderAlignment.middle, baseline: TextBaseline.alphabetic)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
if (currentTl.gamesPlayed > 9) TLProgress(
|
if (currentTl.gamesPlayed > 9) TLProgress(
|
||||||
tlData: currentTl,
|
tlData: currentTl,
|
||||||
previousRankTRcutoff: widget.thatRankCutoff,
|
previousRankTRcutoff: widget.thatRankCutoff,
|
||||||
|
@ -245,10 +290,12 @@ class _TLThingyState extends State<TLThingy> {
|
||||||
),
|
),
|
||||||
if (currentTl.estTr != null)
|
if (currentTl.estTr != null)
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(8, 20, 8, 20),
|
padding: const EdgeInsets.fromLTRB(0, 20, 0, 20),
|
||||||
child: Container(
|
child: Container(
|
||||||
|
//alignment: Alignment.center,
|
||||||
|
width: bigScreen ? MediaQuery.of(context).size.width * 0.4 : MediaQuery.of(context).size.width * 0.85,
|
||||||
height: 70,
|
height: 70,
|
||||||
constraints: const BoxConstraints(maxWidth: 500),
|
constraints: const BoxConstraints(maxWidth: 768),
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
Positioned(
|
Positioned(
|
||||||
|
@ -259,9 +306,9 @@ class _TLThingyState extends State<TLThingy> {
|
||||||
Text(t.statCellNum.estOfTR, style: const TextStyle(height: 0.1),),
|
Text(t.statCellNum.estOfTR, style: const TextStyle(height: 0.1),),
|
||||||
RichText(
|
RichText(
|
||||||
text: TextSpan(
|
text: TextSpan(
|
||||||
text: estTRformated[0],
|
text: intf.format(currentTl.estTr!.esttr.truncate()),
|
||||||
style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 36 : 30, fontWeight: FontWeight.w500, color: Colors.white),
|
style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 36 : 30, fontWeight: FontWeight.w500, color: Colors.white),
|
||||||
children: [TextSpan(text: decimalSeparator+estTRformated[1], style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, fontWeight: FontWeight.w100))]
|
children: [TextSpan(text: fractionfEstTR.format(currentTl.estTr!.esttr - currentTl.estTr!.esttr.truncate()).substring(1), style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, fontWeight: FontWeight.w100))]
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
RichText(text: TextSpan(
|
RichText(text: TextSpan(
|
||||||
|
@ -288,10 +335,10 @@ class _TLThingyState extends State<TLThingy> {
|
||||||
Text(t.statCellNum.accOfEst, style: const TextStyle(height: 0.1),),
|
Text(t.statCellNum.accOfEst, style: const TextStyle(height: 0.1),),
|
||||||
RichText(
|
RichText(
|
||||||
text: TextSpan(
|
text: TextSpan(
|
||||||
text: (currentTl.esttracc != null && currentTl.bestRank != "z") ? estTRaccFormated[0] : "---",
|
text: (currentTl.esttracc != null && currentTl.bestRank != "z") ? intFDiff.format(currentTl.esttracc!.truncate()) : "---",
|
||||||
style: TextStyle(fontFamily: "Eurostile Round", fontSize: bigScreen ? 36 : 30, fontWeight: FontWeight.w500, color: Colors.white),
|
style: TextStyle(fontFamily: "Eurostile Round", fontSize: bigScreen ? 36 : 30, fontWeight: FontWeight.w500, color: Colors.white),
|
||||||
children: [
|
children: [
|
||||||
TextSpan(text: (currentTl.esttracc != null && currentTl.bestRank != "z") ? decimalSeparator+estTRaccFormated[1] : ".---", style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, fontWeight: FontWeight.w100))
|
TextSpan(text: (currentTl.esttracc != null && currentTl.bestRank != "z") ? fractionfEstTRAcc.format(currentTl.esttracc!.isNegative ? 1 - (currentTl.esttracc! - currentTl.esttracc!.truncate()) : (currentTl.esttracc! - currentTl.esttracc!.truncate())).substring(1) : ".---", style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, fontWeight: FontWeight.w100))
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -4,13 +4,11 @@ import 'package:flutter/services.dart';
|
||||||
import 'package:syncfusion_flutter_gauges/gauges.dart';
|
import 'package:syncfusion_flutter_gauges/gauges.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/main.dart' show teto;
|
|
||||||
import 'package:tetra_stats/views/compare_view.dart';
|
import 'package:tetra_stats/views/compare_view.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:tetra_stats/utils/text_shadow.dart';
|
import 'package:tetra_stats/utils/text_shadow.dart';
|
||||||
import 'dart:developer' as developer;
|
import 'dart:developer' as developer;
|
||||||
import 'package:tetra_stats/widgets/stat_sell_num.dart';
|
import 'package:tetra_stats/widgets/stat_sell_num.dart';
|
||||||
import 'package:tetra_stats/widgets/text_timestamp.dart';
|
|
||||||
|
|
||||||
const Map<int, double> xpTableScuffed = { // level: xp required
|
const Map<int, double> xpTableScuffed = { // level: xp required
|
||||||
05000: 67009018.4885772,
|
05000: 67009018.4885772,
|
||||||
|
@ -37,6 +35,7 @@ class UserThingy extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final t = Translations.of(context);
|
final t = Translations.of(context);
|
||||||
|
final DateFormat dateFormat = DateFormat.yMMMd(LocaleSettings.currentLocale.languageCode).add_Hms();
|
||||||
return LayoutBuilder(builder: (context, constraints) {
|
return LayoutBuilder(builder: (context, constraints) {
|
||||||
bool bigScreen = constraints.maxWidth > 768;
|
bool bigScreen = constraints.maxWidth > 768;
|
||||||
double bannerHeight = bigScreen ? 240 : 120;
|
double bannerHeight = bigScreen ? 240 : 120;
|
||||||
|
@ -126,7 +125,7 @@ class UserThingy extends StatelessWidget {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
showStateTimestamp
|
showStateTimestamp
|
||||||
? Text(t.fetchDate(date: timestamp(player.state)))
|
? Text(t.fetchDate(date: dateFormat.format(player.state)))
|
||||||
: Wrap(direction: Axis.horizontal, alignment: WrapAlignment.center, spacing: 25, crossAxisAlignment: WrapCrossAlignment.start, children: [
|
: Wrap(direction: Axis.horizontal, alignment: WrapAlignment.center, spacing: 25, crossAxisAlignment: WrapCrossAlignment.start, children: [
|
||||||
FutureBuilder(
|
FutureBuilder(
|
||||||
future: teto.isPlayerTracking(player.userId),
|
future: teto.isPlayerTracking(player.userId),
|
||||||
|
@ -340,7 +339,7 @@ class UserThingy extends StatelessWidget {
|
||||||
),
|
),
|
||||||
children: [
|
children: [
|
||||||
if (player.country != null) TextSpan(text: "${t.countries[player.country]} • "),
|
if (player.country != null) TextSpan(text: "${t.countries[player.country]} • "),
|
||||||
TextSpan(text: "${t.playerRole[player.role]}${t.playerRoleAccount}${player.registrationTime == null ? t.wasFromBeginning : '${t.created} ${timestamp(player.registrationTime!)}'}"),
|
TextSpan(text: "${t.playerRole[player.role]}${t.playerRoleAccount}${player.registrationTime == null ? t.wasFromBeginning : '${t.created} ${dateFormat.format(player.registrationTime!)}'}"),
|
||||||
if (player.supporterTier > 0) const TextSpan(text: " • "),
|
if (player.supporterTier > 0) const TextSpan(text: " • "),
|
||||||
if (player.supporterTier > 0) WidgetSpan(child: Icon(player.supporterTier > 1 ? Icons.star : Icons.star_border, color: player.supporterTier > 1 ? Colors.yellowAccent : Colors.white), alignment: PlaceholderAlignment.middle, baseline: TextBaseline.alphabetic),
|
if (player.supporterTier > 0) WidgetSpan(child: Icon(player.supporterTier > 1 ? Icons.star : Icons.star_border, color: player.supporterTier > 1 ? Colors.yellowAccent : Colors.white), alignment: PlaceholderAlignment.middle, baseline: TextBaseline.alphabetic),
|
||||||
if (player.supporterTier > 0) TextSpan(text: player.supporterTier.toString(), style: TextStyle(color: player.supporterTier > 1 ? Colors.yellowAccent : Colors.white))
|
if (player.supporterTier > 0) TextSpan(text: player.supporterTier.toString(), style: TextStyle(color: player.supporterTier > 1 ? Colors.yellowAccent : Colors.white))
|
||||||
|
@ -386,7 +385,7 @@ class UserThingy extends StatelessWidget {
|
||||||
children: [
|
children: [
|
||||||
Image.asset("res/tetrio_badges/${badge.badgeId}.png"),
|
Image.asset("res/tetrio_badges/${badge.badgeId}.png"),
|
||||||
Text(badge.ts != null
|
Text(badge.ts != null
|
||||||
? t.obtainDate(date: timestamp(badge.ts!))
|
? t.obtainDate(date: dateFormat.format(badge.ts!))
|
||||||
: t.assignedManualy),
|
: t.assignedManualy),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
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/widgets/graphs.dart' show MyRadarChart;
|
|
||||||
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';
|
||||||
|
|
||||||
|
@ -32,7 +31,7 @@ class VsGraphs extends StatelessWidget{
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: 310,
|
height: 310,
|
||||||
width: 310,
|
width: 310,
|
||||||
child: MyRadarChart(
|
child: RadarChart(
|
||||||
RadarChartData(
|
RadarChartData(
|
||||||
radarShape: RadarShape.polygon,
|
radarShape: RadarShape.polygon,
|
||||||
tickCount: 4,
|
tickCount: 4,
|
||||||
|
@ -135,7 +134,7 @@ class VsGraphs extends StatelessWidget{
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: 310,
|
height: 310,
|
||||||
width: 310,
|
width: 310,
|
||||||
child: MyRadarChart(
|
child: RadarChart(
|
||||||
RadarChartData(
|
RadarChartData(
|
||||||
radarShape: RadarShape.polygon,
|
radarShape: RadarShape.polygon,
|
||||||
tickCount: 4,
|
tickCount: 4,
|
||||||
|
@ -212,7 +211,7 @@ class VsGraphs extends StatelessWidget{
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: 310,
|
height: 310,
|
||||||
width: 310,
|
width: 310,
|
||||||
child: MyRadarChart(
|
child: RadarChart(
|
||||||
RadarChartData(
|
RadarChartData(
|
||||||
radarShape: RadarShape.polygon,
|
radarShape: RadarShape.polygon,
|
||||||
tickCount: 4,
|
tickCount: 4,
|
||||||
|
|
|
@ -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.6.0+20
|
version: 1.5.3+19
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.0.0'
|
sdk: '>=3.0.0'
|
||||||
|
|
|
@ -8,11 +8,6 @@
|
||||||
"history": "History",
|
"history": "History",
|
||||||
"sprint": "40 Lines",
|
"sprint": "40 Lines",
|
||||||
"blitz": "Blitz",
|
"blitz": "Blitz",
|
||||||
"recent": "Recent",
|
|
||||||
"recentRuns": "Recent runs",
|
|
||||||
"blitzScore": "$p points",
|
|
||||||
"openSPreplay": "Open replay in TETR.IO",
|
|
||||||
"downloadSPreplay": "Download replay",
|
|
||||||
"other": "Other",
|
"other": "Other",
|
||||||
"distinguishment": "Distinguishment",
|
"distinguishment": "Distinguishment",
|
||||||
"zen": "Zen",
|
"zen": "Zen",
|
||||||
|
@ -114,28 +109,14 @@
|
||||||
"yourIDAlertTitle": "Your nickname in TETR.IO",
|
"yourIDAlertTitle": "Your nickname in TETR.IO",
|
||||||
"yourIDText": "When app loads, it will retrieve data for this account",
|
"yourIDText": "When app loads, it will retrieve data for this account",
|
||||||
"language": "Language",
|
"language": "Language",
|
||||||
"updateInBackground": "Update stats in the background",
|
|
||||||
"updateInBackgroundDescription": "While Tetra Stats is running, it can update stats of the current player when cache expires",
|
|
||||||
"customization": "Customization",
|
"customization": "Customization",
|
||||||
"customizationDescription": "Change appearance of different things in Tetra Stats UI",
|
"customizationDescription": "There is only one toggle, planned to add more settings",
|
||||||
"oskKagari": "Osk Kagari gimmick",
|
|
||||||
"oskKagariDescription": "If on, osk's rank on main view will be rendered as :kagari:",
|
|
||||||
"AccentColor": "Accent color",
|
|
||||||
"AccentColorDescription": "Almost all interactive UI elements highlighted with this color",
|
|
||||||
"timestamps": "Timestamps",
|
|
||||||
"timestampsDescription": "You can choose, in which way timestamps shows time",
|
|
||||||
"timestampsAbsoluteGMT": "Absolute (GMT)",
|
|
||||||
"timestampsAbsoluteLocalTime": "Absolute (Your timezone)",
|
|
||||||
"timestampsRelative": "Relative",
|
|
||||||
"rating": "Main representation of rating",
|
|
||||||
"ratingDescription": "TR is not linear, while Glicko does not have boundaries and percentile is volatile",
|
|
||||||
"ratingLBposition": "LB position",
|
|
||||||
"sheetbotGraphs": "Sheetbot-like behavior for radar graphs",
|
|
||||||
"sheetbotGraphsDescription": "If on, points on the graphs can appear on the opposite half of the graph if value is negative",
|
|
||||||
"lbStats": "Show leaderboard based stats",
|
"lbStats": "Show leaderboard based stats",
|
||||||
"lbStatsDescription": "That will impact on loading times, but will allow you to see position on LB by stats and comparison with average values",
|
"lbStatsDescription": "That will impact on loading times, but will allow you to see position on LB by stats and comparison with average values",
|
||||||
"aboutApp": "About app",
|
"aboutApp": "About app",
|
||||||
"aboutAppText": "${appName} (${packageName}) Version ${version} Build ${buildNumber}\n\nDeveloped by dan63047\nFormulas provided by kerrmunism\nHistory provided by p1nkl0bst3r\nTETR.IO replay grabber API by szy",
|
"aboutAppText": "${appName} (${packageName}) Version ${version} Build ${buildNumber}\n\nDeveloped by dan63047\nFormulas provided by kerrmunism\nHistory provided by p1nkl0bst3r\nTETR.IO replay grabber API by szy",
|
||||||
|
"oskKagari": "Osk Kagari gimmick",
|
||||||
|
"oskKagariDescription": "If on, osk's rank on main view will be rendered as :kagari:",
|
||||||
"stateViewTitle": "${nickname} account on ${date}",
|
"stateViewTitle": "${nickname} account on ${date}",
|
||||||
"statesViewTitle": "${number} states of ${nickname} account",
|
"statesViewTitle": "${number} states of ${nickname} account",
|
||||||
"matchesViewTitle": "${nickname} TL matches",
|
"matchesViewTitle": "${nickname} TL matches",
|
||||||
|
|
|
@ -8,11 +8,6 @@
|
||||||
"history": "История",
|
"history": "История",
|
||||||
"sprint": "40 линий",
|
"sprint": "40 линий",
|
||||||
"blitz": "Блиц",
|
"blitz": "Блиц",
|
||||||
"recent": "Недавно",
|
|
||||||
"recentRuns": "Недавние",
|
|
||||||
"blitzScore": "$p очков",
|
|
||||||
"openSPreplay": "Открыть повтор в TETR.IO",
|
|
||||||
"downloadSPreplay": "Скачать повтор",
|
|
||||||
"other": "Другое",
|
"other": "Другое",
|
||||||
"distinguishment": "Заслуга",
|
"distinguishment": "Заслуга",
|
||||||
"zen": "Дзен",
|
"zen": "Дзен",
|
||||||
|
@ -114,28 +109,14 @@
|
||||||
"yourIDAlertTitle": "Ваш ник в TETR.IO",
|
"yourIDAlertTitle": "Ваш ник в TETR.IO",
|
||||||
"yourIDText": "При запуске приложения оно будет получать статистику этого игрока.",
|
"yourIDText": "При запуске приложения оно будет получать статистику этого игрока.",
|
||||||
"language": "Язык (Language)",
|
"language": "Язык (Language)",
|
||||||
"updateInBackground": "Обновлять статистику в фоне",
|
|
||||||
"updateInBackgroundDescription": "Пока Tetra Stats работает, он может обновлять статистику самостоятельно когда кеш истекает",
|
|
||||||
"customization": "Кастомизация",
|
"customization": "Кастомизация",
|
||||||
"customizationDescription": "Измените внешний вид пользовательского интерфейса Tetra Stats",
|
"customizationDescription": "Здесь только один переключатель, в планах добавить больше",
|
||||||
"oskKagari": "\"Оск Кагари\" прикол",
|
|
||||||
"oskKagariDescription": "Если включено, вместо настоящего ранга оска будет рендерится :kagari:",
|
|
||||||
"AccentColor": "Цветовой акцент",
|
|
||||||
"AccentColorDescription": "Почти все интерактивные элементы пользовательского интерфейса окрашены в этот цвет",
|
|
||||||
"timestamps": "Метки времени",
|
|
||||||
"timestampsDescription": "Вы можете выбрать, каким образом метки времени показывают время",
|
|
||||||
"timestampsAbsoluteGMT": "Абсолютные (GMT)",
|
|
||||||
"timestampsAbsoluteLocalTime": "Абсолютные (Ваш часовой пояс)",
|
|
||||||
"timestampsRelative": "Относительные",
|
|
||||||
"rating": "Основное представление рейтинга",
|
|
||||||
"ratingDescription": "TR нелинеен, тогда как Glicko не имеет границ, а положение в таблице лидеров волатильно",
|
|
||||||
"ratingLBposition": "Позиция в рейтинге",
|
|
||||||
"sheetbotGraphs": "Графики-радары как у sheetBot",
|
|
||||||
"sheetbotGraphsDescription": "Если включено, точки на графике могут появляться на противоположной стороне графика если значение отрицательное",
|
|
||||||
"lbStats": "Показывать статистику, основанную на рейтинговой таблице",
|
"lbStats": "Показывать статистику, основанную на рейтинговой таблице",
|
||||||
"lbStatsDescription": "Это повлияет на время загрузки, но позволит видеть положение в рейтинге и сравнение со средними значениями по рангу по каждой стате",
|
"lbStatsDescription": "Это повлияет на время загрузки, но позволит видеть положение в рейтинге и сравнение со средними значениями по рангу по каждой стате",
|
||||||
"aboutApp": "О приложении",
|
"aboutApp": "О приложении",
|
||||||
"aboutAppText": "${appName} (${packageName}) Версия ${version} Сборка ${buildNumber}\n\nРазработал dan63047\nФормулы предоставил kerrmunism\nИсторию предоставляет p1nkl0bst3r\nВозможность скачивать повторы из TETR.IO предоставляет szy",
|
"aboutAppText": "${appName} (${packageName}) Версия ${version} Сборка ${buildNumber}\n\nРазработал dan63047\nФормулы предоставил kerrmunism\nИсторию предоставляет p1nkl0bst3r\nВозможность скачивать повторы из TETR.IO предоставляет szy",
|
||||||
|
"oskKagari": "\"Оск Кагари\" прикол",
|
||||||
|
"oskKagariDescription": "Если включено, вместо настоящего ранга оска будет рендерится :kagari:",
|
||||||
"stateViewTitle": "Аккаунт ${nickname} ${date}",
|
"stateViewTitle": "Аккаунт ${nickname} ${date}",
|
||||||
"statesViewTitle": "${number} состояний аккаунта ${nickname}",
|
"statesViewTitle": "${number} состояний аккаунта ${nickname}",
|
||||||
"matchesViewTitle": "Матчи аккаунта ${nickname}",
|
"matchesViewTitle": "Матчи аккаунта ${nickname}",
|
||||||
|
|
102
web/index.html
102
web/index.html
|
@ -36,115 +36,21 @@
|
||||||
// The value below is injected by flutter build, do not touch.
|
// The value below is injected by flutter build, do not touch.
|
||||||
var serviceWorkerVersion = null;
|
var serviceWorkerVersion = null;
|
||||||
</script>
|
</script>
|
||||||
<style>
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Eurostile Round';
|
|
||||||
src: local('Eurostile Round Medium'), local('EurostileRound-Medium'), url('https://dan63.by/static/EurostileRound-Medium.ttf') format('truetype');
|
|
||||||
font-weight: 500;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Eurostile Round Extended';
|
|
||||||
src: local('Eurostile Round Extended Regular'), local('EurostileRoundExtended-Regular'), url('https://dan63.by/static/EurostileRoundExtended-Regular.ttf') format('truetype');
|
|
||||||
font-weight: normal;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Eurostile Round Extended';
|
|
||||||
src: local('Eurostile Round Extended Black'), local('EurostileRoundExtended-Black'), url('https://dan63.by/static/EurostileRoundExtended-Black.ttf') format('truetype');
|
|
||||||
font-weight: 900;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
@keyframes breathing {
|
|
||||||
from{
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
to{
|
|
||||||
opacity: 0.3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*{
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
body{
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 100vw;
|
|
||||||
height: 100vh;
|
|
||||||
background-color: #000;
|
|
||||||
color: #fff;
|
|
||||||
font-family: 'Eurostile Round';
|
|
||||||
font-weight: 500;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
#preloader{
|
|
||||||
display: flex;
|
|
||||||
width: fit-content;
|
|
||||||
height: fit-content;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
background-color: #111;
|
|
||||||
border-radius: 24px;
|
|
||||||
padding: 8px;
|
|
||||||
background-image: linear-gradient(240deg, #0000009e, transparent);
|
|
||||||
}
|
|
||||||
.hidden{
|
|
||||||
display: none!important;
|
|
||||||
}
|
|
||||||
.title{
|
|
||||||
font-family: "Eurostile Round Extended";
|
|
||||||
font-weight: 100;
|
|
||||||
padding-bottom: 8px;
|
|
||||||
}
|
|
||||||
.subtitle{
|
|
||||||
color: grey;
|
|
||||||
}
|
|
||||||
.logo{
|
|
||||||
height: 128px;
|
|
||||||
padding-right: 8px;
|
|
||||||
border-radius: 24px;
|
|
||||||
}
|
|
||||||
#progress{
|
|
||||||
padding-top: 8px;
|
|
||||||
animation: 1s cubic-bezier(.46,.03,.52,.96) infinite alternate breathing;
|
|
||||||
}
|
|
||||||
@media (max-width: 502px){
|
|
||||||
#preloader{
|
|
||||||
flex-direction: column;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<!-- This script adds the flutter initialization JS code -->
|
<!-- This script adds the flutter initialization JS code -->
|
||||||
<script src="flutter.js" defer></script>
|
<script src="flutter.js" defer></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="preloader">
|
|
||||||
<img class="logo" src="icons/Icon-192.png" alt="Tetra Stats icon">
|
|
||||||
<div>
|
|
||||||
<h1 class="title">Tetra Stats</h1>
|
|
||||||
<p class="subtitle">Track your and other player stats in TETR.IO.<br>Made by dan63047</p>
|
|
||||||
<p id="progress">Loading...</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<script>
|
<script>
|
||||||
window.addEventListener('load', function(ev) {
|
window.addEventListener('load', function(ev) {
|
||||||
let progress = document.querySelector("#progress");
|
|
||||||
let preloader = document.querySelector("#preloader");
|
|
||||||
// Download main.dart.js
|
// Download main.dart.js
|
||||||
_flutter.loader.loadEntrypoint({
|
_flutter.loader.loadEntrypoint({
|
||||||
serviceWorker: {
|
serviceWorker: {
|
||||||
serviceWorkerVersion: serviceWorkerVersion,
|
serviceWorkerVersion: serviceWorkerVersion,
|
||||||
},
|
},
|
||||||
onEntrypointLoaded: async function(engineInitializer) {
|
onEntrypointLoaded: function(engineInitializer) {
|
||||||
console.log(serviceWorkerVersion);
|
engineInitializer.initializeEngine().then(function(appRunner) {
|
||||||
let appRunner = await engineInitializer.initializeEngine();
|
appRunner.runApp();
|
||||||
progress.innerHTML = "Booting...";
|
});
|
||||||
await appRunner.runApp();
|
|
||||||
preloader.classList.add("hidden");
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue