App now can tell what happening with connection

Also now we can manage stored matches
This commit is contained in:
dan63047 2023-09-23 22:09:36 +03:00
parent 2c4c72aa1a
commit 7ed93d3fb1
14 changed files with 585 additions and 338 deletions

View File

@ -4,9 +4,9 @@
/// To regenerate, run: `dart run slang` /// To regenerate, run: `dart run slang`
/// ///
/// Locales: 2 /// Locales: 2
/// Strings: 914 (457 per locale) /// Strings: 940 (470 per locale)
/// ///
/// Built on 2023-09-06 at 18:46 UTC /// Built on 2023-09-23 at 18:57 UTC
// coverage:ignore-file // coverage:ignore-file
// ignore_for_file: type=lint // ignore_for_file: type=lint
@ -223,8 +223,11 @@ class _StringsEn implements BaseTranslations<AppLocale, _StringsEn> {
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'; 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';
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 statesViewEntry({required Object level, required Object gameTime, required Object friends, required Object rd}) => 'Level ${level}, ${gameTime} of gametime, ${friends} friends, ${rd} RD'; String statesViewEntry({required Object level, required Object gameTime, required Object friends, required Object rd}) => 'Level ${level}, ${gameTime} of gametime, ${friends} friends, ${rd} RD';
String stateRemoved({required Object date}) => '${date} state was removed from database!'; String stateRemoved({required Object date}) => '${date} state was removed from database!';
String matchRemoved({required Object date}) => '${date} match was removed from database!';
String get viewAllMatches => 'View all matches';
String get trackedPlayersViewTitle => 'Stored data'; String get trackedPlayersViewTitle => 'Stored data';
String get trackedPlayersZeroEntrys => 'Empty list. Press "Track" button in previous view to add current player here'; String get trackedPlayersZeroEntrys => 'Empty list. Press "Track" button in previous view to add current player here';
String get trackedPlayersOneEntry => 'There is only one player'; String get trackedPlayersOneEntry => 'There is only one player';
@ -654,7 +657,17 @@ class _StringsErrorsEn {
// Translations // Translations
String connection({required Object code, required Object message}) => 'Some issue with connection: ${code} ${message}'; String connection({required Object code, required Object message}) => 'Some issue with connection: ${code} ${message}';
String get noSuchUser => 'No such user'; String get noSuchUser => 'No such user';
String socketException({required Object host, required Object message}) => 'Can\'t connect with ${host}: ${message}'; String get history => 'History for that player is missing';
String get clientException => 'No internet connection';
String get forbidden => 'Your IP address is blocked.\nChange IP address or reach out to osk';
String get tooManyRequests => 'You have been rate limited. Try again later';
String get internal => 'Something happend on the tetr.io side';
String get internalWebVersion => 'Something happend on the tetr.io side (or on oskware_bridge, idk honestly)';
String get oskwareBridge => 'Something happend with oskware_bridge. Let dan63047 know';
String get p1nkl0bst3rForbidden => 'Third party API blocked your IP address.\nChange IP address or reach out to p1nkl0bst3r';
String get p1nkl0bst3rTooManyRequests => 'Too many requests to third party API. Try again later';
String get p1nkl0bst3rinternal => 'Something happend on the p1nkl0bst3r side';
String get p1nkl0bst3rinternalWebVersion => 'Something happend on the p1nkl0bst3r side (or on oskware_bridge, idk honestly)';
} }
// Path: <root> // Path: <root>
@ -755,8 +768,11 @@ class _StringsRu implements _StringsEn {
@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'; @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';
@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 statesViewEntry({required Object level, required Object gameTime, required Object friends, required Object rd}) => '${level} уровень, ${gameTime} сыграно, ${friends} друзей, ${rd} RD'; @override String statesViewEntry({required Object level, required Object gameTime, required Object friends, required Object rd}) => '${level} уровень, ${gameTime} сыграно, ${friends} друзей, ${rd} RD';
@override String stateRemoved({required Object date}) => 'Состояние от ${date} было удалено из локальной базы данных!'; @override String stateRemoved({required Object date}) => 'Состояние от ${date} было удалено из локальной базы данных!';
@override String matchRemoved({required Object date}) => 'Матч от ${date} был удален из локальной базы данных!';
@override String get viewAllMatches => 'Все матчи';
@override String get trackedPlayersViewTitle => 'Сохранённые данные'; @override String get trackedPlayersViewTitle => 'Сохранённые данные';
@override String get trackedPlayersZeroEntrys => 'Пустой список. Вернитесь на предыдущий экран и нажмите кнопку "Отслеживать", чтобы текущий игрок появился здесь'; @override String get trackedPlayersZeroEntrys => 'Пустой список. Вернитесь на предыдущий экран и нажмите кнопку "Отслеживать", чтобы текущий игрок появился здесь';
@override String get trackedPlayersOneEntry => 'В списке только один игрок'; @override String get trackedPlayersOneEntry => 'В списке только один игрок';
@ -1186,7 +1202,17 @@ class _StringsErrorsRu implements _StringsErrorsEn {
// Translations // Translations
@override String connection({required Object code, required Object message}) => 'Проблема с подключением: ${code} ${message}'; @override String connection({required Object code, required Object message}) => 'Проблема с подключением: ${code} ${message}';
@override String get noSuchUser => 'Нет такого пользователя'; @override String get noSuchUser => 'Нет такого пользователя';
@override String socketException({required Object host, required Object message}) => 'Невозможно подключиться к ${host}: ${message}'; @override String get history => 'История данного игрока отсутствует';
@override String get clientException => 'Нет соединения с интернетом';
@override String get forbidden => 'Ваш IP адрес заблокирован.\nСмените IP адрес или свяжитесь с osk-ом';
@override String get tooManyRequests => 'Слишком много запросов. Попробуйте позже';
@override String get internal => 'Что-то случилось на стороне tetr.io';
@override String get internalWebVersion => 'Что-то случилось на стороне tetr.io (или на стороне oskware_bridge, я хз если честно)';
@override String get oskwareBridge => 'Что-то случилось с oskware_bridge. Дайте dan63047 знать';
@override String get p1nkl0bst3rForbidden => 'Стороннее API заблокировало ваш IP адрес.\nСмените IP адрес или свяжитесь с p1nkl0bst3r-ом';
@override String get p1nkl0bst3rTooManyRequests => 'Слишком много запросов к стороннему API. Попробуйте позже';
@override String get p1nkl0bst3rinternal => 'Что-то случилось на стороне p1nkl0bst3r-а';
@override String get p1nkl0bst3rinternalWebVersion => 'Что-то случилось на стороне p1nkl0bst3r-а (или на стороне oskware_bridge, я хз если честно)';
} }
/// Flat map(s) containing all translations. /// Flat map(s) containing all translations.
@ -1266,8 +1292,11 @@ extension on _StringsEn {
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'; 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';
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 'statesViewEntry': return ({required Object level, required Object gameTime, required Object friends, required Object rd}) => 'Level ${level}, ${gameTime} of gametime, ${friends} friends, ${rd} RD'; case 'statesViewEntry': return ({required Object level, required Object gameTime, required Object friends, required Object rd}) => 'Level ${level}, ${gameTime} of gametime, ${friends} friends, ${rd} RD';
case 'stateRemoved': return ({required Object date}) => '${date} state was removed from database!'; case 'stateRemoved': return ({required Object date}) => '${date} state was removed from database!';
case 'matchRemoved': return ({required Object date}) => '${date} match was removed from database!';
case 'viewAllMatches': return 'View all matches';
case 'trackedPlayersViewTitle': return 'Stored data'; case 'trackedPlayersViewTitle': return 'Stored data';
case 'trackedPlayersZeroEntrys': return 'Empty list. Press "Track" button in previous view to add current player here'; case 'trackedPlayersZeroEntrys': return 'Empty list. Press "Track" button in previous view to add current player here';
case 'trackedPlayersOneEntry': return 'There is only one player'; case 'trackedPlayersOneEntry': return 'There is only one player';
@ -1394,7 +1423,17 @@ extension on _StringsEn {
case 'popupActions.ok': return 'OK'; case 'popupActions.ok': return 'OK';
case 'errors.connection': return ({required Object code, required Object message}) => 'Some issue with connection: ${code} ${message}'; case 'errors.connection': return ({required Object code, required Object message}) => 'Some issue with connection: ${code} ${message}';
case 'errors.noSuchUser': return 'No such user'; case 'errors.noSuchUser': return 'No such user';
case 'errors.socketException': return ({required Object host, required Object message}) => 'Can\'t connect with ${host}: ${message}'; case 'errors.history': return 'History for that player is missing';
case 'errors.clientException': return 'No internet connection';
case 'errors.forbidden': return 'Your IP address is blocked.\nChange IP address or reach out to osk';
case 'errors.tooManyRequests': return 'You have been rate limited. Try again later';
case 'errors.internal': return 'Something happend on the tetr.io side';
case 'errors.internalWebVersion': return 'Something happend on the tetr.io side (or on oskware_bridge, idk honestly)';
case 'errors.oskwareBridge': return 'Something happend with oskware_bridge. Let dan63047 know';
case 'errors.p1nkl0bst3rForbidden': return 'Third party API blocked your IP address.\nChange IP address or reach out to p1nkl0bst3r';
case 'errors.p1nkl0bst3rTooManyRequests': return 'Too many requests to third party API. Try again later';
case 'errors.p1nkl0bst3rinternal': return 'Something happend on the p1nkl0bst3r side';
case 'errors.p1nkl0bst3rinternalWebVersion': return 'Something happend on the p1nkl0bst3r side (or on oskware_bridge, idk honestly)';
case 'countries.': return 'Not selected'; case 'countries.': return 'Not selected';
case 'countries.AF': return 'Afghanistan'; case 'countries.AF': return 'Afghanistan';
case 'countries.AX': return 'Åland Islands'; case 'countries.AX': return 'Åland Islands';
@ -1733,8 +1772,11 @@ extension on _StringsRu {
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'; 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';
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 'statesViewEntry': return ({required Object level, required Object gameTime, required Object friends, required Object rd}) => '${level} уровень, ${gameTime} сыграно, ${friends} друзей, ${rd} RD'; case 'statesViewEntry': return ({required Object level, required Object gameTime, required Object friends, required Object rd}) => '${level} уровень, ${gameTime} сыграно, ${friends} друзей, ${rd} RD';
case 'stateRemoved': return ({required Object date}) => 'Состояние от ${date} было удалено из локальной базы данных!'; case 'stateRemoved': return ({required Object date}) => 'Состояние от ${date} было удалено из локальной базы данных!';
case 'matchRemoved': return ({required Object date}) => 'Матч от ${date} был удален из локальной базы данных!';
case 'viewAllMatches': return 'Все матчи';
case 'trackedPlayersViewTitle': return 'Сохранённые данные'; case 'trackedPlayersViewTitle': return 'Сохранённые данные';
case 'trackedPlayersZeroEntrys': return 'Пустой список. Вернитесь на предыдущий экран и нажмите кнопку "Отслеживать", чтобы текущий игрок появился здесь'; case 'trackedPlayersZeroEntrys': return 'Пустой список. Вернитесь на предыдущий экран и нажмите кнопку "Отслеживать", чтобы текущий игрок появился здесь';
case 'trackedPlayersOneEntry': return 'В списке только один игрок'; case 'trackedPlayersOneEntry': return 'В списке только один игрок';
@ -1861,7 +1903,17 @@ extension on _StringsRu {
case 'popupActions.ok': return 'OK'; case 'popupActions.ok': return 'OK';
case 'errors.connection': return ({required Object code, required Object message}) => 'Проблема с подключением: ${code} ${message}'; case 'errors.connection': return ({required Object code, required Object message}) => 'Проблема с подключением: ${code} ${message}';
case 'errors.noSuchUser': return 'Нет такого пользователя'; case 'errors.noSuchUser': return 'Нет такого пользователя';
case 'errors.socketException': return ({required Object host, required Object message}) => 'Невозможно подключиться к ${host}: ${message}'; case 'errors.history': return 'История данного игрока отсутствует';
case 'errors.clientException': return 'Нет соединения с интернетом';
case 'errors.forbidden': return 'Ваш IP адрес заблокирован.\nСмените IP адрес или свяжитесь с osk-ом';
case 'errors.tooManyRequests': return 'Слишком много запросов. Попробуйте позже';
case 'errors.internal': return 'Что-то случилось на стороне tetr.io';
case 'errors.internalWebVersion': return 'Что-то случилось на стороне tetr.io (или на стороне oskware_bridge, я хз если честно)';
case 'errors.oskwareBridge': return 'Что-то случилось с oskware_bridge. Дайте dan63047 знать';
case 'errors.p1nkl0bst3rForbidden': return 'Стороннее API заблокировало ваш IP адрес.\nСмените IP адрес или свяжитесь с p1nkl0bst3r-ом';
case 'errors.p1nkl0bst3rTooManyRequests': return 'Слишком много запросов к стороннему API. Попробуйте позже';
case 'errors.p1nkl0bst3rinternal': return 'Что-то случилось на стороне p1nkl0bst3r-а';
case 'errors.p1nkl0bst3rinternalWebVersion': return 'Что-то случилось на стороне p1nkl0bst3r-а (или на стороне oskware_bridge, я хз если честно)';
case 'countries.': return 'Не выбрана'; case 'countries.': return 'Не выбрана';
case 'countries.AF': return 'Афганистан'; case 'countries.AF': return 'Афганистан';
case 'countries.AX': return 'Аландские острова'; case 'countries.AX': return 'Аландские острова';

View File

@ -6,12 +6,30 @@ class UnableToGetDocuments implements Exception {}
class CouldNotDeletePlayer implements Exception {} class CouldNotDeletePlayer implements Exception {}
class CouldNotDeleteMatch implements Exception {}
class CouldNotUpdatePlayer implements Exception {} class CouldNotUpdatePlayer implements Exception {}
class TetrioPlayerAlreadyExist implements Exception {} class TetrioPlayerAlreadyExist implements Exception {}
class TetrioPlayerNotExist implements Exception {} class TetrioPlayerNotExist implements Exception {}
class TetrioHistoryNotExist implements Exception {}
class TetrioTooManyRequests implements Exception {}
class TetrioForbidden implements Exception {}
class P1nkl0bst3rTooManyRequests implements Exception {}
class P1nkl0bst3rForbidden implements Exception {}
class P1nkl0bst3rInternalProblem implements Exception {}
class TetrioOskwareBridgeProblem implements Exception {}
class TetrioInternalProblem implements Exception {}
class ConnectionIssue implements Exception { class ConnectionIssue implements Exception {
const ConnectionIssue(this.code, this.message); const ConnectionIssue(this.code, this.message);

View File

@ -0,0 +1,14 @@
import 'package:http/http.dart' as http;
class UserAgentClient extends http.BaseClient {
final String userAgent;
final http.Client _inner;
UserAgentClient(this.userAgent, this._inner);
@override
Future<http.StreamedResponse> send(http.BaseRequest request) {
request.headers['user-agent'] = userAgent;
return _inner.send(request);
}
}

View File

@ -2,6 +2,7 @@ import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:developer' as developer; import 'dart:developer' as developer;
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:tetra_stats/services/custom_http_client.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:tetra_stats/services/crud_exceptions.dart'; import 'package:tetra_stats/services/crud_exceptions.dart';
import 'package:tetra_stats/services/sqlite_db_controller.dart'; import 'package:tetra_stats/services/sqlite_db_controller.dart';
@ -53,6 +54,7 @@ class TetrioService extends DB {
final Map<String, Map<String, dynamic>> _recordsCache = {}; final Map<String, Map<String, dynamic>> _recordsCache = {};
final Map<String, TetrioPlayersLeaderboard> _leaderboardsCache = {}; final Map<String, TetrioPlayersLeaderboard> _leaderboardsCache = {};
final Map<String, TetraLeagueAlphaStream> _tlStreamsCache = {}; // i'm trying to respect oskware api It should look something like {"cached_until": TetrioPlayer} final Map<String, TetraLeagueAlphaStream> _tlStreamsCache = {}; // i'm trying to respect oskware api It should look something like {"cached_until": TetrioPlayer}
final client = UserAgentClient("Tetra Stats v1.2.3 (dm @dan63047 if someone abuse that software)", http.Client());
static final TetrioService _shared = TetrioService._sharedInstance(); static final TetrioService _shared = TetrioService._sharedInstance();
factory TetrioService() => _shared; factory TetrioService() => _shared;
late final StreamController<Map<String, List<TetrioPlayer>>> _tetrioStreamController; late final StreamController<Map<String, List<TetrioPlayer>>> _tetrioStreamController;
@ -109,54 +111,74 @@ class TetrioService extends DB {
} else { } else {
url = Uri.https('api.p1nkl0bst3r.xyz', 'tlhist/$id'); url = Uri.https('api.p1nkl0bst3r.xyz', 'tlhist/$id');
} }
final response = await http.get(url); try{
if (response.statusCode == 200) { final response = await client.get(url);
List<List<dynamic>> csv = const CsvToListConverter().convert(response.body)..removeAt(0);
List<TetrioPlayer> history = []; switch (response.statusCode) {
String nick = await getNicknameByID(id); case 200:
for (List<dynamic> entry in csv){ List<List<dynamic>> csv = const CsvToListConverter().convert(response.body)..removeAt(0);
TetrioPlayer state = TetrioPlayer( List<TetrioPlayer> history = [];
userId: id, String nick = await getNicknameByID(id);
username: nick, for (List<dynamic> entry in csv){
role: "p1nkl0bst3r", TetrioPlayer state = TetrioPlayer(
state: DateTime.parse(entry[9]), userId: id,
badges: [], username: nick,
friendCount: -1, role: "p1nkl0bst3r",
gamesPlayed: -1, state: DateTime.parse(entry[9]),
gamesWon: -1, badges: [],
gameTime: const Duration(seconds: -1), friendCount: -1,
xp: -1, gamesPlayed: -1,
supporterTier: 0, gamesWon: -1,
verified: false, gameTime: const Duration(seconds: -1),
connections: null, xp: -1,
tlSeason1: TetraLeagueAlpha(timestamp: DateTime.parse(entry[9]), apm: entry[6] != '' ? entry[6] : null, pps: entry[7] != '' ? entry[7] : null, vs: entry[8] != '' ? entry[8] : null, glicko: entry[4], rd: noTrRd, gamesPlayed: entry[1], gamesWon: entry[2], bestRank: "z", decaying: false, rating: entry[3], rank: entry[5], percentileRank: entry[5], percentile: rankCutoffs[entry[5]]!, standing: -1, standingLocal: -1, nextAt: -1, prevAt: -1), supporterTier: 0,
sprint: [], verified: false,
blitz: [] connections: null,
); tlSeason1: TetraLeagueAlpha(timestamp: DateTime.parse(entry[9]), apm: entry[6] != '' ? entry[6] : null, pps: entry[7] != '' ? entry[7] : null, vs: entry[8] != '' ? entry[8] : null, glicko: entry[4], rd: noTrRd, gamesPlayed: entry[1], gamesWon: entry[2], bestRank: "z", decaying: false, rating: entry[3], rank: entry[5], percentileRank: entry[5], percentile: rankCutoffs[entry[5]]!, standing: -1, standingLocal: -1, nextAt: -1, prevAt: -1),
history.add(state); sprint: [],
blitz: []
);
history.add(state);
}
await ensureDbIsOpen();
final db = getDatabaseOrThrow();
late List<TetrioPlayer> states;
try{
states = _players[id]!;
}catch(e){
var player = await fetchPlayer(id);
await createPlayer(player);
states = _players[id]!;
}
states.insertAll(0, history.reversed);
final Map<String, dynamic> statesJson = {};
for (var e in states) {
statesJson.addEntries({(e.state.millisecondsSinceEpoch ~/ 1000).toString(): e.toJson()}.entries);
}
await db.update(tetrioUsersTable, {idCol: id, nickCol: nick, statesCol: jsonEncode(statesJson)}, where: '$idCol = ?', whereArgs: [id]);
_tetrioStreamController.add(_players);
return history;
case 404:
developer.log("fetchTLHistory: Probably, history doesn't exist", name: "services/tetrio_crud", error: response.statusCode);
throw TetrioHistoryNotExist();
case 403:
throw P1nkl0bst3rForbidden();
case 429:
throw P1nkl0bst3rTooManyRequests();
case 418:
throw TetrioOskwareBridgeProblem();
case 500:
case 502:
case 503:
case 504:
throw P1nkl0bst3rInternalProblem();
default:
developer.log("fetchTLHistory: Failed to fetch history", name: "services/tetrio_crud", error: response.statusCode);
throw ConnectionIssue(response.statusCode, response.reasonPhrase??"No reason");
} }
await ensureDbIsOpen(); } on http.ClientException catch (e, s) {
final db = getDatabaseOrThrow(); developer.log("$e, $s");
late List<TetrioPlayer> states; throw http.ClientException(e.message, e.uri);
try{
states = _players[id]!;
}catch(e){
var player = await fetchPlayer(id);
await createPlayer(player);
states = _players[id]!;
}
states.insertAll(0, history.reversed);
final Map<String, dynamic> statesJson = {};
for (var e in states) {
statesJson.addEntries({(e.state.millisecondsSinceEpoch ~/ 1000).toString(): e.toJson()}.entries);
}
await db.update(tetrioUsersTable, {idCol: id, nickCol: nick, statesCol: jsonEncode(statesJson)}, where: '$idCol = ?', whereArgs: [id]);
_tetrioStreamController.add(_players);
return history;
}
else {
developer.log("fetchTLHistory: Probably, history doesn't exist", name: "services/tetrio_crud", error: response.statusCode);
throw Exception('Failed to fetch player');
} }
} }
@ -179,21 +201,39 @@ class TetrioService extends DB {
} else { } else {
url = Uri.https('ch.tetr.io', 'api/users/lists/league/all'); url = Uri.https('ch.tetr.io', 'api/users/lists/league/all');
} }
final response = await http.get(url); try{
if (response.statusCode == 200) { final response = await client.get(url);
var rawJson = jsonDecode(response.body);
if (rawJson['success']) { switch (response.statusCode) {
TetrioPlayersLeaderboard leaderboard = TetrioPlayersLeaderboard.fromJson(rawJson['data']['users'], "league", DateTime.fromMillisecondsSinceEpoch(rawJson['cache']['cached_at'])); case 200:
developer.log("fetchTLLeaderboard: Leaderboard retrieved and cached", name: "services/tetrio_crud"); var rawJson = jsonDecode(response.body);
_leaderboardsCache[rawJson['cache']['cached_until'].toString()] = leaderboard; if (rawJson['success']) {
return leaderboard; TetrioPlayersLeaderboard leaderboard = TetrioPlayersLeaderboard.fromJson(rawJson['data']['users'], "league", DateTime.fromMillisecondsSinceEpoch(rawJson['cache']['cached_at']));
} else { developer.log("fetchTLLeaderboard: Leaderboard retrieved and cached", name: "services/tetrio_crud");
developer.log("fetchTLLeaderboard: Bruh", name: "services/tetrio_crud", error: rawJson); _leaderboardsCache[rawJson['cache']['cached_until'].toString()] = leaderboard;
throw Exception("User doesn't exist"); return leaderboard;
} else {
developer.log("fetchTLLeaderboard: Bruh", name: "services/tetrio_crud", error: rawJson);
throw Exception("Failed to get leaderboard (problems on the tetr.io side)");
}
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("fetchTLLeaderboard: Failed to fetch leaderboard", name: "services/tetrio_crud", error: response.statusCode);
throw ConnectionIssue(response.statusCode, response.reasonPhrase??"No reason");
} }
} else { } on http.ClientException catch (e, s) {
developer.log("fetchTLLeaderboard: Failed to fetch leaderboard", name: "services/tetrio_crud", error: response.statusCode); developer.log("$e, $s");
throw Exception('Failed to fetch player'); throw http.ClientException(e.message, e.uri);
} }
} }
@ -217,22 +257,38 @@ class TetrioService extends DB {
} else { } else {
url = Uri.https('ch.tetr.io', 'api/streams/league_userrecent_${userID.toLowerCase().trim()}'); url = Uri.https('ch.tetr.io', 'api/streams/league_userrecent_${userID.toLowerCase().trim()}');
} }
final response = await http.get(url); try {
final response = await client.get(url);
if (response.statusCode == 200) { switch (response.statusCode) {
if (jsonDecode(response.body)['success']) { case 200:
TetraLeagueAlphaStream stream = TetraLeagueAlphaStream.fromJson( if (jsonDecode(response.body)['success']) {
jsonDecode(response.body)['data']['records'], userID); TetraLeagueAlphaStream stream = TetraLeagueAlphaStream.fromJson(jsonDecode(response.body)['data']['records'], userID);
developer.log("getTLStream: $userID stream retrieved and cached", name: "services/tetrio_crud"); developer.log("getTLStream: $userID stream retrieved and cached", name: "services/tetrio_crud");
_tlStreamsCache[jsonDecode(response.body)['cache']['cached_until'].toString()] = stream; _tlStreamsCache[jsonDecode(response.body)['cache']['cached_until'].toString()] = stream;
return stream; return stream;
} else { } else {
developer.log("getTLStream User dosen't exist", name: "services/tetrio_crud", error: response.body); developer.log("getTLStream User dosen't exist", name: "services/tetrio_crud", error: response.body);
throw Exception("User doesn't exist"); 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("getTLStream Failed to fetch stream", name: "services/tetrio_crud", error: response.statusCode);
throw ConnectionIssue(response.statusCode, response.reasonPhrase??"No reason");
} }
} else { } on http.ClientException catch (e, s) {
developer.log("getTLStream Failed to fetch stream", name: "services/tetrio_crud", error: response.statusCode); developer.log("$e, $s");
throw Exception('Failed to fetch player'); throw http.ClientException(e.message, e.uri);
} }
} }
@ -257,6 +313,15 @@ class TetrioService extends DB {
return matches; return matches;
} }
Future<void> deleteTLMatch(String matchID) async {
await ensureDbIsOpen();
final db = getDatabaseOrThrow();
final results = await db.delete(tetraLeagueMatchesTable, where: '$idCol = ?', whereArgs: [matchID]);
if (results != 1) {
throw CouldNotDeleteMatch();
}
}
Future<Map<String, dynamic>> fetchRecords(String userID) async { Future<Map<String, dynamic>> fetchRecords(String userID) async {
try{ try{
var cached = _recordsCache.entries.firstWhere((element) => element.value['user'] == userID); var cached = _recordsCache.entries.firstWhere((element) => element.value['user'] == userID);
@ -277,29 +342,46 @@ class TetrioService extends DB {
} else { } else {
url = Uri.https('ch.tetr.io', 'api/users/${userID.toLowerCase().trim()}/records'); url = Uri.https('ch.tetr.io', 'api/users/${userID.toLowerCase().trim()}/records');
} }
final response = await http.get(url); try{
final response = await client.get(url);
if (response.statusCode == 200) { switch (response.statusCode) {
if (jsonDecode(response.body)['success']) { case 200:
Map jsonRecords = jsonDecode(response.body); if (jsonDecode(response.body)['success']) {
var sprint = jsonRecords['data']['records']['40l']['record'] != null Map jsonRecords = jsonDecode(response.body);
? [RecordSingle.fromJson(jsonRecords['data']['records']['40l']['record'], jsonRecords['data']['records']['40l']['rank'])] var sprint = jsonRecords['data']['records']['40l']['record'] != null
: []; ? [RecordSingle.fromJson(jsonRecords['data']['records']['40l']['record'], jsonRecords['data']['records']['40l']['rank'])]
var blitz = jsonRecords['data']['records']['blitz']['record'] != null : [];
? [RecordSingle.fromJson(jsonRecords['data']['records']['blitz']['record'], jsonRecords['data']['records']['blitz']['rank'])] var blitz = jsonRecords['data']['records']['blitz']['record'] != null
: []; ? [RecordSingle.fromJson(jsonRecords['data']['records']['blitz']['record'], jsonRecords['data']['records']['blitz']['rank'])]
var zen = TetrioZen.fromJson(jsonRecords['data']['zen']); : [];
Map<String, dynamic> map = {"user": userID.toLowerCase().trim(), "sprint": sprint, "blitz": blitz, "zen": zen}; var zen = TetrioZen.fromJson(jsonRecords['data']['zen']);
developer.log("fetchRecords: $userID records retrieved and cached", name: "services/tetrio_crud"); Map<String, dynamic> map = {"user": userID.toLowerCase().trim(), "sprint": sprint, "blitz": blitz, "zen": zen};
_recordsCache[jsonDecode(response.body)['cache']['cached_until'].toString()] = map; developer.log("fetchRecords: $userID records retrieved and cached", name: "services/tetrio_crud");
return map; _recordsCache[jsonDecode(response.body)['cache']['cached_until'].toString()] = map;
} else { return map;
developer.log("fetchRecords User dosen't exist", name: "services/tetrio_crud", error: response.body); } else {
throw Exception("User doesn't exist"); developer.log("fetchRecords 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("fetchRecords Failed to fetch records", name: "services/tetrio_crud", error: response.statusCode);
throw ConnectionIssue(response.statusCode, response.reasonPhrase??"No reason");
} }
} else { } on http.ClientException catch (e, s) {
developer.log("fetchRecords Failed to fetch records", name: "services/tetrio_crud", error: response.statusCode); developer.log("$e, $s");
throw Exception('Failed to fetch player'); throw http.ClientException(e.message, e.uri);
} }
} }
@ -332,11 +414,7 @@ class TetrioService extends DB {
await ensureDbIsOpen(); await ensureDbIsOpen();
final db = getDatabaseOrThrow(); final db = getDatabaseOrThrow();
final results = await db.query(tetrioUsersToTrackTable, where: '$idCol = ?', whereArgs: [id.toLowerCase()]); final results = await db.query(tetrioUsersToTrackTable, where: '$idCol = ?', whereArgs: [id.toLowerCase()]);
if (results.isEmpty) { return results.isNotEmpty;
return false;
} else {
return true;
}
} }
Future<Iterable<String>> getAllPlayerToTrack() async { Future<Iterable<String>> getAllPlayerToTrack() async {
@ -437,43 +515,79 @@ class TetrioService extends DB {
} else { } else {
dUrl = Uri.https('ch.tetr.io', 'api/users/search/${user.toLowerCase().trim()}'); dUrl = Uri.https('ch.tetr.io', 'api/users/search/${user.toLowerCase().trim()}');
} }
final response = await http.get(dUrl); try{
if (response.statusCode == 200) { final response = await client.get(dUrl);
var json = jsonDecode(response.body);
if (json['success'] && json['data'] != null) { switch (response.statusCode) {
user = json['data']['user']['_id']; case 200:
} else { var json = jsonDecode(response.body);
developer.log("fetchPlayer User dosen't exist", name: "services/tetrio_crud", error: response.body); if (json['success'] && json['data'] != null) {
throw TetrioPlayerNotExist(); user = json['data']['user']['_id'];
} else {
developer.log("fetchPlayer User dosen't exist", name: "services/tetrio_crud", error: response.body);
throw TetrioPlayerNotExist();
}
break;
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("fetchPlayer Failed to fetch player", name: "services/tetrio_crud", error: response.statusCode);
throw ConnectionIssue(response.statusCode, response.reasonPhrase??"No reason");
} }
} else { } on http.ClientException catch (e, s) {
developer.log("fetchPlayer Failed to fetch player", name: "services/tetrio_crud", error: response.statusCode); developer.log("$e, $s");
throw ConnectionIssue(response.statusCode, response.reasonPhrase??"No reason"); throw http.ClientException(e.message, e.uri);
} }
} }
Uri url; Uri url;
if (kIsWeb) { if (kIsWeb) {
url = Uri.https('ts.dan63.by', 'oskware_bridge.php', {"endpoint": "tetrioUser", "user": user.toLowerCase().trim()}); url = Uri.https('ts.dan63.by', 'oskware_bridge.php', {"endpoint": "tetrioUser", "user": user.toLowerCase().trim()});
} else { } else {
url = Uri.https('ch.tetr.io', 'api/users/${user.toLowerCase().trim()}'); url = Uri.https('ch.tetr.io', 'api/users/${user.toLowerCase().trim()}');
} }
final response = await http.get(url); try{
final response = await client.get(url);
if (response.statusCode == 200) { switch (response.statusCode) {
var json = jsonDecode(response.body); case 200:
if (json['success']) { var json = jsonDecode(response.body);
TetrioPlayer player = TetrioPlayer.fromJson(json['data']['user'], DateTime.fromMillisecondsSinceEpoch(json['cache']['cached_at'], isUtc: true), json['data']['user']['_id'], json['data']['user']['username']); if (json['success']) {
developer.log("fetchPlayer: $user retrieved and cached", name: "services/tetrio_crud"); TetrioPlayer player = TetrioPlayer.fromJson(json['data']['user'], DateTime.fromMillisecondsSinceEpoch(json['cache']['cached_at'], isUtc: true), json['data']['user']['_id'], json['data']['user']['username']);
_playersCache[jsonDecode(response.body)['cache']['cached_until'].toString()] = player; developer.log("fetchPlayer: $user retrieved and cached", name: "services/tetrio_crud");
return player; _playersCache[jsonDecode(response.body)['cache']['cached_until'].toString()] = player;
} else { return player;
developer.log("fetchPlayer User dosen't exist", name: "services/tetrio_crud", error: response.body); } else {
throw TetrioPlayerNotExist(); developer.log("fetchPlayer 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("fetchPlayer Failed to fetch player", name: "services/tetrio_crud", error: response.statusCode);
throw ConnectionIssue(response.statusCode, response.reasonPhrase??"No reason");
} }
} else { }on http.ClientException catch (e, s) {
developer.log("fetchPlayer Failed to fetch player", name: "services/tetrio_crud", error: response.statusCode); developer.log("$e, $s");
throw ConnectionIssue(response.statusCode, response.reasonPhrase??"No reason"); throw http.ClientException(e.message, e.uri);
} }
} }

View File

@ -875,7 +875,10 @@ class CompareState extends State<CompareView> {
) )
], ],
) )
] : [Text(t.compareViewNoValues(avgR: "\$avgR"))], // This is so fucked up holy shit ] : [Padding(
padding: const EdgeInsets.all(8.0),
child: Text(t.compareViewNoValues(avgR: "\$avgR"), textAlign: TextAlign.center),
)], // This is so fucked up holy shit
) )
), ),
), ),

View File

@ -2,6 +2,7 @@ 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_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:http/http.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'dart:math'; import 'dart:math';
import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/fl_chart.dart';
@ -31,6 +32,7 @@ const allowedHeightForPlayerBioInPixels = 30.0;
const givenTextHeightByScreenPercentage = 0.3; const givenTextHeightByScreenPercentage = 0.3;
final NumberFormat timeInSec = NumberFormat("#,###.###s."); final NumberFormat timeInSec = NumberFormat("#,###.###s.");
final NumberFormat f2 = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 2); final NumberFormat f2 = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 2);
final NumberFormat secs = NumberFormat("00.###");
final NumberFormat f4 = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 4); final NumberFormat f4 = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 4);
final DateFormat dateFormat = DateFormat.yMMMd(LocaleSettings.currentLocale.languageCode).add_Hms(); final DateFormat dateFormat = DateFormat.yMMMd(LocaleSettings.currentLocale.languageCode).add_Hms();
@ -348,19 +350,37 @@ class _MainState extends State<MainView> with SingleTickerProviderStateMixin {
var err = snapshot.error as ConnectionIssue; var err = snapshot.error as ConnectionIssue;
errText = t.errors.connection(code: err.code, message: err.message); errText = t.errors.connection(code: err.code, message: err.message);
break; break;
case SocketException: // TODO: Find a way to catch case P1nkl0bst3rForbidden:
var err = snapshot.error as SocketException; errText = t.errors.p1nkl0bst3rForbidden;
errText = t.errors.socketException(host: err.address!.host, message: err.osError!.message); break;
case P1nkl0bst3rTooManyRequests:
errText = t.errors.p1nkl0bst3rTooManyRequests;
break;
case P1nkl0bst3rInternalProblem:
errText = kIsWeb ? t.errors.p1nkl0bst3rinternalWebVersion : t.errors.p1nkl0bst3rinternal;
break;
case TetrioHistoryNotExist:
errText = t.errors.history;
break;
case TetrioForbidden:
errText = t.errors.forbidden;
break;
case TetrioTooManyRequests:
errText = t.errors.tooManyRequests;
break;
case TetrioOskwareBridgeProblem:
errText = t.errors.oskwareBridge;
break;
case TetrioInternalProblem:
errText = kIsWeb ? t.errors.internalWebVersion : t.errors.internal;
break;
case ClientException:
errText = t.errors.clientException;
break; break;
default: default:
errText = snapshot.error.toString(); errText = snapshot.error.toString();
} }
return Center( return Center(child: Text(errText, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 42, fontWeight: FontWeight.bold), textAlign: TextAlign.center));
child: Text(errText,
style: const TextStyle(
fontFamily: "Eurostile Round Extended",
fontSize: 42),
textAlign: TextAlign.center));
} }
break; break;
default: default:
@ -570,7 +590,7 @@ class _History extends StatelessWidget{
else Center(child: Text(t.notEnoughData, style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 28))) else Center(child: Text(t.notEnoughData, style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 28)))
], ],
), ),
] : [Center(child: Text(t.noHistorySaved, style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 28)))]); ] : [Center(child: Text(t.noHistorySaved, textAlign: TextAlign.center, style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 28)))]);
} }
} }
@ -650,7 +670,7 @@ class _RecordThingy extends StatelessWidget {
fontSize: bigScreen ? 42 : 28)), fontSize: bigScreen ? 42 : 28)),
if (record!.stream.contains("40l")) if (record!.stream.contains("40l"))
if (record!.endContext!.finalTime.inMicroseconds > 60000000) Text( if (record!.endContext!.finalTime.inMicroseconds > 60000000) Text(
"${(record!.endContext!.finalTime.inMicroseconds/1000000/60).floor()}:${(f2.format(record!.endContext!.finalTime.inMicroseconds /1000000 % 60))}", "${(record!.endContext!.finalTime.inMicroseconds/1000000/60).floor()}:${(secs.format(record!.endContext!.finalTime.inMicroseconds /1000000 % 60))}",
style: TextStyle( style: TextStyle(
fontFamily: "Eurostile Round Extended", fontFamily: "Eurostile Round Extended",
fontSize: bigScreen ? 42 : 28)) fontSize: bigScreen ? 42 : 28))
@ -755,194 +775,108 @@ class _RecordThingy extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Row( Row(
mainAxisAlignment: mainAxisAlignment: MainAxisAlignment.spaceBetween,
MainAxisAlignment.spaceBetween,
children: [ children: [
Text("${t.numOfGameActions.pc}:", Text("${t.numOfGameActions.pc}:", style: const TextStyle(fontSize: 24)),
style: const TextStyle(fontSize: 24)), Text(record!.endContext!.clears.allClears.toString(), style: const TextStyle(fontSize: 24)),
Text( ],
record!.endContext!.clears.allClears ),
.toString(), Row(
style: const TextStyle(fontSize: 24), mainAxisAlignment: MainAxisAlignment.spaceBetween,
), children: [
Text("${t.numOfGameActions.hold}:", style: const TextStyle(fontSize: 24)),
Text(record!.endContext!.holds.toString(), style: const TextStyle(fontSize: 24)),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text("${t.numOfGameActions.tspinsTotal}:", style: const TextStyle(fontSize: 24)),
Text(record!.endContext!.tSpins.toString(), style: const TextStyle(fontSize: 24)),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(" - T-spin zero:", style: TextStyle(fontSize: 18)),
Text(record!.endContext!.clears.tSpinZeros.toString(), style: const TextStyle(fontSize: 18)),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(" - T-spin singles:", style: TextStyle(fontSize: 18)),
Text(record!.endContext!.clears.tSpinSingles.toString(), style: const TextStyle(fontSize: 18)),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(" - T-spin doubles:", style: TextStyle(fontSize: 18)),
Text(record!.endContext!.clears.tSpinDoubles.toString(), style: const TextStyle(fontSize: 18)),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(" - T-spin triples:", style: TextStyle(fontSize: 18)),
Text(record!.endContext!.clears.tSpinTriples.toString(), style: const TextStyle(fontSize: 18)),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(" - T-spin mini zero:", style: TextStyle(fontSize: 18)),
Text(record!.endContext!.clears.tSpinMiniZeros.toString(), style: const TextStyle(fontSize: 18)),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(" - T-spin mini singles:", style: TextStyle(fontSize: 18)),
Text(record!.endContext!.clears.tSpinMiniSingles.toString(), style: const TextStyle(fontSize: 18)),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(" - T-spin mini doubles:", style: TextStyle(fontSize: 18)),
Text(record!.endContext!.clears.tSpinMiniDoubles.toString(), style: const TextStyle(fontSize: 18)),
],
),
Row(mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text("${t.numOfGameActions.lineClears}:", style: const TextStyle(fontSize: 24)),
Text(record!.endContext!.lines.toString(), style: const TextStyle(fontSize: 24)),
], ],
), ),
Row( Row(
mainAxisAlignment: mainAxisAlignment:
MainAxisAlignment.spaceBetween, MainAxisAlignment.spaceBetween,
children: [ children: [
Text("${t.numOfGameActions.hold}:", const Text(" - Singles:", style: TextStyle(fontSize: 18)),
style: const TextStyle(fontSize: 24)), Text(record!.endContext!.clears.singles.toString(), style: const TextStyle(fontSize: 18)),
Text(
record!.endContext!.holds.toString(),
style: const TextStyle(fontSize: 24),
),
], ],
), ),
Row( Row(
mainAxisAlignment: mainAxisAlignment: MainAxisAlignment.spaceBetween,
MainAxisAlignment.spaceBetween,
children: [ children: [
Text("${t.numOfGameActions.tspinsTotal}:", const Text(" - Doubles:", style: TextStyle(fontSize: 18)),
style: const TextStyle(fontSize: 24)), Text(record!.endContext!.clears.doubles.toString(), style: const TextStyle(fontSize: 18)),
Text(
record!.endContext!.tSpins.toString(),
style: const TextStyle(fontSize: 24),
),
], ],
), ),
Row( Row(
mainAxisAlignment: mainAxisAlignment: MainAxisAlignment.spaceBetween,
MainAxisAlignment.spaceBetween,
children: [ children: [
const Text(" - T-spin zero:", const Text(" - Triples:", style: TextStyle(fontSize: 18)),
style: TextStyle(fontSize: 18)), Text(record!.endContext!.clears.triples.toString(), style: const TextStyle(fontSize: 18)),
Text(
record!.endContext!.clears.tSpinZeros
.toString(),
style: const TextStyle(fontSize: 18),
),
], ],
), ),
Row( Row(
mainAxisAlignment: mainAxisAlignment: MainAxisAlignment.spaceBetween,
MainAxisAlignment.spaceBetween,
children: [ children: [
const Text(" - T-spin singles:", const Text(" - Quads:", style: TextStyle(fontSize: 18)),
style: TextStyle(fontSize: 18)), Text(record!.endContext!.clears.quads.toString(), style: const TextStyle(fontSize: 18)),
Text(
record!.endContext!.clears.tSpinSingles
.toString(),
style: const TextStyle(fontSize: 18),
),
],
),
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
const Text(" - T-spin doubles:",
style: TextStyle(fontSize: 18)),
Text(
record!.endContext!.clears.tSpinDoubles
.toString(),
style: const TextStyle(fontSize: 18),
),
],
),
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
const Text(" - T-spin triples:",
style: TextStyle(fontSize: 18)),
Text(
record!.endContext!.clears.tSpinTriples
.toString(),
style: const TextStyle(fontSize: 18),
),
],
),
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
const Text(" - T-spin mini zero:",
style: TextStyle(fontSize: 18)),
Text(
record!.endContext!.clears.tSpinMiniZeros
.toString(),
style: const TextStyle(fontSize: 18),
),
],
),
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
const Text(" - T-spin mini singles:",
style: TextStyle(fontSize: 18)),
Text(
record!.endContext!.clears.tSpinMiniSingles
.toString(),
style: const TextStyle(fontSize: 18),
),
],
),
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
const Text(" - T-spin mini doubles:",
style: TextStyle(fontSize: 18)),
Text(
record!.endContext!.clears.tSpinMiniDoubles
.toString(),
style: const TextStyle(fontSize: 18),
),
],
),
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text("${t.numOfGameActions.lineClears}:",
style: const TextStyle(fontSize: 24)),
Text(
record!.endContext!.lines.toString(),
style: const TextStyle(fontSize: 24),
),
],
),
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
const Text(" - Singles:",
style: TextStyle(fontSize: 18)),
Text(
record!.endContext!.clears.singles
.toString(),
style: const TextStyle(fontSize: 18),
),
],
),
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
const Text(" - Doubles:",
style: TextStyle(fontSize: 18)),
Text(
record!.endContext!.clears.doubles
.toString(),
style: const TextStyle(fontSize: 18),
),
],
),
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
const Text(" - Triples:",
style: TextStyle(fontSize: 18)),
Text(
record!.endContext!.clears.triples
.toString(),
style: const TextStyle(fontSize: 18),
),
],
),
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
const Text(" - Quads:",
style: TextStyle(fontSize: 18)),
Text(
record!.endContext!.clears.quads.toString(),
style: const TextStyle(fontSize: 18),
),
], ],
), ),
], ],
@ -951,7 +885,7 @@ class _RecordThingy extends StatelessWidget {
), ),
] ]
: [ : [
Text(t.noRecord, style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 28)) Text(t.noRecord, textAlign: TextAlign.center, style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 28))
], ],
); );
}); });

View File

@ -0,0 +1,73 @@
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:tetra_stats/services/tetrio_crud.dart';
import 'package:tetra_stats/gen/strings.g.dart';
import 'package:tetra_stats/views/tl_match_view.dart';
final TetrioService teto = TetrioService();
final NumberFormat f2 = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 2);
class MatchesView extends StatefulWidget {
final String userID;
final String username;
const MatchesView({Key? key, required this.userID, required this.username}) : super(key: key);
@override
State<StatefulWidget> createState() => MatchesState();
}
class MatchesState extends State<MatchesView> {
@override
Widget build(BuildContext context) {
final t = Translations.of(context);
bool bigScreen = MediaQuery.of(context).size.width > 768;
final DateFormat dateFormat = DateFormat.yMMMd(LocaleSettings.currentLocale.languageCode).add_Hms();
return Scaffold(
appBar: AppBar(
title: Text(t.matchesViewTitle(nickname: widget.username)),
),
backgroundColor: Colors.black,
body: SafeArea(
child: FutureBuilder(
future: teto.getTLMatchesbyPlayerID(widget.userID),
builder: (context, snapshot){
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.waiting:
case ConnectionState.active:
return const Center(child: CircularProgressIndicator(color: Colors.white));
case ConnectionState.done:
return ListView(
physics: const AlwaysScrollableScrollPhysics(),
children: (snapshot.data!.isNotEmpty)
? [for (var value in snapshot.data!) ListTile(
leading: Text("${value.endContext.firstWhere((element) => element.userId == widget.userID).points} : ${value.endContext.firstWhere((element) => element.userId != widget.userID).points}",
style: bigScreen ? const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 28) :
const TextStyle(fontSize: 28)),
title: Text("vs. ${value.endContext.firstWhere((element) => element.userId != widget.userID).username}"),
subtitle: Text(dateFormat.format(value.timestamp)),
trailing: IconButton(
icon: const Icon(Icons.delete_forever),
onPressed: () {
DateTime nn = value.timestamp;
teto.deleteTLMatch(value.ownId).then((value) => setState(() {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.matchRemoved(date: dateFormat.format(nn)))));
}));
},
),
onTap: (){Navigator.push(
context,
MaterialPageRoute(
builder: (context) => TlMatchResultView(record: value, initPlayerId: widget.userID),
),
);},
)]
: [Center(child: Text(t.noRecords, style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 28)))],
);
}
}
)
)
);
}
}

View File

@ -346,7 +346,7 @@ class RankState extends State<RankView> with SingleTickerProviderStateMixin {
_ListEntry(value: widget.rank[1]["lowestVSAPM"], label: "VS / APM", id: widget.rank[1]["lowestVSAPMid"], username: widget.rank[1]["lowestVSAPMnick"], approximate: false, fractionDigits: 3), _ListEntry(value: widget.rank[1]["lowestVSAPM"], label: "VS / APM", id: widget.rank[1]["lowestVSAPMid"], username: widget.rank[1]["lowestVSAPMnick"], approximate: false, fractionDigits: 3),
_ListEntry(value: widget.rank[1]["lowestDSS"], label: t.statCellNum.dss.replaceAll(RegExp(r'\n'), " "), id: widget.rank[1]["lowestDSSid"], username: widget.rank[1]["lowestDSSnick"], approximate: false, fractionDigits: 3), _ListEntry(value: widget.rank[1]["lowestDSS"], label: t.statCellNum.dss.replaceAll(RegExp(r'\n'), " "), id: widget.rank[1]["lowestDSSid"], username: widget.rank[1]["lowestDSSnick"], approximate: false, fractionDigits: 3),
_ListEntry(value: widget.rank[1]["lowestDSP"], label: t.statCellNum.dsp.replaceAll(RegExp(r'\n'), " "), id: widget.rank[1]["lowestDSPid"], username: widget.rank[1]["lowestDSPnick"], approximate: false, fractionDigits: 3), _ListEntry(value: widget.rank[1]["lowestDSP"], label: t.statCellNum.dsp.replaceAll(RegExp(r'\n'), " "), id: widget.rank[1]["lowestDSPid"], username: widget.rank[1]["lowestDSPnick"], approximate: false, fractionDigits: 3),
_ListEntry(value: widget.rank[1]["lowestAPPDSP"], label: t.statCellNum.dsp.replaceAll(RegExp(r'\n'), " "), id: widget.rank[1]["lowestAPPDSPid"], username: widget.rank[1]["lowestAPPDSPnick"], approximate: false, fractionDigits: 3), _ListEntry(value: widget.rank[1]["lowestAPPDSP"], label: t.statCellNum.appdsp.replaceAll(RegExp(r'\n'), " "), id: widget.rank[1]["lowestAPPDSPid"], username: widget.rank[1]["lowestAPPDSPnick"], approximate: false, fractionDigits: 3),
_ListEntry(value: widget.rank[1]["lowestCheese"], label: t.statCellNum.cheese.replaceAll(RegExp(r'\n'), " "), id: widget.rank[1]["lowestCheeseID"], username: widget.rank[1]["lowestCheeseNick"], approximate: false, fractionDigits: 2), _ListEntry(value: widget.rank[1]["lowestCheese"], label: t.statCellNum.cheese.replaceAll(RegExp(r'\n'), " "), id: widget.rank[1]["lowestCheeseID"], username: widget.rank[1]["lowestCheeseNick"], approximate: false, fractionDigits: 2),
_ListEntry(value: widget.rank[1]["lowestGBE"], label: t.statCellNum.gbe.replaceAll(RegExp(r'\n'), " "), id: widget.rank[1]["lowestGBEid"], username: widget.rank[1]["lowestGBEnick"], approximate: false, fractionDigits: 3), _ListEntry(value: widget.rank[1]["lowestGBE"], label: t.statCellNum.gbe.replaceAll(RegExp(r'\n'), " "), id: widget.rank[1]["lowestGBEid"], username: widget.rank[1]["lowestGBEnick"], approximate: false, fractionDigits: 3),
_ListEntry(value: widget.rank[1]["lowestNyaAPP"], label: t.statCellNum.nyaapp.replaceAll(RegExp(r'\n'), " "), id: widget.rank[1]["lowestNyaAPPid"], username: widget.rank[1]["lowestNyaAPPnick"], approximate: false, fractionDigits: 3), _ListEntry(value: widget.rank[1]["lowestNyaAPP"], label: t.statCellNum.nyaapp.replaceAll(RegExp(r'\n'), " "), id: widget.rank[1]["lowestNyaAPPid"], username: widget.rank[1]["lowestNyaAPPnick"], approximate: false, fractionDigits: 3),
@ -380,7 +380,7 @@ class RankState extends State<RankView> with SingleTickerProviderStateMixin {
_ListEntry(value: widget.rank[1]["avgAPP"], label: "VS / APM", id: "", username: "", approximate: true, fractionDigits: 3), _ListEntry(value: widget.rank[1]["avgAPP"], label: "VS / APM", id: "", username: "", approximate: true, fractionDigits: 3),
_ListEntry(value: widget.rank[1]["avgDSS"], label: t.statCellNum.dss.replaceAll(RegExp(r'\n'), " "), id: "", username: "", approximate: true, fractionDigits: 3), _ListEntry(value: widget.rank[1]["avgDSS"], label: t.statCellNum.dss.replaceAll(RegExp(r'\n'), " "), id: "", username: "", approximate: true, fractionDigits: 3),
_ListEntry(value: widget.rank[1]["avgDSP"], label: t.statCellNum.dsp.replaceAll(RegExp(r'\n'), " "), id: "", username: "", approximate: true, fractionDigits: 3), _ListEntry(value: widget.rank[1]["avgDSP"], label: t.statCellNum.dsp.replaceAll(RegExp(r'\n'), " "), id: "", username: "", approximate: true, fractionDigits: 3),
_ListEntry(value: widget.rank[1]["avgAPPDSP"], label: t.statCellNum.dsp.replaceAll(RegExp(r'\n'), " "), id: "", username: "", approximate: true, fractionDigits: 3), _ListEntry(value: widget.rank[1]["avgAPPDSP"], label: t.statCellNum.appdsp.replaceAll(RegExp(r'\n'), " "), id: "", username: "", approximate: true, fractionDigits: 3),
_ListEntry(value: widget.rank[1]["avgCheese"], label: t.statCellNum.cheese.replaceAll(RegExp(r'\n'), " "), id: "", username: "", approximate: true, fractionDigits: 2), _ListEntry(value: widget.rank[1]["avgCheese"], label: t.statCellNum.cheese.replaceAll(RegExp(r'\n'), " "), id: "", username: "", approximate: true, fractionDigits: 2),
_ListEntry(value: widget.rank[1]["avgGBE"], label: t.statCellNum.gbe.replaceAll(RegExp(r'\n'), " "), id: "", username: "", approximate: true, fractionDigits: 3), _ListEntry(value: widget.rank[1]["avgGBE"], label: t.statCellNum.gbe.replaceAll(RegExp(r'\n'), " "), id: "", username: "", approximate: true, fractionDigits: 3),
_ListEntry(value: widget.rank[1]["avgNyaAPP"], label: t.statCellNum.nyaapp.replaceAll(RegExp(r'\n'), " "), id: "", username: "", approximate: true, fractionDigits: 3), _ListEntry(value: widget.rank[1]["avgNyaAPP"], label: t.statCellNum.nyaapp.replaceAll(RegExp(r'\n'), " "), id: "", username: "", approximate: true, fractionDigits: 3),
@ -413,7 +413,7 @@ class RankState extends State<RankView> with SingleTickerProviderStateMixin {
_ListEntry(value: widget.rank[1]["highestVSAPM"], label: "VS / APM", id: widget.rank[1]["highestVSAPMid"], username: widget.rank[1]["highestVSAPMnick"], approximate: false, fractionDigits: 3), _ListEntry(value: widget.rank[1]["highestVSAPM"], label: "VS / APM", id: widget.rank[1]["highestVSAPMid"], username: widget.rank[1]["highestVSAPMnick"], approximate: false, fractionDigits: 3),
_ListEntry(value: widget.rank[1]["highestDSS"], label: t.statCellNum.dss.replaceAll(RegExp(r'\n'), " "), id: widget.rank[1]["highestDSSid"], username: widget.rank[1]["highestDSSnick"], approximate: false, fractionDigits: 3), _ListEntry(value: widget.rank[1]["highestDSS"], label: t.statCellNum.dss.replaceAll(RegExp(r'\n'), " "), id: widget.rank[1]["highestDSSid"], username: widget.rank[1]["highestDSSnick"], approximate: false, fractionDigits: 3),
_ListEntry(value: widget.rank[1]["highestDSP"], label: t.statCellNum.dsp.replaceAll(RegExp(r'\n'), " "), id: widget.rank[1]["highestDSPid"], username: widget.rank[1]["highestDSPnick"], approximate: false, fractionDigits: 3), _ListEntry(value: widget.rank[1]["highestDSP"], label: t.statCellNum.dsp.replaceAll(RegExp(r'\n'), " "), id: widget.rank[1]["highestDSPid"], username: widget.rank[1]["highestDSPnick"], approximate: false, fractionDigits: 3),
_ListEntry(value: widget.rank[1]["highestAPPDSP"], label: t.statCellNum.dsp.replaceAll(RegExp(r'\n'), " "), id: widget.rank[1]["highestAPPDSPid"], username: widget.rank[1]["highestAPPDSPnick"], approximate: false, fractionDigits: 3), _ListEntry(value: widget.rank[1]["highestAPPDSP"], label: t.statCellNum.appdsp.replaceAll(RegExp(r'\n'), " "), id: widget.rank[1]["highestAPPDSPid"], username: widget.rank[1]["highestAPPDSPnick"], approximate: false, fractionDigits: 3),
_ListEntry(value: widget.rank[1]["highestCheese"], label: t.statCellNum.cheese.replaceAll(RegExp(r'\n'), " "), id: widget.rank[1]["highestCheeseID"], username: widget.rank[1]["highestCheeseNick"], approximate: false, fractionDigits: 2), _ListEntry(value: widget.rank[1]["highestCheese"], label: t.statCellNum.cheese.replaceAll(RegExp(r'\n'), " "), id: widget.rank[1]["highestCheeseID"], username: widget.rank[1]["highestCheeseNick"], approximate: false, fractionDigits: 2),
_ListEntry(value: widget.rank[1]["highestGBE"], label: t.statCellNum.gbe.replaceAll(RegExp(r'\n'), " "), id: widget.rank[1]["highestGBEid"], username: widget.rank[1]["highestGBEnick"], approximate: false, fractionDigits: 3), _ListEntry(value: widget.rank[1]["highestGBE"], label: t.statCellNum.gbe.replaceAll(RegExp(r'\n'), " "), id: widget.rank[1]["highestGBEid"], username: widget.rank[1]["highestGBEnick"], approximate: false, fractionDigits: 3),
_ListEntry(value: widget.rank[1]["highestNyaAPP"], label: t.statCellNum.nyaapp.replaceAll(RegExp(r'\n'), " "), id: widget.rank[1]["highestNyaAPPid"], username: widget.rank[1]["highestNyaAPPnick"], approximate: false, fractionDigits: 3), _ListEntry(value: widget.rank[1]["highestNyaAPP"], label: t.statCellNum.nyaapp.replaceAll(RegExp(r'\n'), " "), id: widget.rank[1]["highestNyaAPPid"], username: widget.rank[1]["highestNyaAPPnick"], approximate: false, fractionDigits: 3),

View File

@ -44,12 +44,14 @@ class RanksAverages extends State<RankAveragesView> {
subtitle: Text("${f2.format(averages[keys[index]]?[0].apm)} APM, ${f2.format(averages[keys[index]]?[0].pps)} PPS, ${f2.format(averages[keys[index]]?[0].vs)} VS, ${f2.format(averages[keys[index]]?[0].nerdStats.app)} APP, ${f2.format(averages[keys[index]]?[0].nerdStats.vsapm)} VS/APM"), subtitle: Text("${f2.format(averages[keys[index]]?[0].apm)} APM, ${f2.format(averages[keys[index]]?[0].pps)} PPS, ${f2.format(averages[keys[index]]?[0].vs)} VS, ${f2.format(averages[keys[index]]?[0].nerdStats.app)} APP, ${f2.format(averages[keys[index]]?[0].nerdStats.vsapm)} VS/APM"),
trailing: Text("${f2.format(averages[keys[index]]?[1]["toEnterTR"])} TR", style: bigScreen ? const TextStyle(fontSize: 28) : null), trailing: Text("${f2.format(averages[keys[index]]?[1]["toEnterTR"])} TR", style: bigScreen ? const TextStyle(fontSize: 28) : null),
onTap: (){ onTap: (){
Navigator.push( if (averages[keys[index]]?[1]["players"] > 0) {
Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => RankView(rank: averages[keys[index]]!), builder: (context) => RankView(rank: averages[keys[index]]!),
), ),
); );
}
}, },
); );
}) })

View File

@ -2,7 +2,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/views/compare_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';
class StatesView extends StatefulWidget { class StatesView extends StatefulWidget {
@ -21,6 +21,17 @@ class StatesState extends State<StatesView> {
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())),
actions: [
IconButton(
onPressed: (){
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => MatchesView(userID: widget.states.first.userId, username: widget.states.first.username),
),
);
}, icon: const Icon(Icons.list), tooltip: t.viewAllMatches)
],
), ),
backgroundColor: Colors.black, backgroundColor: Colors.black,
body: SafeArea( body: SafeArea(

View File

@ -21,10 +21,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: archive name: archive
sha256: "49b1fad315e57ab0bbc15bcbb874e83116a1d78f77ebd500a4af6c9407d6b28e" sha256: "1227dc3efc4ea571eebb2dfb814506ed2cfb1d4b1b89fb918abdddde617ead3c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.3.8" version: "3.4.0"
args: args:
dependency: transitive dependency: transitive
description: description:
@ -229,10 +229,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: file_selector_macos name: file_selector_macos
sha256: "182c3f8350cee659f7b115e956047ee3dc672a96665883a545e81581b9a82c72" sha256: b15c3da8bd4908b9918111fa486903f5808e388b8d1c559949f584725a6594d6
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.9.3+2" version: "0.9.3+3"
file_selector_platform_interface: file_selector_platform_interface:
dependency: transitive dependency: transitive
description: description:
@ -361,10 +361,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: image name: image
sha256: a72242c9a0ffb65d03de1b7113bc4e189686fc07c7147b8b41811d0dd0e0d9bf sha256: "6e703d5e2f8c63fb31a77753915c1ec8baebde8088844e0d29f71b8f0b108888"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.0.17" version: "4.1.0"
intl: intl:
dependency: "direct main" dependency: "direct main"
description: description:
@ -782,10 +782,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: sqlite3_flutter_libs name: sqlite3_flutter_libs
sha256: fb115050b0c2589afe2085a62d77f5deda4db65db20a5c65a6e0c92fda89b45e sha256: "11a41f380fbcbda5bbba03ddcdbe0545e46094ab043783c46c70e8335831df03"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.5.16" version: "0.5.17"
stack_trace: stack_trace:
dependency: transitive dependency: transitive
description: description:
@ -814,18 +814,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: syncfusion_flutter_core name: syncfusion_flutter_core
sha256: "2baf60cd245a21a7069f036bbca1ca222633d38f57748e133da97a305712627c" sha256: aea119c8117953fa5decf4a313b431e556b0959cd35ff88f8fbdc0eda9bedb06
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "22.2.11" version: "23.1.36"
syncfusion_flutter_gauges: syncfusion_flutter_gauges:
dependency: "direct main" dependency: "direct main"
description: description:
name: syncfusion_flutter_gauges name: syncfusion_flutter_gauges
sha256: c086f17e84452e809b12f9832763ec4cea347b9f6e1e662a0e8addabca6cc2e5 sha256: ae46df959f60f0fed6a8c86c8c971883ed790450f8d32f546dc8a02cb4500cbd
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "22.2.11" version: "23.1.36"
synchronized: synchronized:
dependency: transitive dependency: transitive
description: description:
@ -1014,10 +1014,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: win32 name: win32
sha256: "9e82a402b7f3d518fb9c02d0e9ae45952df31b9bf34d77baf19da2de03fc2aaa" sha256: c97defd418eef4ec88c0d1652cdce84b9f7b63dd7198e266d06ac1710d527067
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.0.7" version: "5.0.8"
xdg_directories: xdg_directories:
dependency: transitive dependency: transitive
description: description:

View File

@ -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.2.2+10 version: 1.2.3+11
environment: environment:
sdk: '>=2.19.6 <3.0.0' sdk: '>=2.19.6 <3.0.0'
@ -31,7 +31,7 @@ dependencies:
package_info_plus: ^4.0.2 package_info_plus: ^4.0.2
shared_preferences: ^2.1.1 shared_preferences: ^2.1.1
intl: ^0.18.0 intl: ^0.18.0
syncfusion_flutter_gauges: ^22.1.34 syncfusion_flutter_gauges: ^23.1.36
file_selector: ^1.0.1 file_selector: ^1.0.1
file_picker: ^5.3.2 file_picker: ^5.3.2
slang: ^3.20.0 slang: ^3.20.0

View File

@ -72,8 +72,11 @@
"aboutAppText": "${appName} (${packageName}) Version ${version} Build ${buildNumber}\n\nDeveloped by dan63047\nFormulas provided by kerrmunism\nHistory provided by p1nkl0bst3r", "aboutAppText": "${appName} (${packageName}) Version ${version} Build ${buildNumber}\n\nDeveloped by dan63047\nFormulas provided by kerrmunism\nHistory provided by p1nkl0bst3r",
"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",
"statesViewEntry": "Level ${level}, ${gameTime} of gametime, ${friends} friends, ${rd} RD", "statesViewEntry": "Level ${level}, ${gameTime} of gametime, ${friends} friends, ${rd} RD",
"stateRemoved": "${date} state was removed from database!", "stateRemoved": "${date} state was removed from database!",
"matchRemoved": "${date} match was removed from database!",
"viewAllMatches": "View all matches",
"trackedPlayersViewTitle": "Stored data", "trackedPlayersViewTitle": "Stored data",
"trackedPlayersZeroEntrys": "Empty list. Press \"Track\" button in previous view to add current player here", "trackedPlayersZeroEntrys": "Empty list. Press \"Track\" button in previous view to add current player here",
"trackedPlayersOneEntry": "There is only one player", "trackedPlayersOneEntry": "There is only one player",
@ -209,7 +212,17 @@
"errors":{ "errors":{
"connection": "Some issue with connection: ${code} ${message}", "connection": "Some issue with connection: ${code} ${message}",
"noSuchUser": "No such user", "noSuchUser": "No such user",
"socketException": "Can't connect with ${host}: ${message}" "history": "History for that player is missing",
"clientException": "No internet connection",
"forbidden": "Your IP address is blocked.\nChange IP address or reach out to osk",
"tooManyRequests": "You have been rate limited. Try again later",
"internal": "Something happend on the tetr.io side",
"internalWebVersion": "Something happend on the tetr.io side (or on oskware_bridge, idk honestly)",
"oskwareBridge": "Something happend with oskware_bridge. Let dan63047 know",
"p1nkl0bst3rForbidden": "Third party API blocked your IP address.\nChange IP address or reach out to p1nkl0bst3r",
"p1nkl0bst3rTooManyRequests": "Too many requests to third party API. Try again later",
"p1nkl0bst3rinternal": "Something happend on the p1nkl0bst3r side",
"p1nkl0bst3rinternalWebVersion": "Something happend on the p1nkl0bst3r side (or on oskware_bridge, idk honestly)"
}, },
"countries(map)": { "countries(map)": {
"": "Not selected", "": "Not selected",

View File

@ -72,8 +72,11 @@
"aboutAppText": "${appName} (${packageName}) Версия ${version} Сборка ${buildNumber}\n\nРазработал dan63047\nФормулы предоставил kerrmunism\nИсторию предоставляет p1nkl0bst3r", "aboutAppText": "${appName} (${packageName}) Версия ${version} Сборка ${buildNumber}\n\nРазработал dan63047\nФормулы предоставил kerrmunism\nИсторию предоставляет p1nkl0bst3r",
"stateViewTitle": "Аккаунт ${nickname} ${date}", "stateViewTitle": "Аккаунт ${nickname} ${date}",
"statesViewTitle": "${number} состояний аккаунта ${nickname}", "statesViewTitle": "${number} состояний аккаунта ${nickname}",
"matchesViewTitle": "Матчи аккаунта ${nickname}",
"statesViewEntry": "${level} уровень, ${gameTime} сыграно, ${friends} друзей, ${rd} RD", "statesViewEntry": "${level} уровень, ${gameTime} сыграно, ${friends} друзей, ${rd} RD",
"stateRemoved": "Состояние от ${date} было удалено из локальной базы данных!", "stateRemoved": "Состояние от ${date} было удалено из локальной базы данных!",
"matchRemoved": "Матч от ${date} был удален из локальной базы данных!",
"viewAllMatches": "Все матчи",
"trackedPlayersViewTitle": "Сохранённые данные", "trackedPlayersViewTitle": "Сохранённые данные",
"trackedPlayersZeroEntrys": "Пустой список. Вернитесь на предыдущий экран и нажмите кнопку \"Отслеживать\", чтобы текущий игрок появился здесь", "trackedPlayersZeroEntrys": "Пустой список. Вернитесь на предыдущий экран и нажмите кнопку \"Отслеживать\", чтобы текущий игрок появился здесь",
"trackedPlayersOneEntry": "В списке только один игрок", "trackedPlayersOneEntry": "В списке только один игрок",
@ -209,7 +212,17 @@
"errors":{ "errors":{
"connection": "Проблема с подключением: ${code} ${message}", "connection": "Проблема с подключением: ${code} ${message}",
"noSuchUser": "Нет такого пользователя", "noSuchUser": "Нет такого пользователя",
"socketException": "Невозможно подключиться к ${host}: ${message}" "history": "История данного игрока отсутствует",
"clientException": "Нет соединения с интернетом",
"forbidden": "Ваш IP адрес заблокирован.\nСмените IP адрес или свяжитесь с osk-ом",
"tooManyRequests": "Слишком много запросов. Попробуйте позже",
"internal": "Что-то случилось на стороне tetr.io",
"internalWebVersion": "Что-то случилось на стороне tetr.io (или на стороне oskware_bridge, я хз если честно)",
"oskwareBridge": "Что-то случилось с oskware_bridge. Дайте dan63047 знать",
"p1nkl0bst3rForbidden": "Стороннее API заблокировало ваш IP адрес.\nСмените IP адрес или свяжитесь с p1nkl0bst3r-ом",
"p1nkl0bst3rTooManyRequests": "Слишком много запросов к стороннему API. Попробуйте позже",
"p1nkl0bst3rinternal": "Что-то случилось на стороне p1nkl0bst3r-а",
"p1nkl0bst3rinternalWebVersion": "Что-то случилось на стороне p1nkl0bst3r-а (или на стороне oskware_bridge, я хз если честно)"
}, },
"countries(map)": { "countries(map)": {
"": "Не выбрана", "": "Не выбрана",