Compare commits
No commits in common. "712a52ad7b7c8f06f7ba725388ea37b4f4ff5e0e" and "0262c8dcf9dc868d86207236fce2ced1dcdc845c" have entirely different histories.
712a52ad7b
...
0262c8dcf9
|
@ -1222,7 +1222,7 @@ class News{
|
||||||
News(this.id, this.news);
|
News(this.id, this.news);
|
||||||
|
|
||||||
News.fromJson(Map<String, dynamic> json, String? userID){
|
News.fromJson(Map<String, dynamic> json, String? userID){
|
||||||
id = userID != null ? "user_$userID" : json['news'].first['stream'];
|
id = userID != null ? "user_${userID}" : json['news'].first['stream'];
|
||||||
news = [for (var entry in json['news']) NewsEntry.fromJson(entry)];
|
news = [for (var entry in json['news']) NewsEntry.fromJson(entry)];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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}';
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
import 'dart:isolate';
|
||||||
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 'package:flutter/services.dart';
|
||||||
import 'dart:developer' as developer;
|
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';
|
||||||
|
@ -27,37 +29,37 @@ late SharedPreferences prefs;
|
||||||
late TetrioService teto;
|
late TetrioService teto;
|
||||||
ThemeData theme = ThemeData(fontFamily: 'Eurostile Round', colorScheme: const ColorScheme.dark(primary: Colors.cyanAccent, secondary: Colors.white), scaffoldBackgroundColor: Colors.black);
|
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 {
|
Future<dynamic> computeIsolate(Future Function() function) async {
|
||||||
// final receivePort = ReceivePort();
|
final receivePort = ReceivePort();
|
||||||
// var rootToken = RootIsolateToken.instance!;
|
var rootToken = RootIsolateToken.instance!;
|
||||||
// await Isolate.spawn<_IsolateData>(
|
await Isolate.spawn<_IsolateData>(
|
||||||
// _isolateEntry,
|
_isolateEntry,
|
||||||
// _IsolateData(
|
_IsolateData(
|
||||||
// token: rootToken,
|
token: rootToken,
|
||||||
// function: function,
|
function: function,
|
||||||
// answerPort: receivePort.sendPort,
|
answerPort: receivePort.sendPort,
|
||||||
// ),
|
),
|
||||||
// );
|
);
|
||||||
// return await receivePort.first;
|
return await receivePort.first;
|
||||||
// }
|
}
|
||||||
|
|
||||||
// void _isolateEntry(_IsolateData isolateData) async {
|
void _isolateEntry(_IsolateData isolateData) async {
|
||||||
// BackgroundIsolateBinaryMessenger.ensureInitialized(isolateData.token);
|
BackgroundIsolateBinaryMessenger.ensureInitialized(isolateData.token);
|
||||||
// final answer = await isolateData.function();
|
final answer = await isolateData.function();
|
||||||
// isolateData.answerPort.send(answer);
|
isolateData.answerPort.send(answer);
|
||||||
// }
|
}
|
||||||
|
|
||||||
// class _IsolateData {
|
class _IsolateData {
|
||||||
// final RootIsolateToken token;
|
final RootIsolateToken token;
|
||||||
// final Function function;
|
final Function function;
|
||||||
// final SendPort answerPort;
|
final SendPort answerPort;
|
||||||
|
|
||||||
// _IsolateData({
|
_IsolateData({
|
||||||
// required this.token,
|
required this.token,
|
||||||
// required this.function,
|
required this.function,
|
||||||
// required this.answerPort,
|
required this.answerPort,
|
||||||
// });
|
});
|
||||||
// }
|
}
|
||||||
|
|
||||||
final router = GoRouter(
|
final router = GoRouter(
|
||||||
initialLocation: "/",
|
initialLocation: "/",
|
||||||
|
@ -143,9 +145,9 @@ void main() async {
|
||||||
}
|
}
|
||||||
|
|
||||||
// I dont want to store old cache
|
// I dont want to store old cache
|
||||||
Timer.periodic(const Duration(minutes: 5), (Timer timer) {
|
Timer.periodic(Duration(minutes: 5), (Timer timer) {
|
||||||
teto.cacheRoutine();
|
teto.cacheRoutine();
|
||||||
developer.log("Cache routine complete, next one in ${DateTime.now().add(const Duration(minutes: 5))}", name: "main");
|
developer.log("Cache routine complete, next one in ${DateTime.now().add(Duration(minutes: 5))}", name: "main");
|
||||||
// if (prefs.getBool("updateInBG") == true) teto.fetchTracked(); // TODO: Somehow avoid doing that in main isolate
|
// if (prefs.getBool("updateInBG") == true) teto.fetchTracked(); // TODO: Somehow avoid doing that in main isolate
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -163,12 +165,6 @@ class MyApp extends StatefulWidget {
|
||||||
|
|
||||||
class MyAppState extends State<MyApp> {
|
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???
|
void setAccentColor(Color color){ // does this thing work??? yes??? no???
|
||||||
setState(() {
|
setState(() {
|
||||||
theme = theme.copyWith(colorScheme: theme.colorScheme.copyWith(primary: color));
|
theme = theme.copyWith(colorScheme: theme.colorScheme.copyWith(primary: color));
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
// 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;
|
||||||
|
@ -89,7 +87,7 @@ class CacheController {
|
||||||
case Cutoffs:
|
case Cutoffs:
|
||||||
return object.runtimeType.toString();
|
return object.runtimeType.toString();
|
||||||
case TetrioPlayerFromLeaderboard: // i may be a little stupid
|
case TetrioPlayerFromLeaderboard: // i may be a little stupid
|
||||||
return "${object.runtimeType}topone";
|
return object.runtimeType.toString()+"topone";
|
||||||
case TetraLeagueAlphaStream:
|
case TetraLeagueAlphaStream:
|
||||||
return object.runtimeType.toString()+object.userId;
|
return object.runtimeType.toString()+object.userId;
|
||||||
case SingleplayerStream:
|
case SingleplayerStream:
|
||||||
|
|
|
@ -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';
|
||||||
|
|
|
@ -3,7 +3,7 @@ import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_colorpicker/flutter_colorpicker.dart';
|
import 'package:flutter_colorpicker/flutter_colorpicker.dart';
|
||||||
import 'package:tetra_stats/views/settings_view.dart' show subtitleStyle;
|
import 'package:tetra_stats/views/settings_view.dart' show subtitleStyle;
|
||||||
import 'package:tetra_stats/main.dart' show MyAppState, prefs;
|
import 'package:tetra_stats/main.dart' show MyApp, MyAppState, prefs, setAccentColor;
|
||||||
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';
|
||||||
|
|
||||||
|
@ -89,8 +89,7 @@ class CustomizationState extends State<CustomizationView> {
|
||||||
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), width: 25, height: 25),
|
trailing: ColorIndicator(HSVColor.fromColor(Theme.of(context).colorScheme.primary), width: 25, height: 25),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
showDialog(
|
showDialog(
|
||||||
|
@ -109,7 +108,6 @@ class CustomizationState extends State<CustomizationView> {
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
context.findAncestorStateOfType<MyAppState>()?.setAccentColor(pickerColor);
|
context.findAncestorStateOfType<MyAppState>()?.setAccentColor(pickerColor);
|
||||||
prefs.setInt("accentColor", pickerColor.value);
|
|
||||||
});
|
});
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
|
@ -121,14 +119,22 @@ class CustomizationState extends State<CustomizationView> {
|
||||||
// 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),
|
ListTile(title: Text(t.oskKagari),
|
||||||
subtitle: Text(t.timestampsDescription, style: subtitleStyle),
|
subtitle: Text(t.oskKagariDescription, style: subtitleStyle),
|
||||||
|
trailing: Switch(value: oskKagariGimmick, onChanged: (bool value){
|
||||||
|
prefs.setBool("oskKagariGimmick", value);
|
||||||
|
setState(() {
|
||||||
|
oskKagariGimmick = value;
|
||||||
|
});
|
||||||
|
}),),
|
||||||
|
ListTile(title: Text("Timestamps"),
|
||||||
|
subtitle: Text(t.oskKagariDescription, style: subtitleStyle),
|
||||||
trailing: DropdownButton(
|
trailing: DropdownButton(
|
||||||
value: timestampMode,
|
value: timestampMode,
|
||||||
items: <DropdownMenuItem>[
|
items: <DropdownMenuItem>[
|
||||||
DropdownMenuItem(value: 0, child: Text(t.timestampsAbsoluteGMT)),
|
DropdownMenuItem(value: 0, child: Text("Absolute (GMT)")),
|
||||||
DropdownMenuItem(value: 1, child: Text(t.timestampsAbsoluteLocalTime)),
|
DropdownMenuItem(value: 1, child: Text("Absolute (Local Time)")),
|
||||||
DropdownMenuItem(value: 2, child: Text(t.timestampsRelative))
|
DropdownMenuItem(value: 2, child: Text("Relative"))
|
||||||
],
|
],
|
||||||
onChanged: (dynamic value){
|
onChanged: (dynamic value){
|
||||||
prefs.setInt("timestampMode", value);
|
prefs.setInt("timestampMode", value);
|
||||||
|
@ -138,14 +144,14 @@ class CustomizationState extends State<CustomizationView> {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
ListTile(title: Text(t.rating),
|
ListTile(title: Text("Main representation of rating"),
|
||||||
subtitle: Text(t.ratingDescription, style: subtitleStyle),
|
subtitle: Text(t.oskKagariDescription, style: subtitleStyle),
|
||||||
trailing: DropdownButton(
|
trailing: DropdownButton(
|
||||||
value: ratingMode,
|
value: ratingMode,
|
||||||
items: <DropdownMenuItem>[
|
items: <DropdownMenuItem>[
|
||||||
const DropdownMenuItem(value: 0, child: Text("TR")),
|
DropdownMenuItem(value: 0, child: Text("TR")),
|
||||||
const DropdownMenuItem(value: 1, child: Text("Glicko")),
|
DropdownMenuItem(value: 1, child: Text("Glicko")),
|
||||||
DropdownMenuItem(value: 2, child: Text(t.ratingLBposition))
|
DropdownMenuItem(value: 2, child: Text("LB position"))
|
||||||
],
|
],
|
||||||
onChanged: (dynamic value){
|
onChanged: (dynamic value){
|
||||||
prefs.setInt("ratingMode", value);
|
prefs.setInt("ratingMode", value);
|
||||||
|
@ -155,21 +161,13 @@ class CustomizationState extends State<CustomizationView> {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
ListTile(title: Text(t.sheetbotGraphs),
|
ListTile(title: Text("Sheetbot-like behavior for radar graphs"),
|
||||||
subtitle: Text(t.sheetbotGraphsDescription, style: subtitleStyle),
|
subtitle: Text(t.oskKagariDescription, style: subtitleStyle),
|
||||||
trailing: Switch(value: sheetbotRadarGraphs, onChanged: (bool value){
|
trailing: Switch(value: sheetbotRadarGraphs, onChanged: (bool value){
|
||||||
prefs.setBool("sheetbotRadarGraphs", value);
|
prefs.setBool("sheetbotRadarGraphs", value);
|
||||||
setState(() {
|
setState(() {
|
||||||
sheetbotRadarGraphs = value;
|
sheetbotRadarGraphs = value;
|
||||||
});
|
});
|
||||||
}),),
|
|
||||||
ListTile(title: Text(t.oskKagari),
|
|
||||||
subtitle: Text(t.oskKagariDescription, style: subtitleStyle),
|
|
||||||
trailing: Switch(value: oskKagariGimmick, onChanged: (bool value){
|
|
||||||
prefs.setBool("oskKagariGimmick", value);
|
|
||||||
setState(() {
|
|
||||||
oskKagariGimmick = value;
|
|
||||||
});
|
|
||||||
}),)
|
}),)
|
||||||
],
|
],
|
||||||
)),
|
)),
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// 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:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
@ -75,7 +75,7 @@ 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), (){});
|
Timer backgroundUpdate = Timer(Duration(days: 365), (){});
|
||||||
bool _TLHistoryWasFetched = false;
|
bool _TLHistoryWasFetched = false;
|
||||||
late TabController _tabController;
|
late TabController _tabController;
|
||||||
late TabController _wideScreenTabController;
|
late TabController _wideScreenTabController;
|
||||||
|
@ -438,7 +438,7 @@ 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: "Recent runs"),
|
||||||
Tab(text: t.other),
|
Tab(text: t.other),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -743,7 +743,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(timestamp(data[index].timestamp), style: TextStyle(color: Colors.grey)),
|
||||||
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,
|
||||||
|
@ -1089,8 +1089,8 @@ class _TwoRecordsThingy extends StatelessWidget {
|
||||||
crossAxisAlignment: WrapCrossAlignment.start,
|
crossAxisAlignment: WrapCrossAlignment.start,
|
||||||
spacing: 20,
|
spacing: 20,
|
||||||
children: [
|
children: [
|
||||||
TextButton(onPressed: (){launchInBrowser(Uri.parse("https://tetr.io/#r:${sprint!.replayId}"));}, child: Text(t.openSPreplay)),
|
TextButton(onPressed: (){launchInBrowser(Uri.parse("https://tetr.io/#r:${sprint!.replayId}"));}, child: Text("Open replay in TETR.IO")),
|
||||||
TextButton(onPressed: (){launchInBrowser(Uri.parse("https://inoue.szy.lol/api/replay/${sprint!.replayId}"));}, child: Text(t.downloadSPreplay)),
|
TextButton(onPressed: (){launchInBrowser(Uri.parse("https://inoue.szy.lol/api/replay/${sprint!.replayId}"));}, child: Text("Download replay")),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (sprintStream.records.length > 1) SizedBox(
|
if (sprintStream.records.length > 1) SizedBox(
|
||||||
|
@ -1102,8 +1102,8 @@ class _TwoRecordsThingy extends StatelessWidget {
|
||||||
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (context) => SingleplayerRecordView(record: sprintStream.records[i]))),
|
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) ),
|
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),
|
title: Text(get40lTime(sprintStream.records[i].endContext.finalTime.inMicroseconds),
|
||||||
style: const TextStyle(fontSize: 18)),
|
style: TextStyle(fontSize: 18)),
|
||||||
subtitle: Text(timestamp(sprintStream.records[i].timestamp), style: const TextStyle(color: Colors.grey, height: 0.85)),
|
subtitle: Text(timestamp(sprintStream.records[i].timestamp), style: TextStyle(color: Colors.grey, height: 0.85)),
|
||||||
trailing: SpTrailingStats(sprintStream.records[i].endContext)
|
trailing: SpTrailingStats(sprintStream.records[i].endContext)
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
@ -1175,8 +1175,8 @@ class _TwoRecordsThingy extends StatelessWidget {
|
||||||
crossAxisAlignment: WrapCrossAlignment.start,
|
crossAxisAlignment: WrapCrossAlignment.start,
|
||||||
spacing: 20,
|
spacing: 20,
|
||||||
children: [
|
children: [
|
||||||
TextButton(onPressed: (){launchInBrowser(Uri.parse("https://tetr.io/#r:${blitz!.replayId}"));}, child: Text(t.openSPreplay)),
|
TextButton(onPressed: (){launchInBrowser(Uri.parse("https://tetr.io/#r:${blitz!.replayId}"));}, child: Text("Open replay in TETR.IO")),
|
||||||
TextButton(onPressed: (){launchInBrowser(Uri.parse("https://inoue.szy.lol/api/replay/${blitz!.replayId}"));}, child: Text(t.downloadSPreplay)),
|
TextButton(onPressed: (){launchInBrowser(Uri.parse("https://inoue.szy.lol/api/replay/${blitz!.replayId}"));}, child: Text("Download replay")),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (blitzStream.records.length > 1) SizedBox(
|
if (blitzStream.records.length > 1) SizedBox(
|
||||||
|
@ -1188,8 +1188,8 @@ class _TwoRecordsThingy extends StatelessWidget {
|
||||||
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (context) => SingleplayerRecordView(record: blitzStream.records[i]))),
|
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) ),
|
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",
|
title: Text("${NumberFormat.decimalPattern().format(blitzStream.records[i].endContext.score)} points",
|
||||||
style: const TextStyle(fontSize: 18)),
|
style: TextStyle(fontSize: 18)),
|
||||||
subtitle: Text(timestamp(blitzStream.records[i].timestamp), style: const TextStyle(color: Colors.grey, height: 0.85)),
|
subtitle: Text(timestamp(blitzStream.records[i].timestamp), style: TextStyle(color: Colors.grey, height: 0.85)),
|
||||||
trailing: SpTrailingStats(blitzStream.records[i].endContext)
|
trailing: SpTrailingStats(blitzStream.records[i].endContext)
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
|
|
@ -263,8 +263,8 @@ class SettingsState extends State<SettingsView> {
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.go("/settings/customization");
|
context.go("/settings/customization");
|
||||||
},),
|
},),
|
||||||
ListTile(title: Text(t.updateInBackground),
|
ListTile(title: Text("Update stats in the background"),
|
||||||
subtitle: Text(t.updateInBackgroundDescription, style: const TextStyle(fontFamily: "Eurostile Round Condensed", color: Colors.grey)),
|
subtitle: Text("While tetra stats is running, it can update stats of the current player when cache expires", style: const TextStyle(fontFamily: "Eurostile Round Condensed", color: Colors.grey)),
|
||||||
trailing: Switch(value: updateInBG, onChanged: (bool value){
|
trailing: Switch(value: updateInBG, onChanged: (bool value){
|
||||||
prefs.setBool("updateInBG", value);
|
prefs.setBool("updateInBG", value);
|
||||||
setState(() {
|
setState(() {
|
||||||
|
|
|
@ -60,7 +60,7 @@ 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: [
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// 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';
|
||||||
|
|
|
@ -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';
|
||||||
|
|
|
@ -5,6 +5,7 @@ import 'package:tetra_stats/gen/strings.g.dart';
|
||||||
import 'package:tetra_stats/utils/relative_timestamps.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/singleplayer_record_view.dart';
|
||||||
|
import 'package:tetra_stats/widgets/singleplayer_record.dart';
|
||||||
import 'package:tetra_stats/widgets/sp_trailing_stats.dart';
|
import 'package:tetra_stats/widgets/sp_trailing_stats.dart';
|
||||||
import 'package:tetra_stats/widgets/text_timestamp.dart';
|
import 'package:tetra_stats/widgets/text_timestamp.dart';
|
||||||
|
|
||||||
|
@ -18,9 +19,9 @@ class RecentSingleplayerGames extends StatelessWidget{
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
if (!hideTitle) Padding(
|
if (!hideTitle) const Padding(
|
||||||
padding: const EdgeInsets.only(bottom: 8.0),
|
padding: EdgeInsets.only(bottom: 8.0),
|
||||||
child: Text(t.recent, style: const TextStyle(height: 0.1, fontFamily: "Eurostile Round Extended", fontSize: 18)),
|
child: Text("Recent", style: TextStyle(height: 0.1, fontFamily: "Eurostile Round Extended", fontSize: 18)),
|
||||||
),
|
),
|
||||||
for(RecordSingle record in recent.records) ListTile(
|
for(RecordSingle record in recent.records) ListTile(
|
||||||
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (context) => SingleplayerRecordView(record: record))),
|
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (context) => SingleplayerRecordView(record: record))),
|
||||||
|
@ -36,12 +37,12 @@ class RecentSingleplayerGames extends StatelessWidget{
|
||||||
title: Text(
|
title: Text(
|
||||||
switch (record.endContext.gameType){
|
switch (record.endContext.gameType){
|
||||||
"40l" => get40lTime(record.endContext.finalTime.inMicroseconds),
|
"40l" => get40lTime(record.endContext.finalTime.inMicroseconds),
|
||||||
"blitz" => t.blitzScore(p: NumberFormat.decimalPattern().format(record.endContext.score)),
|
"blitz" => "${NumberFormat.decimalPattern().format(record.endContext.score)} points",
|
||||||
"5mblast" => get40lTime(record.endContext.finalTime.inMicroseconds),
|
"5mblast" => get40lTime(record.endContext.finalTime.inMicroseconds),
|
||||||
String() => "huh",
|
String() => "huh",
|
||||||
},
|
},
|
||||||
style: const TextStyle(fontSize: 18)),
|
style: const TextStyle(fontSize: 18)),
|
||||||
subtitle: Text(timestamp(record.timestamp), style: const TextStyle(color: Colors.grey, height: 0.85)),
|
subtitle: Text(timestamp(record.timestamp), style: TextStyle(color: Colors.grey, height: 0.85)),
|
||||||
trailing: SpTrailingStats(record.endContext)
|
trailing: SpTrailingStats(record.endContext)
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
|
|
@ -131,8 +131,8 @@ class SingleplayerRecord extends StatelessWidget {
|
||||||
crossAxisAlignment: WrapCrossAlignment.start,
|
crossAxisAlignment: WrapCrossAlignment.start,
|
||||||
spacing: 20,
|
spacing: 20,
|
||||||
children: [
|
children: [
|
||||||
TextButton(onPressed: (){launchInBrowser(Uri.parse("https://tetr.io/#r:${record!.replayId}"));}, child: Text(t.openSPreplay)),
|
TextButton(onPressed: (){launchInBrowser(Uri.parse("https://tetr.io/#r:${record!.replayId}"));}, child: Text("Open replay in TETR.IO")),
|
||||||
TextButton(onPressed: (){launchInBrowser(Uri.parse("https://inoue.szy.lol/api/replay/${record!.replayId}"));}, child: Text(t.downloadSPreplay)),
|
TextButton(onPressed: (){launchInBrowser(Uri.parse("https://inoue.szy.lol/api/replay/${record!.replayId}"));}, child: Text("Download replay")),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (stream != null && stream!.records.length > 1) for(int i = 1; i < stream!.records.length; i++) ListTile(
|
if (stream != null && stream!.records.length > 1) for(int i = 1; i < stream!.records.length; i++) ListTile(
|
||||||
|
@ -143,12 +143,12 @@ class SingleplayerRecord extends StatelessWidget {
|
||||||
title: Text(
|
title: Text(
|
||||||
switch (stream!.records[i].endContext.gameType){
|
switch (stream!.records[i].endContext.gameType){
|
||||||
"40l" => get40lTime(stream!.records[i].endContext.finalTime.inMicroseconds),
|
"40l" => get40lTime(stream!.records[i].endContext.finalTime.inMicroseconds),
|
||||||
"blitz" => t.blitzScore(p: NumberFormat.decimalPattern().format(stream!.records[i].endContext.score)),
|
"blitz" => "${NumberFormat.decimalPattern().format(stream!.records[i].endContext.score)} points",
|
||||||
"5mblast" => get40lTime(stream!.records[i].endContext.finalTime.inMicroseconds),
|
"5mblast" => get40lTime(stream!.records[i].endContext.finalTime.inMicroseconds),
|
||||||
String() => "huh",
|
String() => "huh",
|
||||||
},
|
},
|
||||||
style: const TextStyle(fontSize: 18)),
|
style: TextStyle(fontSize: 18)),
|
||||||
subtitle: Text(timestamp(stream!.records[i].timestamp), style: const TextStyle(color: Colors.grey, height: 0.85)),
|
subtitle: Text(timestamp(stream!.records[i].timestamp), style: TextStyle(color: Colors.grey, height: 0.85)),
|
||||||
trailing: SpTrailingStats(stream!.records[i].endContext)
|
trailing: SpTrailingStats(stream!.records[i].endContext)
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
|
@ -5,7 +5,7 @@ import 'package:tetra_stats/utils/numers_formats.dart';
|
||||||
class SpTrailingStats extends StatelessWidget{
|
class SpTrailingStats extends StatelessWidget{
|
||||||
final EndContextSingle endContext;
|
final EndContextSingle endContext;
|
||||||
|
|
||||||
const SpTrailingStats(this.endContext, {super.key});
|
const SpTrailingStats(this.endContext);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
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';
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.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/main.dart';
|
||||||
|
|
|
@ -13,7 +13,7 @@ class TLRatingThingy extends StatelessWidget{
|
||||||
final TetraLeagueAlpha? oldTl;
|
final TetraLeagueAlpha? oldTl;
|
||||||
final double? topTR;
|
final double? topTR;
|
||||||
|
|
||||||
const TLRatingThingy({super.key, required this.userID, required this.tlData, this.oldTl, this.topTR});
|
const TLRatingThingy({required this.userID, required this.tlData, this.oldTl, this.topTR});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -44,7 +44,7 @@ class TLRatingThingy extends StatelessWidget{
|
||||||
TextSpan(text: " Glicko", style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28))
|
TextSpan(text: " Glicko", style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28))
|
||||||
],
|
],
|
||||||
2 => [
|
2 => [
|
||||||
TextSpan(text: "${t.top} ${formatedPercentile[0]}", style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)),
|
TextSpan(text: "Top ${formatedPercentile[0]}", style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)),
|
||||||
if (formatedPercentile.elementAtOrNull(1) != null) TextSpan(text: decimalSeparator + formatedPercentile[1]),
|
if (formatedPercentile.elementAtOrNull(1) != null) TextSpan(text: decimalSeparator + formatedPercentile[1]),
|
||||||
TextSpan(text: " %", style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28))
|
TextSpan(text: " %", style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28))
|
||||||
],
|
],
|
||||||
|
@ -77,7 +77,7 @@ class TLRatingThingy extends StatelessWidget{
|
||||||
text: TextSpan(
|
text: TextSpan(
|
||||||
style: DefaultTextStyle.of(context).style,
|
style: DefaultTextStyle.of(context).style,
|
||||||
children: [
|
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()})"),
|
TextSpan(text: prefs.getInt("ratingMode") == 2 ? "${f2.format(tlData.rating)} TR • % 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") const TextSpan(text: " • "),
|
||||||
if (tlData.bestRank != "z") TextSpan(text: "${t.topRank}: ${tlData.bestRank.toUpperCase()}"),
|
if (tlData.bestRank != "z") TextSpan(text: "${t.topRank}: ${tlData.bestRank.toUpperCase()}"),
|
||||||
if (topTR != null) TextSpan(text: " (${f2.format(topTR)} TR)"),
|
if (topTR != null) TextSpan(text: " (${f2.format(topTR)} TR)"),
|
||||||
|
|
|
@ -58,7 +58,7 @@ class _TLThingyState extends State<TLThingy> {
|
||||||
final t = Translations.of(context);
|
final t = Translations.of(context);
|
||||||
String decimalSeparator = f2.symbols.DECIMAL_SEP;
|
String decimalSeparator = f2.symbols.DECIMAL_SEP;
|
||||||
List<String> estTRformated = f2.format(currentTl.estTr!.esttr).split(decimalSeparator);
|
List<String> estTRformated = f2.format(currentTl.estTr!.esttr).split(decimalSeparator);
|
||||||
List<String> estTRaccFormated = intFDiff.format(currentTl.esttracc!).split(".");
|
List<String> estTRaccFormated = intFDiff.format(currentTl.esttracc!).split(decimalSeparator);
|
||||||
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;
|
||||||
|
@ -245,10 +245,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(
|
||||||
|
|
|
@ -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}",
|
||||||
|
|
Loading…
Reference in New Issue