From 07929ca6f7c2c151e9d6ee4e4aac8b7bc081b7e8 Mon Sep 17 00:00:00 2001 From: dan63047 Date: Sat, 3 Feb 2024 16:02:58 +0300 Subject: [PATCH 1/4] Fetching old Tetra League matches Also changed behavior of main view fetch function --- lib/data_objects/tetrio.dart | 9 ++- lib/gen/strings.g.dart | 20 +++++- lib/services/tetrio_crud.dart | 94 ++++++++++++++++++++++++++ lib/views/compare_view.dart | 35 ++++------ lib/views/main_view.dart | 120 ++++++++++++++++++++-------------- lib/views/tl_match_view.dart | 7 +- res/i18n/strings.i18n.json | 4 ++ res/i18n/strings_ru.i18n.json | 4 ++ 8 files changed, 218 insertions(+), 75 deletions(-) diff --git a/lib/data_objects/tetrio.dart b/lib/data_objects/tetrio.dart index 5e555b8..6df9f83 100644 --- a/lib/data_objects/tetrio.dart +++ b/lib/data_objects/tetrio.dart @@ -830,7 +830,14 @@ class EndContextMulti { required this.tertiaryTracking, required this.extra, required this.extraTracking, - required this.success}); + required this.success}){ + nerdStats = NerdStats(secondary, tertiary, extra); + nerdStatsTracking = [for (int i = 0; i < secondaryTracking.length; i++) NerdStats(secondaryTracking[i], tertiaryTracking[i], extraTracking[i])]; + estTr = EstTr(secondary, tertiary, extra, nerdStats.app, nerdStats.dss, nerdStats.dsp, nerdStats.gbe); + estTrTracking = [for (int i = 0; i < secondaryTracking.length; i++) EstTr(secondaryTracking[i], tertiaryTracking[i], extraTracking[i], nerdStatsTracking[i].app, nerdStatsTracking[i].dss, nerdStatsTracking[i].dsp, nerdStatsTracking[i].gbe)]; + playstyle = Playstyle(secondary, tertiary, nerdStats.app, nerdStats.vsapm, nerdStats.dsp, nerdStats.gbe, estTr.srarea, estTr.statrank); + playstyleTracking = [for (int i = 0; i < secondaryTracking.length; i++) Playstyle(secondaryTracking[i], tertiaryTracking[i], nerdStatsTracking[i].app, nerdStatsTracking[i].vsapm, nerdStatsTracking[i].dsp, nerdStatsTracking[i].gbe, estTrTracking[i].srarea, estTrTracking[i].statrank)]; + } EndContextMulti.fromJson(Map json) { userId = json['id'] ?? json['user']['_id']; diff --git a/lib/gen/strings.g.dart b/lib/gen/strings.g.dart index 7b9f9dc..1e105df 100644 --- a/lib/gen/strings.g.dart +++ b/lib/gen/strings.g.dart @@ -4,9 +4,9 @@ /// To regenerate, run: `dart run slang` /// /// Locales: 2 -/// Strings: 1008 (504 per locale) +/// Strings: 1016 (508 per locale) /// -/// Built on 2024-01-22 at 19:27 UTC +/// Built on 2024-02-03 at 12:49 UTC // coverage:ignore-file // ignore_for_file: type=lint @@ -167,6 +167,9 @@ class Translations implements BaseTranslations { String get closeSearch => 'Close search'; String get refresh => 'Refresh'; String get fetchAndsaveTLHistory => 'Get player history'; + String get fetchAndSaveOldTLmatches => 'Get Tetra League matches history'; + String fetchAndsaveTLHistoryResult({required Object number}) => '${number} states was found'; + String fetchAndSaveOldTLmatchesResult({required Object number}) => '${number} matches was found'; String get showStoredData => 'Show stored data'; String get statsCalc => 'Stats Calculator'; String get settings => 'Settings'; @@ -697,6 +700,7 @@ class _StringsErrorsEn { String connection({required Object code, required Object message}) => 'Some issue with connection: ${code} ${message}'; String get noSuchUser => 'No such user'; String get history => 'History for that player is missing'; + String get p1nkl0bst3rTLmatches => 'No Tetra League matches was found'; 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'; @@ -755,6 +759,9 @@ class _StringsRu implements Translations { @override String get closeSearch => 'Закрыть поиск'; @override String get refresh => 'Обновить'; @override String get fetchAndsaveTLHistory => 'Получить историю игрока'; + @override String get fetchAndSaveOldTLmatches => 'Получить старые матчи Тетра Лиги'; + @override String fetchAndsaveTLHistoryResult({required Object number}) => '${number} состояний было найдено'; + @override String fetchAndSaveOldTLmatchesResult({required Object number}) => '${number} старых матчей было найдено'; @override String get showStoredData => 'Показать сохранённые данные'; @override String get statsCalc => 'Калькулятор статистики'; @override String get settings => 'Настройки'; @@ -1285,6 +1292,7 @@ class _StringsErrorsRu implements _StringsErrorsEn { @override String connection({required Object code, required Object message}) => 'Проблема с подключением: ${code} ${message}'; @override String get noSuchUser => 'Нет такого пользователя'; @override String get history => 'История данного игрока отсутствует'; + @override String get p1nkl0bst3rTLmatches => 'Старых матчей Тетра Лиги не было найдено'; @override String get clientException => 'Нет соединения с интернетом'; @override String get forbidden => 'Ваш IP адрес заблокирован.\nСмените IP адрес или свяжитесь с osk-ом'; @override String get tooManyRequests => 'Слишком много запросов. Попробуйте позже'; @@ -1335,6 +1343,9 @@ extension on Translations { case 'closeSearch': return 'Close search'; case 'refresh': return 'Refresh'; case 'fetchAndsaveTLHistory': return 'Get player history'; + case 'fetchAndSaveOldTLmatches': return 'Get Tetra League matches history'; + case 'fetchAndsaveTLHistoryResult': return ({required Object number}) => '${number} states was found'; + case 'fetchAndSaveOldTLmatchesResult': return ({required Object number}) => '${number} matches was found'; case 'showStoredData': return 'Show stored data'; case 'statsCalc': return 'Stats Calculator'; case 'settings': return 'Settings'; @@ -1540,6 +1551,7 @@ extension on Translations { 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.history': return 'History for that player is missing'; + case 'errors.p1nkl0bst3rTLmatches': return 'No Tetra League matches was found'; 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'; @@ -1849,6 +1861,9 @@ extension on _StringsRu { case 'closeSearch': return 'Закрыть поиск'; case 'refresh': return 'Обновить'; case 'fetchAndsaveTLHistory': return 'Получить историю игрока'; + case 'fetchAndSaveOldTLmatches': return 'Получить старые матчи Тетра Лиги'; + case 'fetchAndsaveTLHistoryResult': return ({required Object number}) => '${number} состояний было найдено'; + case 'fetchAndSaveOldTLmatchesResult': return ({required Object number}) => '${number} старых матчей было найдено'; case 'showStoredData': return 'Показать сохранённые данные'; case 'statsCalc': return 'Калькулятор статистики'; case 'settings': return 'Настройки'; @@ -2054,6 +2069,7 @@ extension on _StringsRu { case 'errors.connection': return ({required Object code, required Object message}) => 'Проблема с подключением: ${code} ${message}'; case 'errors.noSuchUser': return 'Нет такого пользователя'; case 'errors.history': return 'История данного игрока отсутствует'; + case 'errors.p1nkl0bst3rTLmatches': return 'Старых матчей Тетра Лиги не было найдено'; case 'errors.clientException': return 'Нет соединения с интернетом'; case 'errors.forbidden': return 'Ваш IP адрес заблокирован.\nСмените IP адрес или свяжитесь с osk-ом'; case 'errors.tooManyRequests': return 'Слишком много запросов. Попробуйте позже'; diff --git a/lib/services/tetrio_crud.dart b/lib/services/tetrio_crud.dart index 5d42d00..7f8843b 100644 --- a/lib/services/tetrio_crud.dart +++ b/lib/services/tetrio_crud.dart @@ -393,6 +393,100 @@ class TetrioService extends DB { } } + /// Docs later + Future> fetchAndSaveOldTLmatches(String userID) async { + Uri url; + if (kIsWeb) { + url = Uri.https('ts.dan63.by', 'oskware_bridge.php', {"endpoint": "TLMatches", "user": userID}); + } else { + url = Uri.https('api.p1nkl0bst3r.xyz', 'tlmatches/$userID'); + } + + try{ + final response = await client.get(url); + + switch (response.statusCode) { + case 200: + // that one api returns csv instead of json + List> csv = const CsvToListConverter().convert(response.body)..removeAt(0); + List matches = []; + + // parsing data into TetraLeagueAlphaRecord objects + for (var entry in csv){ + TetraLeagueAlphaRecord match = TetraLeagueAlphaRecord( + replayId: entry[0], + ownId: entry[0], // i gonna disting p1nkl0bst3r entries with it + timestamp: DateTime.parse(entry[1]), + endContext: [ + EndContextMulti( + userId: entry[2], + username: entry[3].toString(), + naturalOrder: 0, + inputs: -1, + piecesPlaced: -1, + handling: Handling(arr: -1, das: -1, sdf: -1, dcd: 0, cancel: true, safeLock: true), + points: entry[4], + wins: entry[4], + secondary: entry[6], + secondaryTracking: [], + tertiary: entry[5], + tertiaryTracking: [], + extra: entry[7], + extraTracking: [], + success: true + ), + EndContextMulti( + userId: entry[8], + username: entry[9].toString(), + naturalOrder: 1, + inputs: -1, + piecesPlaced: -1, + handling: Handling(arr: -1, das: -1, sdf: -1, dcd: 0, cancel: true, safeLock: true), + points: entry[10], + wins: entry[10], + secondary: entry[12], + secondaryTracking: [], + tertiary: entry[11], + tertiaryTracking: [], + extra: entry[13], + extraTracking: [], + success: false + ) + ], + replayAvalable: false + ); + matches.add(match); + } + + // trying to dump it to local DB + TetraLeagueAlphaStream fakeStream = TetraLeagueAlphaStream(userId: userID, records: matches); + saveTLMatchesFromStream(fakeStream); + + return matches; + case 404: + developer.log("fetchAndSaveOldTLmatches: 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("fetchAndSaveOldTLmatches: Failed to fetch history", name: "services/tetrio_crud", error: response.statusCode); + throw ConnectionIssue(response.statusCode, response.reasonPhrase??"No reason"); + } + } on http.ClientException catch (e, s) { + developer.log("$e, $s"); + throw http.ClientException(e.message, e.uri); + } + } + /// Retrieves full Tetra League leaderboard from Tetra Channel api. Returns a leaderboard object. Throws an exception if fails to retrieve. Future fetchTLLeaderboard() async { try{ diff --git a/lib/views/compare_view.dart b/lib/views/compare_view.dart index b22661c..2dd567f 100644 --- a/lib/views/compare_view.dart +++ b/lib/views/compare_view.dart @@ -1,5 +1,3 @@ -// ignore_for_file: use_build_context_synchronously - import 'dart:io'; import 'dart:math'; import 'package:flutter/foundation.dart'; @@ -70,8 +68,7 @@ class CompareState extends State { theRedSide = [null, null, average]; return setState(() {}); }on Exception { - ScaffoldMessenger.of(context) - .showSnackBar(SnackBar(content: Text(t.compareViewWrongValue(value: user)))); + if (context.mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.compareViewWrongValue(value: user)))); return; } } @@ -126,8 +123,7 @@ class CompareState extends State { } theRedSide = [player, dStates, player.tlSeason1]; } on Exception { - ScaffoldMessenger.of(context) - .showSnackBar(SnackBar(content: Text(t.compareViewWrongValue(value: user)))); + if (context.mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.compareViewWrongValue(value: user)))); } _justUpdate(); } @@ -146,8 +142,7 @@ class CompareState extends State { theGreenSide = [null, null, average]; return setState(() {}); }on Exception { - ScaffoldMessenger.of(context) - .showSnackBar(SnackBar(content: Text("Falied to assign $user"))); + if (context.mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("Falied to assign $user"))); return; } } @@ -202,8 +197,7 @@ class CompareState extends State { } theGreenSide = [player, dStates, player.tlSeason1]; } on Exception { - ScaffoldMessenger.of(context) - .showSnackBar(SnackBar(content: Text("Falied to assign $user"))); + if (context.mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("Falied to assign $user"))); } _justUpdate(); } @@ -213,19 +207,16 @@ class CompareState extends State { theGreenSide[2] = user.tlSeason1;}); } - double getWinrateByTR(double yourGlicko, double yourRD, double notyourGlicko, - double notyourRD) { + double getWinrateByTR(double yourGlicko, double yourRD, double notyourGlicko,double notyourRD) { return ((1 / - (1 + - pow( - 10, - (notyourGlicko - yourGlicko) / - (400 * - sqrt(1 + - (3 * - pow(0.0057564273, 2) * - (pow(yourRD, 2) + pow(notyourRD, 2)) / - pow(pi, 2)))))))); + (1 + pow(10, + (notyourGlicko - yourGlicko) / + (400 * sqrt(1 + (3 * pow(0.0057564273, 2) * + (pow(yourRD, 2) + pow(notyourRD, 2)) / pow(pi, 2) + ))) + ) + ) + )); } void _justUpdate() { diff --git a/lib/views/main_view.dart b/lib/views/main_view.dart index f82a24e..af5eb19 100644 --- a/lib/views/main_view.dart +++ b/lib/views/main_view.dart @@ -116,10 +116,10 @@ class _MainState extends State with TickerProviderStateMixin { /// That function initiate search of data about [player]. If [fetchHistory] is true, /// also attempting to retrieve players history. Can trow an Exception if fails - void changePlayer(String player, {bool fetchHistory = false}) { + void changePlayer(String player, {bool fetchHistory = false, bool fetchTLmatches = false}) { setState(() { _searchFor = player; - me = fetch(_searchFor, fetchHistory: fetchHistory); + me = fetch(_searchFor, fetchHistory: fetchHistory, fetchTLmatches: fetchTLmatches); }); } @@ -128,13 +128,14 @@ class _MainState extends State with TickerProviderStateMixin { } /// Retrieves data from 3 different Tetra Channel API endpoints + 1 endpoint from p1nkl0bst3r's API - /// using [nickOrID] of player. If [fetchHistory] is true, also retrieves players history from p1nkl0bst3r's API. + /// using [nickOrID] of player. /// - /// Returns list which contains players object, his TL records, previous states, TL matches, previos TL state, if player tracked (bool), news entries and topTR. + /// If [fetchHistory] is true, also retrieves players history from p1nkl0bst3r's API. If [fetchTLmatches] is true, also retrieves players old Tetra League + /// matches from p1nkl0bst3r's API. Returns list which contains [TetrioPlayer], his records, previous states, TL matches, previous TL state, + /// if player tracked (bool), news entries and topTR. /// - /// If at least one request to some endpoint fails, whole function will throw an exception. - /// TODO: Change this behavior - Future fetch(String nickOrID, {bool fetchHistory = false}) async { + /// If at least one request to Tetra Channel API fails, whole function will throw an exception. + Future fetch(String nickOrID, {bool fetchHistory = false, bool fetchTLmatches = false}) async { TetrioPlayer me; // If user trying to search with discord id @@ -173,13 +174,28 @@ class _MainState extends State with TickerProviderStateMixin { TetraLeagueAlpha? compareWith; Set uniqueTL = {}; tlMatches = tlStream.records; - var storedRecords = await teto.getTLMatchesbyPlayerID(me.userId); // get old matches + List storedRecords = await teto.getTLMatchesbyPlayerID(me.userId); // get old matches if (isTracking){ // if tracked - save data to local DB await teto.storeState(me); await teto.saveTLMatchesFromStream(tlStream); } // building list of TL matches + if(fetchTLmatches) { + try{ + List oldMatches = await teto.fetchAndSaveOldTLmatches(_searchFor); + storedRecords.addAll(oldMatches); + if (context.mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.fetchAndSaveOldTLmatchesResult(number: oldMatches.length)))); + }on TetrioHistoryNotExist{ + if (context.mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.errors.p1nkl0bst3rTLmatches))); + }on P1nkl0bst3rForbidden { + if (context.mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.errors.p1nkl0bst3rForbidden))); + }on P1nkl0bst3rInternalProblem { + if (context.mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.errors.p1nkl0bst3rinternal))); + }on P1nkl0bst3rTooManyRequests{ + if (context.mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.errors.p1nkl0bst3rTooManyRequests))); + } + } for (var match in storedRecords) { // add stored match to list only if it missing from retrived ones if (!tlMatches.contains(match)) tlMatches.add(match); @@ -192,7 +208,21 @@ class _MainState extends State with TickerProviderStateMixin { }); // Handling history - if(fetchHistory) await teto.fetchAndsaveTLHistory(_searchFor); // Retrieve if needed + if(fetchHistory){ + try{ + var history = await teto.fetchAndsaveTLHistory(_searchFor); // Retrieve if needed + if (context.mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.fetchAndsaveTLHistoryResult(number: history.length)))); + }on TetrioHistoryNotExist{ + if (context.mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.noHistorySaved))); + }on P1nkl0bst3rForbidden { + if (context.mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.errors.p1nkl0bst3rForbidden))); + }on P1nkl0bst3rInternalProblem { + if (context.mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.errors.p1nkl0bst3rinternal))); + }on P1nkl0bst3rTooManyRequests{ + if (context.mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.errors.p1nkl0bst3rTooManyRequests))); + } + } + states.addAll(await teto.getPlayer(me.userId)); for (var element in states) { // For graphs I need only unique entries if (uniqueTL.isNotEmpty && uniqueTL.last != element.tlSeason1) uniqueTL.add(element.tlSeason1); @@ -271,6 +301,10 @@ class _MainState extends State with TickerProviderStateMixin { value: "history", child: Text(t.fetchAndsaveTLHistory), ), + PopupMenuItem( + value: "TLmatches", + child: Text(t.fetchAndSaveOldTLmatches), + ), PopupMenuItem( value: "/states", child: Text(t.showStoredData), @@ -292,6 +326,9 @@ class _MainState extends State with TickerProviderStateMixin { case "history": changePlayer(_searchFor, fetchHistory: true); break; + case "TLmatches": + changePlayer(_searchFor, fetchTLmatches: true); + break; default: context.go(value); } @@ -373,15 +410,6 @@ class _MainState extends State with TickerProviderStateMixin { var err = snapshot.error as ConnectionIssue; errText = t.errors.connection(code: err.code, message: err.message); break; - case P1nkl0bst3rForbidden: - errText = t.errors.p1nkl0bst3rForbidden; - 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; @@ -548,37 +576,33 @@ class _TLRecords extends StatelessWidget { @override Widget build(BuildContext context) { - bool bigScreen = MediaQuery.of(context).size.width > 768; - return ListView( // TODO: Redo using ListView.builder() - physics: const AlwaysScrollableScrollPhysics(), - children: (data.isNotEmpty) - ? [for (var value in data) ListTile( - leading: Text("${value.endContext.firstWhere((element) => element.userId == userID).points} : ${value.endContext.firstWhere((element) => element.userId != 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 != userID).username}"), - subtitle: Text(_dateFormat.format(value.timestamp)), - trailing: Table(defaultColumnWidth: const IntrinsicColumnWidth(), - defaultVerticalAlignment: TableCellVerticalAlignment.baseline, - textBaseline: TextBaseline.alphabetic, - columnWidths: const { - 0: FixedColumnWidth(50), - 2: FixedColumnWidth(50), - }, - children: [ - TableRow(children: [Text(_f2.format(value.endContext.firstWhere((element) => element.userId == userID).secondary), textAlign: TextAlign.right, style: const TextStyle(height: 1.1)), const Text(" :", style: TextStyle(height: 1.1)), Text(_f2.format(value.endContext.firstWhere((element) => element.userId != userID).secondary), textAlign: TextAlign.right, style: const TextStyle(height: 1.1)), const Text(" APM", textAlign: TextAlign.right, style: TextStyle(height: 1.1))]), - TableRow(children: [Text(_f2.format(value.endContext.firstWhere((element) => element.userId == userID).tertiary), textAlign: TextAlign.right, style: const TextStyle(height: 1.1)), const Text(" :", style: TextStyle(height: 1.1)), Text(_f2.format(value.endContext.firstWhere((element) => element.userId != userID).tertiary), textAlign: TextAlign.right, style: const TextStyle(height: 1.1)), const Text(" PPS", textAlign: TextAlign.right, style: TextStyle(height: 1.1))]), - TableRow(children: [Text(_f2.format(value.endContext.firstWhere((element) => element.userId == userID).extra), textAlign: TextAlign.right, style: const TextStyle(height: 1.1)), const Text(" :", style: TextStyle(height: 1.1)), Text(_f2.format(value.endContext.firstWhere((element) => element.userId != userID).extra), textAlign: TextAlign.right, style: const TextStyle(height: 1.1)), const Text(" VS", textAlign: TextAlign.right, style: TextStyle(height: 1.1))]), - ],), - onTap: (){Navigator.push( - context, - MaterialPageRoute( - builder: (context) => TlMatchResultView(record: value, initPlayerId: userID), - ), - );}, - )] - : [Center(child: Text(t.noRecords, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 28)))], + bool bigScreen = MediaQuery.of(context).size.width > 768; + if (data.isEmpty) return Center(child: Text(t.noRecords, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 28))); + return ListView.builder( + physics: const AlwaysScrollableScrollPhysics(), + itemCount: data.length, + itemBuilder: (BuildContext context, int index) { + return ListTile( + 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) : + const TextStyle(fontSize: 28)), + title: Text("vs. ${data[index].endContext.firstWhere((element) => element.userId != userID).username}"), + subtitle: Text(_dateFormat.format(data[index].timestamp)), + trailing: Table(defaultColumnWidth: const IntrinsicColumnWidth(), + defaultVerticalAlignment: TableCellVerticalAlignment.baseline, + textBaseline: TextBaseline.alphabetic, + columnWidths: const { + 0: FixedColumnWidth(50), + 2: FixedColumnWidth(50), + }, + children: [ + TableRow(children: [Text(_f2.format(data[index].endContext.firstWhere((element) => element.userId == userID).secondary), textAlign: TextAlign.right, style: const TextStyle(height: 1.1)), const Text(" :", style: TextStyle(height: 1.1)), Text(_f2.format(data[index].endContext.firstWhere((element) => element.userId != userID).secondary), textAlign: TextAlign.right, style: const TextStyle(height: 1.1)), const Text(" APM", textAlign: TextAlign.right, style: TextStyle(height: 1.1))]), + TableRow(children: [Text(_f2.format(data[index].endContext.firstWhere((element) => element.userId == userID).tertiary), textAlign: TextAlign.right, style: const TextStyle(height: 1.1)), const Text(" :", style: TextStyle(height: 1.1)), Text(_f2.format(data[index].endContext.firstWhere((element) => element.userId != userID).tertiary), textAlign: TextAlign.right, style: const TextStyle(height: 1.1)), const Text(" PPS", textAlign: TextAlign.right, style: TextStyle(height: 1.1))]), + TableRow(children: [Text(_f2.format(data[index].endContext.firstWhere((element) => element.userId == userID).extra), textAlign: TextAlign.right, style: const TextStyle(height: 1.1)), const Text(" :", style: TextStyle(height: 1.1)), Text(_f2.format(data[index].endContext.firstWhere((element) => element.userId != userID).extra), textAlign: TextAlign.right, style: const TextStyle(height: 1.1)), const Text(" VS", textAlign: TextAlign.right, style: TextStyle(height: 1.1))]), + ],), + onTap: () => Navigator.push(context, MaterialPageRoute(builder: (context) => TlMatchResultView(record: data[index], initPlayerId: userID))), ); + }); } } diff --git a/lib/views/tl_match_view.dart b/lib/views/tl_match_view.dart index b353179..219b725 100644 --- a/lib/views/tl_match_view.dart +++ b/lib/views/tl_match_view.dart @@ -195,6 +195,9 @@ class TlMatchResultState extends State { ), ), ), + if (widget.record.ownId == widget.record.replayId) SliverToBoxAdapter( + child: Center(child: Text(t.p1nkl0bst3rAlert, textAlign: TextAlign.center)), + ), SliverToBoxAdapter(child: FutureBuilder(future: replayData, builder: (context, snapshot) { switch(snapshot.connectionState){ case ConnectionState.none: @@ -470,8 +473,8 @@ class TlMatchResultState extends State { ) ], ), - const Divider(), - Column( + if (widget.record.ownId != widget.record.replayId) const Divider(), + if (widget.record.ownId != widget.record.replayId) Column( children: [ Padding( padding: const EdgeInsets.only(bottom: 16), diff --git a/res/i18n/strings.i18n.json b/res/i18n/strings.i18n.json index b186ea9..92042ac 100644 --- a/res/i18n/strings.i18n.json +++ b/res/i18n/strings.i18n.json @@ -32,6 +32,9 @@ "closeSearch": "Close search", "refresh": "Refresh", "fetchAndsaveTLHistory": "Get player history", + "fetchAndSaveOldTLmatches": "Get Tetra League matches history", + "fetchAndsaveTLHistoryResult": "${number} states was found", + "fetchAndSaveOldTLmatchesResult": "${number} matches was found", "showStoredData": "Show stored data", "statsCalc": "Stats Calculator", "settings": "Settings", @@ -246,6 +249,7 @@ "connection": "Some issue with connection: ${code} ${message}", "noSuchUser": "No such user", "history": "History for that player is missing", + "p1nkl0bst3rTLmatches": "No Tetra League matches was found", "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", diff --git a/res/i18n/strings_ru.i18n.json b/res/i18n/strings_ru.i18n.json index 25f4ffd..ebed9c7 100644 --- a/res/i18n/strings_ru.i18n.json +++ b/res/i18n/strings_ru.i18n.json @@ -32,6 +32,9 @@ "closeSearch": "Закрыть поиск", "refresh": "Обновить", "fetchAndsaveTLHistory": "Получить историю игрока", + "fetchAndSaveOldTLmatches": "Получить старые матчи Тетра Лиги", + "fetchAndsaveTLHistoryResult": "${number} состояний было найдено", + "fetchAndSaveOldTLmatchesResult": "${number} старых матчей было найдено", "showStoredData": "Показать сохранённые данные", "statsCalc": "Калькулятор статистики", "settings": "Настройки", @@ -246,6 +249,7 @@ "connection": "Проблема с подключением: ${code} ${message}", "noSuchUser": "Нет такого пользователя", "history": "История данного игрока отсутствует", + "p1nkl0bst3rTLmatches": "Старых матчей Тетра Лиги не было найдено", "clientException": "Нет соединения с интернетом", "forbidden": "Ваш IP адрес заблокирован.\nСмените IP адрес или свяжитесь с osk-ом", "tooManyRequests": "Слишком много запросов. Попробуйте позже", From 7099f7471a5f10824abc796a1a0ddd61f709554f Mon Sep 17 00:00:00 2001 From: dan63047 Date: Tue, 6 Feb 2024 23:38:52 +0300 Subject: [PATCH 2/4] TetrioService now should eat less ram Also app now checks if valid nickname was entered in "Your TETR.IO account" dialog. No need to paste userID, app will do it by itself. --- lib/gen/strings.g.dart | 18 +++--- lib/services/tetrio_crud.dart | 93 +++++++++++---------------- lib/views/main_view.dart | 7 +- lib/views/settings_view.dart | 24 ++++++- lib/views/tracked_players_view.dart | 13 ++-- res/i18n/strings.i18n.json | 4 +- res/i18n/strings_ru.i18n.json | 4 +- res/tetrio_badges/twc23_honorary.png | Bin 0 -> 7267 bytes 8 files changed, 81 insertions(+), 82 deletions(-) create mode 100644 res/tetrio_badges/twc23_honorary.png diff --git a/lib/gen/strings.g.dart b/lib/gen/strings.g.dart index 1e105df..c2eb46b 100644 --- a/lib/gen/strings.g.dart +++ b/lib/gen/strings.g.dart @@ -6,7 +6,7 @@ /// Locales: 2 /// Strings: 1016 (508 per locale) /// -/// Built on 2024-02-03 at 12:49 UTC +/// Built on 2024-02-06 at 20:25 UTC // coverage:ignore-file // ignore_for_file: type=lint @@ -224,8 +224,8 @@ class Translations implements BaseTranslations { String get importCancelled => 'Operation was cancelled'; String get importSuccess => 'Import successful'; String get yourID => 'Your TETR.IO account'; - String get yourIDAlertTitle => 'Your TETR.IO account nickname or ID'; - String get yourIDText => 'Every time when app loads, stats of that player will be fetched. Please prefer ID over nickname because nickname can be changed.'; + String get yourIDAlertTitle => 'Your nickname in TETR.IO'; + String get yourIDText => 'When app loads, it will retrieve data for this account'; String get language => 'Language'; 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'; @@ -816,8 +816,8 @@ class _StringsRu implements Translations { @override String get importCancelled => 'Операция была отменена'; @override String get importSuccess => 'Успешно импортировано'; @override String get yourID => 'Ваш аккаунт в TETR.IO'; - @override String get yourIDAlertTitle => 'Никнейм или ID вашего аккаунта в TETR.IO'; - @override String get yourIDText => 'Каждый раз, когда приложение запускается, приложение будет получать статистику этого игрока. Пожалуйста, отдайте предпочтение ID, так как никнейм можно изменить.'; + @override String get yourIDAlertTitle => 'Ваш ник в TETR.IO'; + @override String get yourIDText => 'При запуске приложения оно будет получать статистику этого игрока.'; @override String get language => 'Язык (Language)'; @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'; @@ -1400,8 +1400,8 @@ extension on Translations { case 'importCancelled': return 'Operation was cancelled'; case 'importSuccess': return 'Import successful'; case 'yourID': return 'Your TETR.IO account'; - case 'yourIDAlertTitle': return 'Your TETR.IO account nickname or ID'; - case 'yourIDText': return 'Every time when app loads, stats of that player will be fetched. Please prefer ID over nickname because nickname can be changed.'; + case 'yourIDAlertTitle': return 'Your nickname in TETR.IO'; + case 'yourIDText': return 'When app loads, it will retrieve data for this account'; case 'language': return 'Language'; 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'; @@ -1918,8 +1918,8 @@ extension on _StringsRu { case 'importCancelled': return 'Операция была отменена'; case 'importSuccess': return 'Успешно импортировано'; case 'yourID': return 'Ваш аккаунт в TETR.IO'; - case 'yourIDAlertTitle': return 'Никнейм или ID вашего аккаунта в TETR.IO'; - case 'yourIDText': return 'Каждый раз, когда приложение запускается, приложение будет получать статистику этого игрока. Пожалуйста, отдайте предпочтение ID, так как никнейм можно изменить.'; + case 'yourIDAlertTitle': return 'Ваш ник в TETR.IO'; + case 'yourIDText': return 'При запуске приложения оно будет получать статистику этого игрока.'; case 'language': return 'Язык (Language)'; 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'; diff --git a/lib/services/tetrio_crud.dart b/lib/services/tetrio_crud.dart index 7f8843b..b5bf668 100644 --- a/lib/services/tetrio_crud.dart +++ b/lib/services/tetrio_crud.dart @@ -27,7 +27,7 @@ const String endContext2 = "endContext2"; const String statesCol = "jsonStates"; const String player1id = "player1id"; const String player2id = "player2id"; -/// Table, that store players data, their stats and some moments of time +/// Table, that store players data, their stats at some moments of time const String createTetrioUsersTable = ''' CREATE TABLE IF NOT EXISTS "tetrioUsers" ( "id" TEXT UNIQUE, @@ -66,7 +66,7 @@ const String createTetrioTLReplayStats = ''' '''; class TetrioService extends DB { - Map> _players = {}; + final Map _players = {}; // I'm trying to send as less requests, as possible, so i'm caching the results of those requests. // Usually those maps looks like this: {"cached_until_unix_milliseconds": Object} @@ -82,9 +82,9 @@ class TetrioService extends DB { /// We should have only one instanse of this service static final TetrioService _shared = TetrioService._sharedInstance(); factory TetrioService() => _shared; - late final StreamController>> _tetrioStreamController; + late final StreamController> _tetrioStreamController; TetrioService._sharedInstance() { - _tetrioStreamController = StreamController>>.broadcast(onListen: () { + _tetrioStreamController = StreamController>.broadcast(onListen: () { _tetrioStreamController.sink.add(_players); }); } @@ -95,17 +95,15 @@ class TetrioService extends DB { await _loadPlayers(); } - Stream>> get allPlayers => _tetrioStreamController.stream; + Stream> get allPlayers => _tetrioStreamController.stream; /// Loading and sending to the stream everyone. Future _loadPlayers() async { - final allPlayers = await getAllPlayers(); - try{ - _players = allPlayers.toList().first; // ??? - }catch (e){ - developer.log("_loadPlayers: allPlayers.toList().first did oopsie", name: "services/tetrio_crud", error: e); - _players = {}; + final allPlayers = await getAllPlayerToTrack(); + for (var element in allPlayers) { + _players[element] = await getNicknameByID(element); } + developer.log("_loadPlayers: $_players", name: "services/tetrio_crud"); _tetrioStreamController.add(_players); } @@ -128,7 +126,10 @@ class TetrioService extends DB { Future getNicknameByID(String id) async { if (id.length <= 16) return id; // nicknames can be up to 16 symbols in length, that's how i'm differentiate nickname from ids try{ - return await getPlayer(id).then((value) => value.last.username); + await ensureDbIsOpen(); + final db = getDatabaseOrThrow(); + var request = await db.query(tetrioUsersTable, limit: 1, where: '$idCol = ?', whereArgs: [id.toLowerCase()]); + return request.first[nickCol] as String; } catch (e){ return await fetchPlayer(id).then((value) => value.username); } @@ -350,16 +351,8 @@ class TetrioService extends DB { // trying to dump it to local DB await ensureDbIsOpen(); final db = getDatabaseOrThrow(); - late List states; - try{ - // checking if tetra stats aware about that player TODO: is it necessary? - states = _players[id]!; - }catch(e){ - // if somehow not - create it - var player = await fetchPlayer(id); - await createPlayer(player); - states = _players[id]!; - } + List states = await getPlayer(id); + if (states.isEmpty) await createPlayer(history.first); states.insertAll(0, history.reversed); final Map statesJson = {}; for (var e in states) { // making one big json out of this list @@ -367,7 +360,6 @@ class TetrioService extends DB { } // and putting it to local DB 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); @@ -732,7 +724,7 @@ class TetrioService extends DB { await db.delete(tetrioTLReplayStatsTable, where: '$idCol = ?', whereArgs: [rID]); } - /// Retrieves Blitz, 40 Lines and Zen records for a given [playerID] from Tetra Channel api. Returns Map, which contains user id (`user`), + /// Retrieves Blitz, 40 Lines and Zen records for a given [userID] from Tetra Channel api. Returns Map, which contains user id (`user`), /// Blitz (`blitz`) and 40 Lines (`sprint`) record objects and Zen object (`zen`). Throws an exception if fails to retrieve. Future> fetchRecords(String userID) async { try{ @@ -811,9 +803,7 @@ class TetrioService extends DB { // converting to json and store final Map statesJson = {(tetrioPlayer.state.millisecondsSinceEpoch ~/ 1000).toString(): tetrioPlayer.toJson()}; db.insert(tetrioUsersTable, {idCol: tetrioPlayer.userId, nickCol: tetrioPlayer.username, statesCol: jsonEncode(statesJson)}); - _players.addEntries({ - tetrioPlayer.userId: [tetrioPlayer] - }.entries); + _players.addEntries({tetrioPlayer.userId: tetrioPlayer.username}.entries); _tetrioStreamController.add(_players); } @@ -841,7 +831,6 @@ class TetrioService extends DB { await ensureDbIsOpen(); final db = getDatabaseOrThrow(); final players = await db.query(tetrioUsersToTrackTable); - developer.log("getAllPlayerToTrack: $players", name: "services/tetrio_crud"); return players.map((noteRow) => noteRow["id"].toString()); } @@ -853,25 +842,22 @@ class TetrioService extends DB { if (deletedPlayer != 1) { throw CouldNotDeletePlayer(); } else { - // _players.removeWhere((key, value) => key == id); - // _tetrioStreamController.add(_players); + _players.removeWhere((key, value) => key == id); + _tetrioStreamController.add(_players); } } /// Saves state (which is [tetrioPlayer]) to the local database. Future storeState(TetrioPlayer tetrioPlayer) async { - await ensureDbIsOpen(); - final db = getDatabaseOrThrow(); - late List states; - try { // retrieveing previous states - states = _players[tetrioPlayer.userId]!; - } catch (e) { // nothing found - player not exist - create them - await createPlayer(tetrioPlayer); - states = await getPlayer(tetrioPlayer.userId); + // if tetrio player doesn't have entry in database - just calling different function + List states = await getPlayer(tetrioPlayer.userId); + if (states.isEmpty) { + await createPlayer(tetrioPlayer); + return; } // we not going to add state, that is same, as the previous - bool test = _players[tetrioPlayer.userId]!.last.isSameState(tetrioPlayer); + bool test = states.last.isSameState(tetrioPlayer); if (test == false) states.add(tetrioPlayer); // Making map of the states @@ -880,21 +866,21 @@ class TetrioService extends DB { // Saving in format: {"unix_seconds": json_of_state} statesJson.addEntries({(e.state.millisecondsSinceEpoch ~/ 1000).toString(): e.toJson()}.entries); } + // Rewrite our database + await ensureDbIsOpen(); + final db = getDatabaseOrThrow(); await db.update(tetrioUsersTable, {idCol: tetrioPlayer.userId, nickCol: tetrioPlayer.username, statesCol: jsonEncode(statesJson)}, where: '$idCol = ?', whereArgs: [tetrioPlayer.userId]); - _players[tetrioPlayer.userId]!.add(tetrioPlayer); - _tetrioStreamController.add(_players); } /// Remove state (which is [tetrioPlayer]) from the local database Future deleteState(TetrioPlayer tetrioPlayer) async { await ensureDbIsOpen(); final db = getDatabaseOrThrow(); - late List states; + List states = await getPlayer(tetrioPlayer.userId); // removing state from map that contain every state of each user - _players[tetrioPlayer.userId]!.removeWhere((element) => element.state == tetrioPlayer.state); - states = _players[tetrioPlayer.userId]!; + states.removeWhere((element) => element.state == tetrioPlayer.state); // Making map of the states (without deleted one) final Map statesJson = {}; @@ -904,7 +890,6 @@ class TetrioService extends DB { // Rewriting database entry with new json await db.update(tetrioUsersTable, {idCol: tetrioPlayer.userId, nickCol: tetrioPlayer.username, statesCol: jsonEncode(statesJson)}, where: '$idCol = ?', whereArgs: [tetrioPlayer.userId]); - _players[tetrioPlayer.userId]!.add(tetrioPlayer); _tetrioStreamController.add(_players); } @@ -924,7 +909,7 @@ class TetrioService extends DB { rawStates.forEach((k, v) => states.add(TetrioPlayer.fromJson(v, DateTime.fromMillisecondsSinceEpoch(int.parse(k) * 1000), id, results.first[nickCol] as String))); // updating the stream _players.removeWhere((key, value) => key == id); - _players.addEntries({states.last.userId: states}.entries); + _players.addEntries({states.last.userId: states.last.username}.entries); _tetrioStreamController.add(_players); return states; } @@ -1034,20 +1019,18 @@ class TetrioService extends DB { } } - /// Basucally, retrieves whole [tetrioUsersTable] and do stupud things idk - /// Returns god knows what. TODO: Rewrite this shit - Future>>> getAllPlayers() async { + /// Retrieves whole [tetrioUsersTable] and returns Map with [TetrioPlayer] objects of everyone in database + Future>> getAllPlayers() async { await ensureDbIsOpen(); final db = getDatabaseOrThrow(); final players = await db.query(tetrioUsersTable); Map> data = {}; - return players.map((row) { - // what the fuck am i doing here? - var test = json.decode(row['jsonStates'] as String); + for (var entry in players){ + var test = json.decode(entry['jsonStates'] as String); List states = []; - test.forEach((k, v) => states.add(TetrioPlayer.fromJson(v, DateTime.fromMillisecondsSinceEpoch(int.parse(k) * 1000), row[idCol] as String, row[nickCol] as String))); + test.forEach((k, v) => states.add(TetrioPlayer.fromJson(v, DateTime.fromMillisecondsSinceEpoch(int.parse(k) * 1000), entry[idCol] as String, entry[nickCol] as String))); data.addEntries({states.last.userId: states}.entries); - return data; - }); + } + return data; } } diff --git a/lib/views/main_view.dart b/lib/views/main_view.dart index af5eb19..66d0d54 100644 --- a/lib/views/main_view.dart +++ b/lib/views/main_view.dart @@ -494,8 +494,9 @@ class _NavDrawerState extends State { case ConnectionState.waiting: case ConnectionState.active: final allPlayers = (snapshot.data != null) - ? snapshot.data as Map> - : >{}; + ? snapshot.data as Map + : {}; + allPlayers.remove(prefs.getString("player") ?? "6098518e3d5155e6ec429cdc"); // player from the home button will be delisted List keys = allPlayers.keys.toList(); return NestedScrollView( headerSliverBuilder: (context, value) { @@ -550,7 +551,7 @@ class _NavDrawerState extends State { itemBuilder: (context, index) { var i = allPlayers.length-1-index; // Last players in this map are most recent ones, they are gonna be shown at the top. return ListTile( - title: Text(allPlayers[keys[i]]?.last.username as String), // Takes last known username from list of states + title: Text(allPlayers[keys[i]]??keys[i]), // Takes last known username from list of states onTap: () { widget.changePlayer(keys[i]); // changes to chosen player Navigator.of(context).pop(); // and closes itself. diff --git a/lib/views/settings_view.dart b/lib/views/settings_view.dart index a0a0807..6edb8b9 100644 --- a/lib/views/settings_view.dart +++ b/lib/views/settings_view.dart @@ -1,4 +1,5 @@ import 'dart:io'; +import 'package:tetra_stats/data_objects/tetrio.dart'; import 'package:tetra_stats/main.dart' show packageInfo; import 'package:file_selector/file_selector.dart'; import 'package:file_picker/file_picker.dart'; @@ -66,6 +67,11 @@ class SettingsState extends State { await _setDefaultNickname(player); } + Future _removePlayer() async { + await prefs.remove('player'); + await _setDefaultNickname("dan63047"); + } + @override Widget build(BuildContext context) { final t = Translations.of(context); @@ -212,9 +218,21 @@ class SettingsState extends State { ), TextButton( child: Text(t.popupActions.submit), - onPressed: () { - _setPlayer(_playertext.text.toLowerCase().trim()); - Navigator.of(context).pop(); + onPressed: () async { + if (_playertext.text.isEmpty) { + _removePlayer(); + Navigator.of(context).pop(); + return; + } + late TetrioPlayer user; + try{ + user = await teto.fetchPlayer(_playertext.text.toLowerCase().trim()); + }on Exception{ + if (context.mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.errors.noSuchUser))); + return; + } + _setPlayer(user.userId); + if (context.mounted) Navigator.of(context).pop(); setState(() {}); }, ) diff --git a/lib/views/tracked_players_view.dart b/lib/views/tracked_players_view.dart index 1916dbb..2b11976 100644 --- a/lib/views/tracked_players_view.dart +++ b/lib/views/tracked_players_view.dart @@ -70,14 +70,15 @@ class TrackedPlayersState extends State { ), backgroundColor: Colors.black, body: SafeArea( - child: StreamBuilder( - stream: teto.allPlayers, + child: FutureBuilder( + future: teto.getAllPlayers(), builder: (context, snapshot) { switch (snapshot.connectionState) { case ConnectionState.none: - return const Center(child: Text('none case of StreamBuilder')); case ConnectionState.waiting: case ConnectionState.active: + return const Center(child: CircularProgressIndicator(color: Colors.white)); + case ConnectionState.done: final allPlayers = (snapshot.data != null) ? snapshot.data as Map> : >{}; List keys = allPlayers.keys.toList(); return NestedScrollView( @@ -114,7 +115,7 @@ class TrackedPlayersState extends State { icon: const Icon(Icons.delete_forever), onPressed: () { String nn = allPlayers[keys[index]]!.last.username; - teto.deletePlayer(keys[index]); + setState(() {teto.deletePlayer(keys[index]);}); ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.trackedPlayersStatesDeleted(nickname: nn)))); }, ), @@ -128,10 +129,6 @@ class TrackedPlayersState extends State { }, ); })); - case ConnectionState.done: - return const Center( - child: Text('done case of StreamBuilder', - style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 42), textAlign: TextAlign.center)); } })), ); diff --git a/res/i18n/strings.i18n.json b/res/i18n/strings.i18n.json index 92042ac..d063400 100644 --- a/res/i18n/strings.i18n.json +++ b/res/i18n/strings.i18n.json @@ -89,8 +89,8 @@ "importCancelled": "Operation was cancelled", "importSuccess": "Import successful", "yourID": "Your TETR.IO account", - "yourIDAlertTitle": "Your TETR.IO account nickname or ID", - "yourIDText": "Every time when app loads, stats of that player will be fetched. Please prefer ID over nickname because nickname can be changed.", + "yourIDAlertTitle": "Your nickname in TETR.IO", + "yourIDText": "When app loads, it will retrieve data for this account", "language": "Language", "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", diff --git a/res/i18n/strings_ru.i18n.json b/res/i18n/strings_ru.i18n.json index ebed9c7..054c6cd 100644 --- a/res/i18n/strings_ru.i18n.json +++ b/res/i18n/strings_ru.i18n.json @@ -89,8 +89,8 @@ "importCancelled": "Операция была отменена", "importSuccess": "Успешно импортировано", "yourID": "Ваш аккаунт в TETR.IO", - "yourIDAlertTitle": "Никнейм или ID вашего аккаунта в TETR.IO", - "yourIDText": "Каждый раз, когда приложение запускается, приложение будет получать статистику этого игрока. Пожалуйста, отдайте предпочтение ID, так как никнейм можно изменить.", + "yourIDAlertTitle": "Ваш ник в TETR.IO", + "yourIDText": "При запуске приложения оно будет получать статистику этого игрока.", "language": "Язык (Language)", "aboutApp": "О приложении", "aboutAppText": "${appName} (${packageName}) Версия ${version} Сборка ${buildNumber}\n\nРазработал dan63047\nФормулы предоставил kerrmunism\nИсторию предоставляет p1nkl0bst3r\nВозможность скачивать повторы из TETR.IO предоставляет szy", diff --git a/res/tetrio_badges/twc23_honorary.png b/res/tetrio_badges/twc23_honorary.png new file mode 100644 index 0000000000000000000000000000000000000000..469ce26dbf425a5a214cb17cd1add2ff52b5cd9e GIT binary patch literal 7267 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 Date: Wed, 7 Feb 2024 16:28:55 +0300 Subject: [PATCH 3/4] README update --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 975959b..d7506ff 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Track your and other players stats in TETR.IO You can [download an app](https://github.com/dan63047/TetraStats/releases), or [use web version](https://ts.dan63.by). -![Screenshot of the app 1](https://imgur.com/CKGYyBg.png) +![Screenshot of the app 1](https://imgur.com/e8CYvj3.png) # Available functionality - Advanced stats for players @@ -15,6 +15,7 @@ You can [download an app](https://github.com/dan63047/TetraStats/releases), or [ - Comparison to players, rank averages, and player stats from the past - Stats Calculator - Player history in charts +- Tetra League matches history # Special thanks - **kerrmunism** — formulas From 7688b49ef9cfdbeee6b68c10b01f3012523fec73 Mon Sep 17 00:00:00 2001 From: dan63047 Date: Fri, 9 Feb 2024 00:39:54 +0300 Subject: [PATCH 4/4] Design changes Colors in TL matches list, new search bar --- lib/gen/strings.g.dart | 12 +- lib/utils/text_shadow.dart | 6 + lib/views/main_view.dart | 80 ++-- lib/views/rank_averages_view.dart | 3 +- lib/widgets/search_box.dart | 89 ++++ lib/widgets/tl_thingy.dart | 685 +++++++++++++++--------------- lib/widgets/user_thingy.dart | 2 +- pubspec.yaml | 2 +- res/i18n/strings.i18n.json | 3 +- res/i18n/strings_ru.i18n.json | 1 + 10 files changed, 487 insertions(+), 396 deletions(-) create mode 100644 lib/utils/text_shadow.dart create mode 100644 lib/widgets/search_box.dart diff --git a/lib/gen/strings.g.dart b/lib/gen/strings.g.dart index c2eb46b..b28bfe7 100644 --- a/lib/gen/strings.g.dart +++ b/lib/gen/strings.g.dart @@ -4,9 +4,9 @@ /// To regenerate, run: `dart run slang` /// /// Locales: 2 -/// Strings: 1016 (508 per locale) +/// Strings: 1018 (509 per locale) /// -/// Built on 2024-02-06 at 20:25 UTC +/// Built on 2024-02-08 at 20:30 UTC // coverage:ignore-file // ignore_for_file: type=lint @@ -165,6 +165,7 @@ class Translations implements BaseTranslations { late final _StringsNewsPartsEn newsParts = _StringsNewsPartsEn._(_root); String get openSearch => 'Search player'; String get closeSearch => 'Close search'; + String get searchHint => 'Nickname, ID or Discord userID (with "ds:" prefix)'; String get refresh => 'Refresh'; String get fetchAndsaveTLHistory => 'Get player history'; String get fetchAndSaveOldTLmatches => 'Get Tetra League matches history'; @@ -265,7 +266,7 @@ class Translations implements BaseTranslations { String get winChance => 'Win Chance'; String get byGlicko => 'By Glicko'; String get byEstTR => 'By Est. TR'; - String compareViewNoValues({required Object avgR}) => 'Please, enter username, user ID, APM-PPS-VS values (divider doesn\'t matter, only order matter) or ${avgR} (where R is rank) to both of fields'; + String compareViewNoValues({required Object avgR}) => 'Please, enter username, user ID, APM-PPS-VS values (divider doesn\'t matter, only order matter) or ${avgR} (where R is rank) to both fields'; String compareViewWrongValue({required Object value}) => 'Falied to assign ${value}'; String get mostRecentOne => 'Most recent one'; String get yes => 'Yes'; @@ -757,6 +758,7 @@ class _StringsRu implements Translations { @override late final _StringsNewsPartsRu newsParts = _StringsNewsPartsRu._(_root); @override String get openSearch => 'Искать игрока'; @override String get closeSearch => 'Закрыть поиск'; + @override String get searchHint => 'Ник, ID или ID в Discord (с префиксом "ds:")'; @override String get refresh => 'Обновить'; @override String get fetchAndsaveTLHistory => 'Получить историю игрока'; @override String get fetchAndSaveOldTLmatches => 'Получить старые матчи Тетра Лиги'; @@ -1341,6 +1343,7 @@ extension on Translations { case 'newsParts.unknownNews': return ({required Object type}) => 'Unknown news of type ${type}'; case 'openSearch': return 'Search player'; case 'closeSearch': return 'Close search'; + case 'searchHint': return 'Nickname, ID or Discord userID (with "ds:" prefix)'; case 'refresh': return 'Refresh'; case 'fetchAndsaveTLHistory': return 'Get player history'; case 'fetchAndSaveOldTLmatches': return 'Get Tetra League matches history'; @@ -1441,7 +1444,7 @@ extension on Translations { case 'winChance': return 'Win Chance'; case 'byGlicko': return 'By Glicko'; case 'byEstTR': return 'By Est. TR'; - case 'compareViewNoValues': return ({required Object avgR}) => 'Please, enter username, user ID, APM-PPS-VS values (divider doesn\'t matter, only order matter) or ${avgR} (where R is rank) to both of fields'; + case 'compareViewNoValues': return ({required Object avgR}) => 'Please, enter username, user ID, APM-PPS-VS values (divider doesn\'t matter, only order matter) or ${avgR} (where R is rank) to both fields'; case 'compareViewWrongValue': return ({required Object value}) => 'Falied to assign ${value}'; case 'mostRecentOne': return 'Most recent one'; case 'yes': return 'Yes'; @@ -1859,6 +1862,7 @@ extension on _StringsRu { case 'newsParts.unknownNews': return ({required Object type}) => 'Неизвестная новость типа ${type}'; case 'openSearch': return 'Искать игрока'; case 'closeSearch': return 'Закрыть поиск'; + case 'searchHint': return 'Ник, ID или ID в Discord (с префиксом "ds:")'; case 'refresh': return 'Обновить'; case 'fetchAndsaveTLHistory': return 'Получить историю игрока'; case 'fetchAndSaveOldTLmatches': return 'Получить старые матчи Тетра Лиги'; diff --git a/lib/utils/text_shadow.dart b/lib/utils/text_shadow.dart new file mode 100644 index 0000000..8022077 --- /dev/null +++ b/lib/utils/text_shadow.dart @@ -0,0 +1,6 @@ +import 'package:flutter/material.dart'; + +const List textShadow = [ // man i love this shadow + Shadow(offset: Offset(0.0, 0.0), blurRadius: 3.0, color: Colors.black), + Shadow(offset: Offset(0.0, 0.0), blurRadius: 8.0, color: Colors.black), +]; \ No newline at end of file diff --git a/lib/views/main_view.dart b/lib/views/main_view.dart index 66d0d54..c56a593 100644 --- a/lib/views/main_view.dart +++ b/lib/views/main_view.dart @@ -16,9 +16,11 @@ import 'package:tetra_stats/gen/strings.g.dart'; import 'package:tetra_stats/services/tetrio_crud.dart'; import 'package:tetra_stats/main.dart' show prefs; import 'package:tetra_stats/services/crud_exceptions.dart'; +import 'package:tetra_stats/utils/text_shadow.dart'; import 'package:tetra_stats/views/ranks_averages_view.dart' show RankAveragesView; import 'package:tetra_stats/views/tl_leaderboard_view.dart' show TLLeaderboardView; import 'package:tetra_stats/views/tl_match_view.dart' show TlMatchResultView; +import 'package:tetra_stats/widgets/search_box.dart'; import 'package:tetra_stats/widgets/stat_sell_num.dart'; import 'package:tetra_stats/widgets/tl_thingy.dart'; import 'package:tetra_stats/widgets/user_thingy.dart'; @@ -40,10 +42,6 @@ final NumberFormat secs = NumberFormat("00.###"); final NumberFormat _f2 = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 2); final NumberFormat _f4 = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 4); final DateFormat _dateFormat = DateFormat.yMMMd(LocaleSettings.currentLocale.languageCode).add_Hms(); -final List textShadow = [ // man i love this shadow - const Shadow(offset: Offset(0.0, 0.0), blurRadius: 3.0, color: Colors.black), - const Shadow(offset: Offset(0.0, 0.0), blurRadius: 8.0, color: Colors.black), -]; class MainView extends StatefulWidget { @@ -74,19 +72,6 @@ class _MainState extends State with TickerProviderStateMixin { late TabController _tabController; late bool fixedScroll; - Widget _searchTextField() { - return TextField( - maxLength: 25, - autocorrect: false, - enableSuggestions: false, - decoration: const InputDecoration(counter: Offstage()), - style: TextStyle(shadows: textShadow), - onSubmitted: (String value) { - changePlayer(value); - }, - ); - } - @override void initState() { initDB(); @@ -265,11 +250,12 @@ class _MainState extends State with TickerProviderStateMixin { @override Widget build(BuildContext context) { final t = Translations.of(context); + bool bigScreen = MediaQuery.of(context).size.width > 768; return Scaffold( drawer: widget.player == null ? NavDrawer(changePlayer) : null, // Side menu hidden if player provided drawerEdgeDragWidth: MediaQuery.of(context).size.width * 0.2, // 20% of left side of the screen used of Drawer gesture appBar: AppBar( - title: _showSearchBar ? _searchTextField() : Text(widget.title, style: TextStyle(shadows: textShadow)), + title: _showSearchBar ? SearchBox(onSubmit: changePlayer, bigScreen: bigScreen) : Text(widget.title, style: const TextStyle(shadows: textShadow)), backgroundColor: Colors.black, actions: widget.player == null ? [ // search bar and PopupMenuButton hidden if player provided TODO: Subject to change _showSearchBar @@ -346,7 +332,6 @@ class _MainState extends State with TickerProviderStateMixin { case ConnectionState.active: return const Center(child: CircularProgressIndicator(color: Colors.white)); case ConnectionState.done: - //bool bigScreen = MediaQuery.of(context).size.width > 1024; if (snapshot.hasData) { return RefreshIndicator( onRefresh: () { @@ -577,31 +562,40 @@ class _TLRecords extends StatelessWidget { @override Widget build(BuildContext context) { - bool bigScreen = MediaQuery.of(context).size.width > 768; if (data.isEmpty) return Center(child: Text(t.noRecords, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 28))); + bool bigScreen = MediaQuery.of(context).size.width > 768; return ListView.builder( physics: const AlwaysScrollableScrollPhysics(), itemCount: data.length, itemBuilder: (BuildContext context, int index) { - return ListTile( - 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) : - const TextStyle(fontSize: 28)), - title: Text("vs. ${data[index].endContext.firstWhere((element) => element.userId != userID).username}"), - subtitle: Text(_dateFormat.format(data[index].timestamp)), - trailing: Table(defaultColumnWidth: const IntrinsicColumnWidth(), - defaultVerticalAlignment: TableCellVerticalAlignment.baseline, - textBaseline: TextBaseline.alphabetic, - columnWidths: const { - 0: FixedColumnWidth(50), - 2: FixedColumnWidth(50), - }, - children: [ - TableRow(children: [Text(_f2.format(data[index].endContext.firstWhere((element) => element.userId == userID).secondary), textAlign: TextAlign.right, style: const TextStyle(height: 1.1)), const Text(" :", style: TextStyle(height: 1.1)), Text(_f2.format(data[index].endContext.firstWhere((element) => element.userId != userID).secondary), textAlign: TextAlign.right, style: const TextStyle(height: 1.1)), const Text(" APM", textAlign: TextAlign.right, style: TextStyle(height: 1.1))]), - TableRow(children: [Text(_f2.format(data[index].endContext.firstWhere((element) => element.userId == userID).tertiary), textAlign: TextAlign.right, style: const TextStyle(height: 1.1)), const Text(" :", style: TextStyle(height: 1.1)), Text(_f2.format(data[index].endContext.firstWhere((element) => element.userId != userID).tertiary), textAlign: TextAlign.right, style: const TextStyle(height: 1.1)), const Text(" PPS", textAlign: TextAlign.right, style: TextStyle(height: 1.1))]), - TableRow(children: [Text(_f2.format(data[index].endContext.firstWhere((element) => element.userId == userID).extra), textAlign: TextAlign.right, style: const TextStyle(height: 1.1)), const Text(" :", style: TextStyle(height: 1.1)), Text(_f2.format(data[index].endContext.firstWhere((element) => element.userId != userID).extra), textAlign: TextAlign.right, style: const TextStyle(height: 1.1)), const Text(" VS", textAlign: TextAlign.right, style: TextStyle(height: 1.1))]), - ],), - onTap: () => Navigator.push(context, MaterialPageRoute(builder: (context) => TlMatchResultView(record: data[index], initPlayerId: userID))), + var accentColor = data[index].endContext.firstWhere((element) => element.userId == userID).success ? Colors.green : Colors.red; + return Container( + decoration: BoxDecoration( + gradient: LinearGradient( + stops: const [0, 0.05], + colors: [accentColor, Colors.transparent] + ) + ), + child: ListTile( + // tileColor: data[index].endContext.firstWhere((element) => element.userId == userID).success ? Colors.green[900] : Colors.red[900], + 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)), + title: Text("vs. ${data[index].endContext.firstWhere((element) => element.userId != userID).username}"), + subtitle: Text(_dateFormat.format(data[index].timestamp)), + trailing: Table(defaultColumnWidth: const IntrinsicColumnWidth(), + defaultVerticalAlignment: TableCellVerticalAlignment.baseline, + textBaseline: TextBaseline.alphabetic, + columnWidths: const { + 0: FixedColumnWidth(50), + 2: FixedColumnWidth(50), + }, + children: [ + TableRow(children: [Text(_f2.format(data[index].endContext.firstWhere((element) => element.userId == userID).secondary), textAlign: TextAlign.right, style: const TextStyle(height: 1.1)), const Text(" :", style: TextStyle(height: 1.1)), Text(_f2.format(data[index].endContext.firstWhere((element) => element.userId != userID).secondary), textAlign: TextAlign.right, style: const TextStyle(height: 1.1)), const Text(" APM", textAlign: TextAlign.right, style: TextStyle(height: 1.1))]), + TableRow(children: [Text(_f2.format(data[index].endContext.firstWhere((element) => element.userId == userID).tertiary), textAlign: TextAlign.right, style: const TextStyle(height: 1.1)), const Text(" :", style: TextStyle(height: 1.1)), Text(_f2.format(data[index].endContext.firstWhere((element) => element.userId != userID).tertiary), textAlign: TextAlign.right, style: const TextStyle(height: 1.1)), const Text(" PPS", textAlign: TextAlign.right, style: TextStyle(height: 1.1))]), + TableRow(children: [Text(_f2.format(data[index].endContext.firstWhere((element) => element.userId == userID).extra), textAlign: TextAlign.right, style: const TextStyle(height: 1.1)), const Text(" :", style: TextStyle(height: 1.1)), Text(_f2.format(data[index].endContext.firstWhere((element) => element.userId != userID).extra), textAlign: TextAlign.right, style: const TextStyle(height: 1.1)), const Text(" VS", textAlign: TextAlign.right, style: TextStyle(height: 1.1))]), + ],), + onTap: () => Navigator.push(context, MaterialPageRoute(builder: (context) => TlMatchResultView(record: data[index], initPlayerId: userID))), + ), ); }); } @@ -645,7 +639,7 @@ class _HistoryChartThigy extends StatefulWidget{ final NumberFormat yFormat; /// Implements graph for the _History widget. Requires [data] which is a list of dots for the graph. [yAxisTitle] used to keep track of changes. - /// [bigScreen] tells if screen wide enough, [leftSpace] sets size, reserved for titles on the left from the graph and [yFormat] sets numer format + /// [bigScreen] tells if screen wide enough, [leftSpace] sets size, reserved for titles on the left from the graph and [yFormat] sets number format /// for left titles const _HistoryChartThigy({required this.data, required this.yAxisTitle, required this.bigScreen, required this.leftSpace, required this.yFormat}); @@ -921,6 +915,7 @@ class _RecordThingy extends StatelessWidget { @override Widget build(BuildContext context) { + if (record == null) return Center(child: Text(t.noRecord, textAlign: TextAlign.center, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 28))); return LayoutBuilder(builder: (context, constraints) { bool bigScreen = constraints.maxWidth > 768; return ListView.builder( @@ -928,7 +923,7 @@ class _RecordThingy extends StatelessWidget { itemCount: 1, itemBuilder: (BuildContext context, int index) { return Column( - children: (record != null) ? [ + children: [ // show mode title if (record!.stream.contains("40l")) Text(t.sprint, style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)) else if (record!.stream.contains("blitz")) Text(t.blitz, style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)), @@ -1080,9 +1075,6 @@ class _RecordThingy extends StatelessWidget { ), ), ] - : [ // If no record, show this - Text(t.noRecord, textAlign: TextAlign.center, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 28)) - ], ); }); }); diff --git a/lib/views/rank_averages_view.dart b/lib/views/rank_averages_view.dart index 1fd6f0b..96ffe5c 100644 --- a/lib/views/rank_averages_view.dart +++ b/lib/views/rank_averages_view.dart @@ -7,7 +7,8 @@ import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:tetra_stats/data_objects/tetrio.dart'; import 'package:tetra_stats/gen/strings.g.dart'; -import 'package:tetra_stats/views/main_view.dart' show MainView, textShadow; +import 'package:tetra_stats/views/main_view.dart' show MainView; +import 'package:tetra_stats/utils/text_shadow.dart'; import 'package:window_manager/window_manager.dart'; var _chartsShortTitlesDropdowns = [for (MapEntry e in chartsShortTitles.entries) DropdownMenuItem(value: e.key, child: Text(e.value),)]; diff --git a/lib/widgets/search_box.dart b/lib/widgets/search_box.dart new file mode 100644 index 0000000..cf4b81c --- /dev/null +++ b/lib/widgets/search_box.dart @@ -0,0 +1,89 @@ +import 'package:flutter/material.dart'; +import 'package:tetra_stats/gen/strings.g.dart'; +import 'package:tetra_stats/utils/text_shadow.dart'; + +const int length = 25; +final TextEditingController controller = TextEditingController(); + +class SearchBox extends StatefulWidget { + final Function onSubmit; + final bool bigScreen; + const SearchBox({required this.onSubmit, required this.bigScreen, super.key}); + + @override + State createState() => _SearchBoxState(); +} + +class _SearchBoxState extends State{ + late FocusNode textbotFocus; + + @override + void initState() { + textbotFocus = FocusNode(); + controller.addListener(() { + setState(() {}); + }); + super.initState(); + } + + @override + void dispose(){ + controller.clear(); + textbotFocus.dispose(); + super.dispose(); + } + + Color getColorOfCounter(){ + // if limit was hit + if ((length - controller.text.length) <= 0) return Colors.redAccent; + // if input more than 16 symbols (username length limit) + if ((length - controller.text.length) < 9) return Colors.yellowAccent; + // if we good (we not) + return Colors.grey; + } + + double getFontSizeOfCounter(){ + return (length - controller.text.length) <= 0 ? 24 : 16; + } + + @override + Widget build(BuildContext context) { + final t = Translations.of(context); + return Row( + mainAxisSize: MainAxisSize.min, + //alignment: Alignment.centerRight, + children: [ + Expanded( + child: TextField( + controller: controller, + maxLength: length, + focusNode: textbotFocus, + autofocus: true, + autocorrect: false, + enableSuggestions: false, + decoration: InputDecoration( + counter: const Offstage(), + hintText: widget.bigScreen ? t.searchHint : null, + ), + style: const TextStyle(shadows: textShadow, fontSize: 18), + onSubmitted: (String value) { + widget.onSubmit(value); + textbotFocus.unfocus(); + }, + ), + ), + AnimatedDefaultTextStyle( + style: TextStyle( + fontFamily: "Eurostile Round", + fontSize: getFontSizeOfCounter(), + color: getColorOfCounter(), + shadows: textShadow + ), + duration: Durations.short4, + curve: Curves.easeOutCirc, + child: Text("${length - controller.text.length}") + ) + ] + ); + } +} \ No newline at end of file diff --git a/lib/widgets/tl_thingy.dart b/lib/widgets/tl_thingy.dart index d5b7573..2207abe 100644 --- a/lib/widgets/tl_thingy.dart +++ b/lib/widgets/tl_thingy.dart @@ -47,6 +47,7 @@ class _TLThingyState extends State { @override Widget build(BuildContext context) { final t = Translations.of(context); + 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) { bool bigScreen = constraints.maxWidth > 768; return ListView.builder( @@ -54,364 +55,360 @@ class _TLThingyState extends State { itemCount: 1, itemBuilder: (BuildContext context, int index) { return Column( - children: (currentTl.gamesPlayed > 0) - ? [ - if (widget.showTitle) Text(t.tetraLeague, style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)), - if (oldTl != null) Text(t.comparingWith(newDate: dateFormat.format(currentTl.timestamp), oldDate: dateFormat.format(oldTl!.timestamp)), - textAlign: TextAlign.center,), - if (oldTl != null) RangeSlider(values: _currentRangeValues, max: widget.states.length.toDouble(), - labels: RangeLabels( - _currentRangeValues.start.round().toString(), - _currentRangeValues.end.round().toString(), - ), - onChanged: (RangeValues values) { - setState(() { - _currentRangeValues = values; - if (values.start.round() == 0){ - currentTl = widget.tl; - }else{ - currentTl = sortedStates[values.start.round()-1].tlSeason1; - } - if (values.end.round() == 0){ - oldTl = widget.tl; - }else{ - oldTl = sortedStates[values.end.round()-1].tlSeason1; - } - }); - }, - ), - if (currentTl.gamesPlayed >= 10) - Wrap( - direction: Axis.horizontal, - alignment: WrapAlignment.spaceAround, - crossAxisAlignment: WrapCrossAlignment.center, - clipBehavior: Clip.hardEdge, - children: [ - widget.userID == "5e32fc85ab319c2ab1beb07c" // he love her so much, you can't even imagine - ? Image.asset("res/icons/kagari.png", height: 128) // Btw why she wearing Kazamatsuri high school uniform? - : Image.asset("res/tetrio_tl_alpha_ranks/${currentTl.rank}.png", height: 128), - Column( - children: [ - Text("${f2.format(currentTl.rating)} TR", style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)), - if (oldTl != null) Text( - "${fDiff.format(currentTl.rating - oldTl!.rating)} TR", - textAlign: TextAlign.center, - style: TextStyle( - color: currentTl.rating - oldTl!.rating < 0 ? - Colors.red : - Colors.green - ), - ), - Column( - children: [ - RichText( - textAlign: TextAlign.center, - softWrap: true, - text: TextSpan( - style: DefaultTextStyle.of(context).style, - children: [ - TextSpan(text: "${t.top} ${f2.format(currentTl.percentile * 100)}% (${currentTl.percentileRank.toUpperCase()})"), - if (currentTl.bestRank != "z") const TextSpan(text: " • "), - if (currentTl.bestRank != "z") TextSpan(text: "${t.topRank}: ${currentTl.bestRank.toUpperCase()}"), - if (widget.topTR != null) TextSpan(text: " (${f2.format(widget.topTR)} TR)"), - TextSpan(text: " • Glicko: ${f2.format(currentTl.glicko!)}±"), - TextSpan(text: f2.format(currentTl.rd!), style: currentTl.decaying ? TextStyle(color: currentTl.rd! > 98 ? Colors.red : Colors.yellow) : null), - if (currentTl.decaying) WidgetSpan(child: Icon(Icons.trending_up, color: currentTl.rd! > 98 ? Colors.red : Colors.yellow,), alignment: PlaceholderAlignment.middle, baseline: TextBaseline.alphabetic) - ], - ), - ), - ], - ), - ], - ), - ], - ), - if (currentTl.gamesPlayed >= 10 && currentTl.rd! < 100 && currentTl.nextAt >=0 && currentTl.prevAt >= 0) Padding( - padding: const EdgeInsets.all(8.0), - child: SfLinearGauge( - minimum: currentTl.nextAt.toDouble(), - maximum: currentTl.prevAt.toDouble(), - interval: currentTl.prevAt.toDouble() - currentTl.nextAt.toDouble(), - ranges: [LinearGaugeRange(startValue: currentTl.standing.toDouble() <= currentTl.prevAt.toDouble() ? currentTl.standing.toDouble() : currentTl.prevAt.toDouble(), endValue: currentTl.prevAt.toDouble(), color: Colors.cyanAccent,)], - markerPointers: [LinearShapePointer(value: currentTl.standing.toDouble() <= currentTl.prevAt.toDouble() ? currentTl.standing.toDouble() : currentTl.prevAt.toDouble(), position: LinearElementPosition.inside, shapeType: LinearShapePointerType.triangle, color: Colors.white, height: 20), - LinearWidgetPointer(offset: 4, position: LinearElementPosition.outside, value: currentTl.standing.toDouble() <= currentTl.prevAt.toDouble() ? currentTl.standing.toDouble() : currentTl.prevAt.toDouble(), child: Text(NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 0).format(currentTl.standing)))], - isAxisInversed: true, - isMirrored: true, - showTicks: true, - showLabels: true - ), - ), - if (currentTl.gamesPlayed < 10) - Text(t.gamesUntilRanked(left: 10 - currentTl.gamesPlayed), - softWrap: true, + children: [ + if (widget.showTitle) Text(t.tetraLeague, style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)), + if (oldTl != null) Text(t.comparingWith(newDate: dateFormat.format(currentTl.timestamp), oldDate: dateFormat.format(oldTl!.timestamp)), + textAlign: TextAlign.center,), + if (oldTl != null) RangeSlider(values: _currentRangeValues, max: widget.states.length.toDouble(), + labels: RangeLabels( + _currentRangeValues.start.round().toString(), + _currentRangeValues.end.round().toString(), + ), + onChanged: (RangeValues values) { + setState(() { + _currentRangeValues = values; + if (values.start.round() == 0){ + currentTl = widget.tl; + }else{ + currentTl = sortedStates[values.start.round()-1].tlSeason1; + } + if (values.end.round() == 0){ + oldTl = widget.tl; + }else{ + oldTl = sortedStates[values.end.round()-1].tlSeason1; + } + }); + }, + ), + if (currentTl.gamesPlayed >= 10) + Wrap( + direction: Axis.horizontal, + alignment: WrapAlignment.spaceAround, + crossAxisAlignment: WrapCrossAlignment.center, + clipBehavior: Clip.hardEdge, + children: [ + widget.userID == "5e32fc85ab319c2ab1beb07c" // he love her so much, you can't even imagine + ? Image.asset("res/icons/kagari.png", height: 128) // Btw why she wearing Kazamatsuri high school uniform? + : Image.asset("res/tetrio_tl_alpha_ranks/${currentTl.rank}.png", height: 128), + Column( + children: [ + Text("${f2.format(currentTl.rating)} TR", style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)), + if (oldTl != null) Text( + "${fDiff.format(currentTl.rating - oldTl!.rating)} TR", textAlign: TextAlign.center, style: TextStyle( - fontFamily: "Eurostile Round Extended", - fontSize: bigScreen ? 42 : 28, - overflow: TextOverflow.visible, - )), + color: currentTl.rating - oldTl!.rating < 0 ? + Colors.red : + Colors.green + ), + ), + Column( + children: [ + RichText( + textAlign: TextAlign.center, + softWrap: true, + text: TextSpan( + style: DefaultTextStyle.of(context).style, + children: [ + TextSpan(text: "${t.top} ${f2.format(currentTl.percentile * 100)}% (${currentTl.percentileRank.toUpperCase()})"), + if (currentTl.bestRank != "z") const TextSpan(text: " • "), + if (currentTl.bestRank != "z") TextSpan(text: "${t.topRank}: ${currentTl.bestRank.toUpperCase()}"), + if (widget.topTR != null) TextSpan(text: " (${f2.format(widget.topTR)} TR)"), + TextSpan(text: " • Glicko: ${f2.format(currentTl.glicko!)}±"), + TextSpan(text: f2.format(currentTl.rd!), style: currentTl.decaying ? TextStyle(color: currentTl.rd! > 98 ? Colors.red : Colors.yellow) : null), + if (currentTl.decaying) WidgetSpan(child: Icon(Icons.trending_up, color: currentTl.rd! > 98 ? Colors.red : Colors.yellow,), alignment: PlaceholderAlignment.middle, baseline: TextBaseline.alphabetic) + ], + ), + ), + ], + ), + ], + ), + ], + ), + if (currentTl.gamesPlayed >= 10 && currentTl.rd! < 100 && currentTl.nextAt >=0 && currentTl.prevAt >= 0) Padding( + padding: const EdgeInsets.all(8.0), + child: SfLinearGauge( + minimum: currentTl.nextAt.toDouble(), + maximum: currentTl.prevAt.toDouble(), + interval: currentTl.prevAt.toDouble() - currentTl.nextAt.toDouble(), + ranges: [LinearGaugeRange(startValue: currentTl.standing.toDouble() <= currentTl.prevAt.toDouble() ? currentTl.standing.toDouble() : currentTl.prevAt.toDouble(), endValue: currentTl.prevAt.toDouble(), color: Colors.cyanAccent,)], + markerPointers: [LinearShapePointer(value: currentTl.standing.toDouble() <= currentTl.prevAt.toDouble() ? currentTl.standing.toDouble() : currentTl.prevAt.toDouble(), position: LinearElementPosition.inside, shapeType: LinearShapePointerType.triangle, color: Colors.white, height: 20), + LinearWidgetPointer(offset: 4, position: LinearElementPosition.outside, value: currentTl.standing.toDouble() <= currentTl.prevAt.toDouble() ? currentTl.standing.toDouble() : currentTl.prevAt.toDouble(), child: Text(NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 0).format(currentTl.standing)))], + isAxisInversed: true, + isMirrored: true, + showTicks: true, + showLabels: true + ), + ), + if (currentTl.gamesPlayed < 10) + Text(t.gamesUntilRanked(left: 10 - currentTl.gamesPlayed), + softWrap: true, + textAlign: TextAlign.center, + style: TextStyle( + fontFamily: "Eurostile Round Extended", + fontSize: bigScreen ? 42 : 28, + overflow: TextOverflow.visible, + )), + Padding( + padding: const EdgeInsets.fromLTRB(0, 16, 0, 48), + child: Wrap( + direction: Axis.horizontal, + alignment: WrapAlignment.center, + spacing: 25, + crossAxisAlignment: WrapCrossAlignment.start, + clipBehavior: Clip.hardEdge, + children: [ + if (currentTl.apm != null) StatCellNum(playerStat: currentTl.apm!, isScreenBig: bigScreen, fractionDigits: 2, playerStatLabel: t.statCellNum.apm, higherIsBetter: true, oldPlayerStat: oldTl?.apm), + if (currentTl.pps != null) StatCellNum(playerStat: currentTl.pps!, isScreenBig: bigScreen, fractionDigits: 2, playerStatLabel: t.statCellNum.pps, higherIsBetter: true, oldPlayerStat: oldTl?.pps), + if (currentTl.vs != null) StatCellNum(playerStat: currentTl.vs!, isScreenBig: bigScreen, fractionDigits: 2, playerStatLabel: t.statCellNum.vs, higherIsBetter: true, oldPlayerStat: oldTl?.vs), + if (currentTl.standingLocal > 0) StatCellNum(playerStat: currentTl.standingLocal, isScreenBig: bigScreen, playerStatLabel: t.statCellNum.lbpc, higherIsBetter: false, oldPlayerStat: oldTl?.standingLocal), + StatCellNum(playerStat: currentTl.gamesPlayed, isScreenBig: bigScreen, playerStatLabel: t.statCellNum.gamesPlayed, higherIsBetter: true, oldPlayerStat: oldTl?.gamesPlayed), + StatCellNum(playerStat: currentTl.gamesWon, isScreenBig: bigScreen, playerStatLabel: t.statCellNum.gamesWonTL, higherIsBetter: true, oldPlayerStat: oldTl?.gamesWon), + StatCellNum(playerStat: currentTl.winrate * 100, isScreenBig: bigScreen, fractionDigits: 2, playerStatLabel: t.statCellNum.winrate, higherIsBetter: true, oldPlayerStat: oldTl != null ? oldTl!.winrate*100 : null), + ], + ), + ), + if (currentTl.nerdStats != null) + Column( + children: [ + Text(t.nerdStats, style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)), Padding( - padding: const EdgeInsets.fromLTRB(0, 16, 0, 48), + padding: const EdgeInsets.fromLTRB(0, 40, 0, 0), child: Wrap( + direction: Axis.horizontal, + alignment: WrapAlignment.center, + spacing: 35, + crossAxisAlignment: WrapCrossAlignment.start, + clipBehavior: Clip.hardEdge, + children: [ + SizedBox( + width: 200, + height: 120, + child: SfRadialGauge( + title: GaugeTitle(text: t.statCellNum.app), + axes: [RadialAxis( + startAngle: 180, + endAngle: 360, + showLabels: false, + showTicks: false, + radiusFactor: 2.1, + centerY: 0.5, + minimum: 0, + maximum: 1, + ranges: [ + GaugeRange(startValue: 0, endValue: 0.2, color: Colors.red), + GaugeRange(startValue: 0.2, endValue: 0.4, color: Colors.yellow), + GaugeRange(startValue: 0.4, endValue: 0.6, color: Colors.green), + GaugeRange(startValue: 0.6, endValue: 0.8, color: Colors.blue), + GaugeRange(startValue: 0.8, endValue: 1, color: Colors.purple), + ], + pointers: [ + NeedlePointer( + value: currentTl.nerdStats!.app, + enableAnimation: true, + needleLength: 0.9, + needleStartWidth: 2, + needleEndWidth: 15, + knobStyle: const KnobStyle(color: Colors.transparent), + gradient: const LinearGradient(colors: [Colors.transparent, Colors.white], begin: Alignment.bottomCenter, end: Alignment.topCenter, stops: [0.5, 1]),) + ], + annotations: [GaugeAnnotation( + widget: TextButton(child: Text(f3.format(currentTl.nerdStats!.app), + style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 36, color: Colors.white)), + onPressed: (){ + showDialog( + context: context, + builder: (BuildContext context) => AlertDialog( + title: Text(t.statCellNum.app, + style: const TextStyle( + fontFamily: "Eurostile Round Extended")), + content: SingleChildScrollView( + child: ListBody(children: [ + Text(t.statCellNum.appDescription), + Text("${t.exactValue}: ${currentTl.nerdStats!.app}") + ]), + ), + actions: [ + TextButton( + child: Text(t.popupActions.ok), + onPressed: () { + Navigator.of(context).pop(); + }, + ) + ], + )); + },), verticalAlignment: GaugeAlignment.far, positionFactor: 0.05,), + if (oldTl != null && oldTl!.gamesPlayed > 0) GaugeAnnotation(widget: Text(fDiff.format(currentTl.nerdStats!.app - oldTl!.nerdStats!.app), style: TextStyle( + color: currentTl.nerdStats!.app - oldTl!.nerdStats!.app < 0 ? + Colors.red : + Colors.green + ),), positionFactor: 0.05,)], + )],), + ), + SizedBox( + width: 200, + height: 120, + child: SfRadialGauge( + title: const GaugeTitle(text: "VS / APM"), + axes: [RadialAxis( + startAngle: 180, + endAngle: 360, + showTicks: false, + showLabels: false, + radiusFactor: 2.1, + centerY: 0.5, + minimum: 1.8, + maximum: 2.4, + ranges: [ + GaugeRange(startValue: 1.8, endValue: 2.0, color: Colors.green), + GaugeRange(startValue: 2.0, endValue: 2.2, color: Colors.blue), + GaugeRange(startValue: 2.2, endValue: 2.4, color: Colors.purple), + ], + pointers: [ + NeedlePointer( + value: currentTl.nerdStats!.vsapm, + enableAnimation: true, + needleLength: 0.9, + needleStartWidth: 2, + needleEndWidth: 15, + knobStyle: const KnobStyle(color: Colors.transparent), + gradient: const LinearGradient(colors: [Colors.transparent, Colors.white], begin: Alignment.bottomCenter, end: Alignment.topCenter, stops: [0.5, 1]),) + ], + annotations: [GaugeAnnotation( + widget: TextButton(child: Text(f3.format(currentTl.nerdStats!.vsapm), + style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 36, color: Colors.white)), + onPressed: (){ + showDialog( + context: context, + builder: (BuildContext context) => AlertDialog( + title: const Text("VS / APM", + style: TextStyle( + fontFamily: "Eurostile Round Extended")), + content: SingleChildScrollView( + child: ListBody(children: [ + Text(t.statCellNum.vsapmDescription), + Text("${t.exactValue}: ${currentTl.nerdStats!.vsapm}") + ]), + ), + actions: [ + TextButton( + child: Text(t.popupActions.ok), + onPressed: () { + Navigator.of(context).pop(); + }, + ) + ], + )); + },), verticalAlignment: GaugeAlignment.far, positionFactor: 0.05), + if (oldTl != null && oldTl!.gamesPlayed > 0) GaugeAnnotation(widget: Text(fDiff.format(currentTl.nerdStats!.vsapm - oldTl!.nerdStats!.vsapm), style: TextStyle( + color: currentTl.nerdStats!.vsapm - oldTl!.nerdStats!.vsapm < 0 ? + Colors.red : + Colors.green + ),), positionFactor: 0.05,)], + )],), + ),]), + ), + Wrap( direction: Axis.horizontal, alignment: WrapAlignment.center, spacing: 25, crossAxisAlignment: WrapCrossAlignment.start, clipBehavior: Clip.hardEdge, children: [ - if (currentTl.apm != null) StatCellNum(playerStat: currentTl.apm!, isScreenBig: bigScreen, fractionDigits: 2, playerStatLabel: t.statCellNum.apm, higherIsBetter: true, oldPlayerStat: oldTl?.apm), - if (currentTl.pps != null) StatCellNum(playerStat: currentTl.pps!, isScreenBig: bigScreen, fractionDigits: 2, playerStatLabel: t.statCellNum.pps, higherIsBetter: true, oldPlayerStat: oldTl?.pps), - if (currentTl.vs != null) StatCellNum(playerStat: currentTl.vs!, isScreenBig: bigScreen, fractionDigits: 2, playerStatLabel: t.statCellNum.vs, higherIsBetter: true, oldPlayerStat: oldTl?.vs), - if (currentTl.standingLocal > 0) StatCellNum(playerStat: currentTl.standingLocal, isScreenBig: bigScreen, playerStatLabel: t.statCellNum.lbpc, higherIsBetter: false, oldPlayerStat: oldTl?.standingLocal), - StatCellNum(playerStat: currentTl.gamesPlayed, isScreenBig: bigScreen, playerStatLabel: t.statCellNum.gamesPlayed, higherIsBetter: true, oldPlayerStat: oldTl?.gamesPlayed), - StatCellNum(playerStat: currentTl.gamesWon, isScreenBig: bigScreen, playerStatLabel: t.statCellNum.gamesWonTL, higherIsBetter: true, oldPlayerStat: oldTl?.gamesWon), - StatCellNum(playerStat: currentTl.winrate * 100, isScreenBig: bigScreen, fractionDigits: 2, playerStatLabel: t.statCellNum.winrate, higherIsBetter: true, oldPlayerStat: oldTl != null ? oldTl!.winrate*100 : null), - ], - ), - ), - if (currentTl.nerdStats != null) - Column( - children: [ - Text(t.nerdStats, style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)), - Padding( - padding: const EdgeInsets.fromLTRB(0, 40, 0, 0), - child: Wrap( - direction: Axis.horizontal, - alignment: WrapAlignment.center, - spacing: 35, - crossAxisAlignment: WrapCrossAlignment.start, - clipBehavior: Clip.hardEdge, - children: [ - SizedBox( - width: 200, - height: 120, - child: SfRadialGauge( - title: GaugeTitle(text: t.statCellNum.app), - axes: [RadialAxis( - startAngle: 180, - endAngle: 360, - showLabels: false, - showTicks: false, - radiusFactor: 2.1, - centerY: 0.5, - minimum: 0, - maximum: 1, - ranges: [ - GaugeRange(startValue: 0, endValue: 0.2, color: Colors.red), - GaugeRange(startValue: 0.2, endValue: 0.4, color: Colors.yellow), - GaugeRange(startValue: 0.4, endValue: 0.6, color: Colors.green), - GaugeRange(startValue: 0.6, endValue: 0.8, color: Colors.blue), - GaugeRange(startValue: 0.8, endValue: 1, color: Colors.purple), - ], - pointers: [ - NeedlePointer( - value: currentTl.nerdStats!.app, - enableAnimation: true, - needleLength: 0.9, - needleStartWidth: 2, - needleEndWidth: 15, - knobStyle: const KnobStyle(color: Colors.transparent), - gradient: const LinearGradient(colors: [Colors.transparent, Colors.white], begin: Alignment.bottomCenter, end: Alignment.topCenter, stops: [0.5, 1]),) - ], - annotations: [GaugeAnnotation( - widget: TextButton(child: Text(f3.format(currentTl.nerdStats!.app), - style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 36, color: Colors.white)), - onPressed: (){ - showDialog( - context: context, - builder: (BuildContext context) => AlertDialog( - title: Text(t.statCellNum.app, - style: const TextStyle( - fontFamily: "Eurostile Round Extended")), - content: SingleChildScrollView( - child: ListBody(children: [ - Text(t.statCellNum.appDescription), - Text("${t.exactValue}: ${currentTl.nerdStats!.app}") - ]), - ), - actions: [ - TextButton( - child: Text(t.popupActions.ok), - onPressed: () { - Navigator.of(context).pop(); - }, - ) - ], - )); - },), verticalAlignment: GaugeAlignment.far, positionFactor: 0.05,), - if (oldTl != null && oldTl!.gamesPlayed > 0) GaugeAnnotation(widget: Text(fDiff.format(currentTl.nerdStats!.app - oldTl!.nerdStats!.app), style: TextStyle( - color: currentTl.nerdStats!.app - oldTl!.nerdStats!.app < 0 ? - Colors.red : - Colors.green - ),), positionFactor: 0.05,)], - )],), - ), - SizedBox( - width: 200, - height: 120, - child: SfRadialGauge( - title: const GaugeTitle(text: "VS / APM"), - axes: [RadialAxis( - startAngle: 180, - endAngle: 360, - showTicks: false, - showLabels: false, - radiusFactor: 2.1, - centerY: 0.5, - minimum: 1.8, - maximum: 2.4, - ranges: [ - GaugeRange(startValue: 1.8, endValue: 2.0, color: Colors.green), - GaugeRange(startValue: 2.0, endValue: 2.2, color: Colors.blue), - GaugeRange(startValue: 2.2, endValue: 2.4, color: Colors.purple), - ], - pointers: [ - NeedlePointer( - value: currentTl.nerdStats!.vsapm, - enableAnimation: true, - needleLength: 0.9, - needleStartWidth: 2, - needleEndWidth: 15, - knobStyle: const KnobStyle(color: Colors.transparent), - gradient: const LinearGradient(colors: [Colors.transparent, Colors.white], begin: Alignment.bottomCenter, end: Alignment.topCenter, stops: [0.5, 1]),) - ], - annotations: [GaugeAnnotation( - widget: TextButton(child: Text(f3.format(currentTl.nerdStats!.vsapm), - style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 36, color: Colors.white)), - onPressed: (){ - showDialog( - context: context, - builder: (BuildContext context) => AlertDialog( - title: const Text("VS / APM", - style: TextStyle( - fontFamily: "Eurostile Round Extended")), - content: SingleChildScrollView( - child: ListBody(children: [ - Text(t.statCellNum.vsapmDescription), - Text("${t.exactValue}: ${currentTl.nerdStats!.vsapm}") - ]), - ), - actions: [ - TextButton( - child: Text(t.popupActions.ok), - onPressed: () { - Navigator.of(context).pop(); - }, - ) - ], - )); - },), verticalAlignment: GaugeAlignment.far, positionFactor: 0.05), - if (oldTl != null && oldTl!.gamesPlayed > 0) GaugeAnnotation(widget: Text(fDiff.format(currentTl.nerdStats!.vsapm - oldTl!.nerdStats!.vsapm), style: TextStyle( - color: currentTl.nerdStats!.vsapm - oldTl!.nerdStats!.vsapm < 0 ? - Colors.red : - Colors.green - ),), positionFactor: 0.05,)], - )],), - ),]), - ), - Wrap( - direction: Axis.horizontal, - alignment: WrapAlignment.center, - spacing: 25, - crossAxisAlignment: WrapCrossAlignment.start, - clipBehavior: Clip.hardEdge, - children: [ - StatCellNum(playerStat: currentTl.nerdStats!.dss, isScreenBig: bigScreen, fractionDigits: 3, playerStatLabel: t.statCellNum.dss, - alertWidgets: [Text(t.statCellNum.dssDescription), - Text("${t.formula}: (VS / 100) - (APM / 60)"), - Text("${t.exactValue}: ${currentTl.nerdStats!.dss}"),], - okText: t.popupActions.ok, - higherIsBetter: true, - oldPlayerStat: oldTl?.nerdStats?.dss,), - StatCellNum(playerStat: currentTl.nerdStats!.dsp, isScreenBig: bigScreen, fractionDigits: 3, playerStatLabel: t.statCellNum.dsp, - alertWidgets: [Text(t.statCellNum.dspDescription), - Text("${t.formula}: DS/S / PPS"), - Text("${t.exactValue}: ${currentTl.nerdStats!.dsp}"),], - okText: t.popupActions.ok, - higherIsBetter: true, - oldPlayerStat: oldTl?.nerdStats?.dsp,), - StatCellNum(playerStat: currentTl.nerdStats!.appdsp, isScreenBig: bigScreen, fractionDigits: 3, playerStatLabel: t.statCellNum.appdsp, - alertWidgets: [Text(t.statCellNum.appdspDescription), - Text("${t.formula}: APP + DS/P"), - Text("${t.exactValue}: ${currentTl.nerdStats!.appdsp}"),], - okText: t.popupActions.ok, - higherIsBetter: true, - oldPlayerStat: oldTl?.nerdStats?.appdsp,), - StatCellNum(playerStat: currentTl.nerdStats!.cheese, isScreenBig: bigScreen, fractionDigits: 2, playerStatLabel: t.statCellNum.cheese, - alertWidgets: [Text(t.statCellNum.cheeseDescription), - Text("${t.formula}: (DS/P * 150) + ((VS/APM - 2) * 50) + (0.6 - APP) * 125"), - Text("${t.exactValue}: ${currentTl.nerdStats!.cheese}"),], - okText: t.popupActions.ok, - higherIsBetter: true, - oldPlayerStat: oldTl?.nerdStats?.cheese,), - StatCellNum(playerStat: currentTl.nerdStats!.gbe, isScreenBig: bigScreen, fractionDigits: 3, playerStatLabel: t.statCellNum.gbe, - alertWidgets: [Text(t.statCellNum.gbeDescription), - Text("${t.formula}: APP * DS/P * 2"), - Text("${t.exactValue}: ${currentTl.nerdStats!.gbe}"),], - okText: t.popupActions.ok, - higherIsBetter: true, - oldPlayerStat: oldTl?.nerdStats?.gbe,), - StatCellNum(playerStat: currentTl.nerdStats!.nyaapp, isScreenBig: bigScreen, fractionDigits: 3, playerStatLabel: t.statCellNum.nyaapp, - alertWidgets: [Text(t.statCellNum.nyaappDescription), - Text("${t.formula}: APP - 5 * tan(radians((Cheese Index / -30) + 1))"), - Text("${t.exactValue}: ${currentTl.nerdStats!.nyaapp}"),], - okText: t.popupActions.ok, - higherIsBetter: true, - oldPlayerStat: oldTl?.nerdStats?.nyaapp,), - StatCellNum(playerStat: currentTl.nerdStats!.area, isScreenBig: bigScreen, fractionDigits: 1, playerStatLabel: t.statCellNum.area, - alertWidgets: [Text(t.statCellNum.areaDescription), - Text("${t.formula}: APM * 1 + PPS * 45 + VS * 0.444 + APP * 185 + DS/S * 175 + DS/P * 450 + Garbage Effi * 315"), - Text("${t.exactValue}: ${currentTl.nerdStats!.area}"),], - okText: t.popupActions.ok, - higherIsBetter: true, - oldPlayerStat: oldTl?.nerdStats?.area,) - ]) - ], - ), - if (currentTl.estTr != null) - Padding( - padding: const EdgeInsets.fromLTRB(0, 16, 0, 48), - child: SizedBox( - width: bigScreen ? MediaQuery.of(context).size.width * 0.4 : MediaQuery.of(context).size.width * 0.85, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + StatCellNum(playerStat: currentTl.nerdStats!.dss, isScreenBig: bigScreen, fractionDigits: 3, playerStatLabel: t.statCellNum.dss, + alertWidgets: [Text(t.statCellNum.dssDescription), + Text("${t.formula}: (VS / 100) - (APM / 60)"), + Text("${t.exactValue}: ${currentTl.nerdStats!.dss}"),], + okText: t.popupActions.ok, + higherIsBetter: true, + oldPlayerStat: oldTl?.nerdStats?.dss,), + StatCellNum(playerStat: currentTl.nerdStats!.dsp, isScreenBig: bigScreen, fractionDigits: 3, playerStatLabel: t.statCellNum.dsp, + alertWidgets: [Text(t.statCellNum.dspDescription), + Text("${t.formula}: DS/S / PPS"), + Text("${t.exactValue}: ${currentTl.nerdStats!.dsp}"),], + okText: t.popupActions.ok, + higherIsBetter: true, + oldPlayerStat: oldTl?.nerdStats?.dsp,), + StatCellNum(playerStat: currentTl.nerdStats!.appdsp, isScreenBig: bigScreen, fractionDigits: 3, playerStatLabel: t.statCellNum.appdsp, + alertWidgets: [Text(t.statCellNum.appdspDescription), + Text("${t.formula}: APP + DS/P"), + Text("${t.exactValue}: ${currentTl.nerdStats!.appdsp}"),], + okText: t.popupActions.ok, + higherIsBetter: true, + oldPlayerStat: oldTl?.nerdStats?.appdsp,), + StatCellNum(playerStat: currentTl.nerdStats!.cheese, isScreenBig: bigScreen, fractionDigits: 2, playerStatLabel: t.statCellNum.cheese, + alertWidgets: [Text(t.statCellNum.cheeseDescription), + Text("${t.formula}: (DS/P * 150) + ((VS/APM - 2) * 50) + (0.6 - APP) * 125"), + Text("${t.exactValue}: ${currentTl.nerdStats!.cheese}"),], + okText: t.popupActions.ok, + higherIsBetter: true, + oldPlayerStat: oldTl?.nerdStats?.cheese,), + StatCellNum(playerStat: currentTl.nerdStats!.gbe, isScreenBig: bigScreen, fractionDigits: 3, playerStatLabel: t.statCellNum.gbe, + alertWidgets: [Text(t.statCellNum.gbeDescription), + Text("${t.formula}: APP * DS/P * 2"), + Text("${t.exactValue}: ${currentTl.nerdStats!.gbe}"),], + okText: t.popupActions.ok, + higherIsBetter: true, + oldPlayerStat: oldTl?.nerdStats?.gbe,), + StatCellNum(playerStat: currentTl.nerdStats!.nyaapp, isScreenBig: bigScreen, fractionDigits: 3, playerStatLabel: t.statCellNum.nyaapp, + alertWidgets: [Text(t.statCellNum.nyaappDescription), + Text("${t.formula}: APP - 5 * tan(radians((Cheese Index / -30) + 1))"), + Text("${t.exactValue}: ${currentTl.nerdStats!.nyaapp}"),], + okText: t.popupActions.ok, + higherIsBetter: true, + oldPlayerStat: oldTl?.nerdStats?.nyaapp,), + StatCellNum(playerStat: currentTl.nerdStats!.area, isScreenBig: bigScreen, fractionDigits: 1, playerStatLabel: t.statCellNum.area, + alertWidgets: [Text(t.statCellNum.areaDescription), + Text("${t.formula}: APM * 1 + PPS * 45 + VS * 0.444 + APP * 185 + DS/S * 175 + DS/P * 450 + Garbage Effi * 315"), + Text("${t.exactValue}: ${currentTl.nerdStats!.area}"),], + okText: t.popupActions.ok, + higherIsBetter: true, + oldPlayerStat: oldTl?.nerdStats?.area,) + ]) + ], + ), + if (currentTl.estTr != null) + Padding( + padding: const EdgeInsets.fromLTRB(0, 16, 0, 48), + child: SizedBox( + width: bigScreen ? MediaQuery.of(context).size.width * 0.4 : MediaQuery.of(context).size.width * 0.85, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "${bigScreen ? t.statCellNum.estOfTR : t.statCellNum.estOfTRShort}:", + style: const TextStyle(fontSize: 24), + ), + Text( + f2.format(currentTl.estTr!.esttr), + style: const TextStyle(fontSize: 24), + ), + ], + ), + if (currentTl.rating >= 0) + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - "${bigScreen ? t.statCellNum.estOfTR : t.statCellNum.estOfTRShort}:", - style: const TextStyle(fontSize: 24), - ), - Text( - f2.format(currentTl.estTr!.esttr), - style: const TextStyle(fontSize: 24), - ), - ], + Text( + "${bigScreen ? t.statCellNum.accOfEst : t.statCellNum.accOfEstShort}:", + style: const TextStyle(fontSize: 24), + ), + Text( + fDiff.format(currentTl.esttracc!), + style: const TextStyle(fontSize: 24), ), - if (currentTl.rating >= 0) - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - "${bigScreen ? t.statCellNum.accOfEst : t.statCellNum.accOfEstShort}:", - style: const TextStyle(fontSize: 24), - ), - Text( - fDiff.format(currentTl.esttracc!), - style: const TextStyle(fontSize: 24), - ), - ], - ), ], ), - ), - ), - if (currentTl.nerdStats != null) Graphs(currentTl.apm!, currentTl.pps!, currentTl.vs!, currentTl.nerdStats!, currentTl.playstyle!) - ] - : [ - 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.nerdStats != null) Graphs(currentTl.apm!, currentTl.pps!, currentTl.vs!, currentTl.nerdStats!, currentTl.playstyle!) + ] ); }, ); diff --git a/lib/widgets/user_thingy.dart b/lib/widgets/user_thingy.dart index a2fa26d..03708a4 100644 --- a/lib/widgets/user_thingy.dart +++ b/lib/widgets/user_thingy.dart @@ -5,7 +5,7 @@ import 'package:tetra_stats/data_objects/tetrio.dart'; import 'package:tetra_stats/gen/strings.g.dart'; import 'package:tetra_stats/views/compare_view.dart'; import 'package:intl/intl.dart'; -import 'package:tetra_stats/views/main_view.dart' show textShadow; +import 'package:tetra_stats/utils/text_shadow.dart'; import 'dart:developer' as developer; import 'package:tetra_stats/widgets/stat_sell_num.dart'; diff --git a/pubspec.yaml b/pubspec.yaml index 45c37ae..a2fd891 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: tetra_stats description: Track your and other player stats in TETR.IO publish_to: 'none' -version: 1.4.0+14 +version: 1.4.1+15 environment: sdk: '>=3.0.0' diff --git a/res/i18n/strings.i18n.json b/res/i18n/strings.i18n.json index d063400..4145f76 100644 --- a/res/i18n/strings.i18n.json +++ b/res/i18n/strings.i18n.json @@ -30,6 +30,7 @@ }, "openSearch": "Search player", "closeSearch": "Close search", + "searchHint": "Nickname, ID or Discord userID (with \"ds:\" prefix)", "refresh": "Refresh", "fetchAndsaveTLHistory": "Get player history", "fetchAndSaveOldTLmatches": "Get Tetra League matches history", @@ -130,7 +131,7 @@ "winChance": "Win Chance", "byGlicko": "By Glicko", "byEstTR": "By Est. TR", - "compareViewNoValues": "Please, enter username, user ID, APM-PPS-VS values (divider doesn't matter, only order matter) or $avgR (where R is rank) to both of fields", + "compareViewNoValues": "Please, enter username, user ID, APM-PPS-VS values (divider doesn't matter, only order matter) or $avgR (where R is rank) to both fields", "compareViewWrongValue": "Falied to assign ${value}", "mostRecentOne": "Most recent one", "yes": "Yes", diff --git a/res/i18n/strings_ru.i18n.json b/res/i18n/strings_ru.i18n.json index 054c6cd..f8dba3b 100644 --- a/res/i18n/strings_ru.i18n.json +++ b/res/i18n/strings_ru.i18n.json @@ -30,6 +30,7 @@ }, "openSearch": "Искать игрока", "closeSearch": "Закрыть поиск", + "searchHint": "Ник, ID или ID в Discord (с префиксом \"ds:\")", "refresh": "Обновить", "fetchAndsaveTLHistory": "Получить историю игрока", "fetchAndSaveOldTLmatches": "Получить старые матчи Тетра Лиги",