From 501832c9aa50ed61b6f195f49537f30bb02878b0 Mon Sep 17 00:00:00 2001 From: dan63047 Date: Mon, 17 Jul 2023 20:57:24 +0300 Subject: [PATCH] Now I can retrieve history thx to p1nkl0bst3r api But it's slow (because my code is shit) --- README.md | 7 ++-- lib/data_objects/tetrio.dart | 19 +++++++--- lib/gen/strings.g.dart | 16 +++++---- lib/services/tetrio_crud.dart | 65 +++++++++++++++++++++++++++++------ lib/views/compare_view.dart | 4 ++- lib/views/main_view.dart | 26 +++++++++----- lib/widgets/tl_thingy.dart | 4 +-- lib/widgets/user_thingy.dart | 18 +++++++--- pubspec.lock | 2 +- pubspec.yaml | 1 + res/i18n/strings.i18n.json | 3 +- res/i18n/strings_ru.i18n.json | 3 +- 12 files changed, 125 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index 2442ea9..7ba3b70 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@
Track your and other players stats in TETR.IO
-![Screenshot of the app](https://imgur.com/GGL0fux.png) +![Screenshot of the app](https://imgur.com/eAtFeBF.png) # Development Roadmap - ~~Ability to fetch player~~ @@ -21,10 +21,13 @@ - ~~Ability to compare player with avgRank~~ - UI Animations *lol* - ~~i18n, EN and RU locales~~ *dev build are here* -- Talk with osk about CORS and EndContext in TL matches *idk lol* +- ~~Talk with osk about CORS and EndContext in TL matches~~ *k* +- storeState becomes slow when there a lot of entries, needs to fix +- im still not rendering distinguishment - RELEASE ??? *that will be v1.0.0* --- Special thanks to kerrmunism for formulas + and to osk for TETR.IO \ No newline at end of file diff --git a/lib/data_objects/tetrio.dart b/lib/data_objects/tetrio.dart index 336c30e..fb60d76 100644 --- a/lib/data_objects/tetrio.dart +++ b/lib/data_objects/tetrio.dart @@ -127,7 +127,11 @@ class TetrioPlayer { userId = json['_id']; username = json['username']; state = stateTime; - role = json['role']; + if (json['role'] == "retrived from p1nkl0bst3r api"){ //i fucked my own db lol i remove it later + role = "p1nkl0bst3r"; + }else{ + role = json['role']; + } registrationTime = json['ts'] != null ? DateTime.parse(json['ts']) : null; if (json['badges'] != null) { json['badges'].forEach((v) { @@ -179,7 +183,7 @@ class TetrioPlayer { return data; } - bool isSameState(TetrioPlayer other) { + bool isSameState(covariant TetrioPlayer other) { if (userId != other.userId) return false; if (username != other.username) return false; if (role != other.role) return false; @@ -201,6 +205,10 @@ class TetrioPlayer { return true; } + bool checkForRetrivedHistory(covariant TetrioPlayer other) { + return tlSeason1.lessStrictCheck(other.tlSeason1); + } + @override String toString() { return "$username ($state)"; @@ -712,7 +720,8 @@ class TetraLeagueAlpha { List? records; TetraLeagueAlpha( - {required this.gamesPlayed, + {required this.timestamp, + required this.gamesPlayed, required this.gamesWon, required this.bestRank, required this.decaying, @@ -769,6 +778,8 @@ class TetraLeagueAlpha { @override bool operator ==(covariant TetraLeagueAlpha other) => gamesPlayed == other.gamesPlayed && rd == other.rd; + bool lessStrictCheck (covariant TetraLeagueAlpha other) => gamesPlayed == other.gamesPlayed && glicko == other.glicko; + double? get esttracc => (estTr != null) ? estTr!.esttr - rating : null; Map toJson() { @@ -911,7 +922,7 @@ class TetrioPlayersLeaderboard { avgRD /= filtredLeaderboard.length; avgGamesPlayed = (totalGamesPlayed / filtredLeaderboard.length).floor(); avgGamesWon = (totalGamesWon / filtredLeaderboard.length).floor(); - return [TetraLeagueAlpha(apm: avgAPM, pps: avgPPS, vs: avgVS, glicko: avgGlicko, rd: avgRD, gamesPlayed: avgGamesPlayed, gamesWon: avgGamesWon, bestRank: rank, decaying: false, rating: avgTR, rank: rank, percentileRank: rank, percentile: 0, standing: -1, standingLocal: -1, nextAt: -1, prevAt: -1), + return [TetraLeagueAlpha(timestamp: DateTime.now(), apm: avgAPM, pps: avgPPS, vs: avgVS, glicko: avgGlicko, rd: avgRD, gamesPlayed: avgGamesPlayed, gamesWon: avgGamesWon, bestRank: rank, decaying: false, rating: avgTR, rank: rank, percentileRank: rank, percentile: 0, standing: -1, standingLocal: -1, nextAt: -1, prevAt: -1), {"totalGamesPlayed": totalGamesPlayed, "totalGamesWon": totalGamesWon, "players": filtredLeaderboard.length, "lowestTR": lowestTR, "toEnterTR": leaderboard[(leaderboard.length * rankCutoffs[rank]!).floor()-1].rating}]; } diff --git a/lib/gen/strings.g.dart b/lib/gen/strings.g.dart index c125dd1..1b14006 100644 --- a/lib/gen/strings.g.dart +++ b/lib/gen/strings.g.dart @@ -1,9 +1,9 @@ /// Generated file. Do not edit. /// /// Locales: 2 -/// Strings: 848 (424 per locale) +/// Strings: 850 (425 per locale) /// -/// Built on 2023-07-15 at 16:11 UTC +/// Built on 2023-07-17 at 17:33 UTC // coverage:ignore-file // ignore_for_file: type=lint @@ -234,7 +234,7 @@ class _StringsEn implements BaseTranslations { String get winChance => 'Win Chance'; String get byGlicko => 'By Glicko'; String get byEstTR => 'By Est. TR'; - String get compareViewNoValues => 'Please, enter username, user ID, or APM-PPS-VS values (divider doesn\'t matter, only order matter) 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 of fields'; String compareViewWrongValue({required Object value}) => 'Falied to assign ${value}'; String get mostRecentOne => 'Most recent one'; String get yes => 'Yes'; @@ -249,6 +249,7 @@ class _StringsEn implements BaseTranslations { String get lbViewZeroEntrys => 'Empty list. Looks like something is wrong...'; String get lbViewOneEntry => 'There is only one player... What?'; String lbViewManyEntrys({required Object numberOfPlayers}) => 'There are ${numberOfPlayers} ranked players.'; + String get p1nkl0bst3rAlert => 'That data was retrived from third party API maintained by p1nkl0bst3r'; late final _StringsStatCellNumEn statCellNum = _StringsStatCellNumEn._(_root); Map get playerRole => { 'user': 'User', @@ -731,7 +732,7 @@ class _StringsRu implements _StringsEn { @override String get winChance => 'Шансы на победу'; @override String get byGlicko => 'По Glicko'; @override String get byEstTR => 'По расч. TR'; - @override String get compareViewNoValues => 'Пожалуйста, введите никнейм, ID или значения APM-PPS-VS (неважно, какой разделитель, важен порядок) в оба поля'; + @override String compareViewNoValues({required Object avgR}) => 'Пожалуйста, введите никнейм, ID, APM-PPS-VS (неважно, какой разделитель, важен порядок) или ${avgR} (где R это ранг), в оба поля'; @override String compareViewWrongValue({required Object value}) => 'Не удалось получить ${value}'; @override String get mostRecentOne => 'Самый последний'; @override String get yes => 'Да'; @@ -746,6 +747,7 @@ class _StringsRu implements _StringsEn { @override String get lbViewZeroEntrys => 'Рейтинговая таблица пуста. Похоже, что-то здесь не так...'; @override String get lbViewOneEntry => 'В рейтинговой таблице всего один игрок... Чего?'; @override String lbViewManyEntrys({required Object numberOfPlayers}) => 'В рейтинговой таблице находится ${numberOfPlayers} игроков.'; + @override String get p1nkl0bst3rAlert => 'Эти данные были получены из стороннего API, который поддерживается p1nkl0bst3r'; @override late final _StringsStatCellNumRu statCellNum = _StringsStatCellNumRu._(_root); @override Map get playerRole => { 'user': 'Пользователь', @@ -1207,7 +1209,7 @@ extension on _StringsEn { case 'winChance': return 'Win Chance'; case 'byGlicko': return 'By Glicko'; case 'byEstTR': return 'By Est. TR'; - case 'compareViewNoValues': return 'Please, enter username, user ID, or APM-PPS-VS values (divider doesn\'t matter, only order matter) 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 of fields'; case 'compareViewWrongValue': return ({required Object value}) => 'Falied to assign ${value}'; case 'mostRecentOne': return 'Most recent one'; case 'yes': return 'Yes'; @@ -1222,6 +1224,7 @@ extension on _StringsEn { case 'lbViewZeroEntrys': return 'Empty list. Looks like something is wrong...'; case 'lbViewOneEntry': return 'There is only one player... What?'; case 'lbViewManyEntrys': return ({required Object numberOfPlayers}) => 'There are ${numberOfPlayers} ranked players.'; + case 'p1nkl0bst3rAlert': return 'That data was retrived from third party API maintained by p1nkl0bst3r'; case 'statCellNum.xpLevel': return 'XP Level'; case 'statCellNum.xpProgress': return 'Progress to next level'; case 'statCellNum.xpFrom0To5000': return 'Progress from 0 XP to level 5000'; @@ -1639,7 +1642,7 @@ extension on _StringsRu { case 'winChance': return 'Шансы на победу'; case 'byGlicko': return 'По Glicko'; case 'byEstTR': return 'По расч. TR'; - case 'compareViewNoValues': return 'Пожалуйста, введите никнейм, ID или значения APM-PPS-VS (неважно, какой разделитель, важен порядок) в оба поля'; + case 'compareViewNoValues': return ({required Object avgR}) => 'Пожалуйста, введите никнейм, ID, APM-PPS-VS (неважно, какой разделитель, важен порядок) или ${avgR} (где R это ранг), в оба поля'; case 'compareViewWrongValue': return ({required Object value}) => 'Не удалось получить ${value}'; case 'mostRecentOne': return 'Самый последний'; case 'yes': return 'Да'; @@ -1654,6 +1657,7 @@ extension on _StringsRu { case 'lbViewZeroEntrys': return 'Рейтинговая таблица пуста. Похоже, что-то здесь не так...'; case 'lbViewOneEntry': return 'В рейтинговой таблице всего один игрок... Чего?'; case 'lbViewManyEntrys': return ({required Object numberOfPlayers}) => 'В рейтинговой таблице находится ${numberOfPlayers} игроков.'; + case 'p1nkl0bst3rAlert': return 'Эти данные были получены из стороннего API, который поддерживается p1nkl0bst3r'; case 'statCellNum.xpLevel': return 'Уровень\nопыта'; case 'statCellNum.xpProgress': return 'Прогресс до следующего уровня'; case 'statCellNum.xpFrom0To5000': return 'Прогресс от 0 XP до 5000 уровня'; diff --git a/lib/services/tetrio_crud.dart b/lib/services/tetrio_crud.dart index 0be56cf..7c34412 100644 --- a/lib/services/tetrio_crud.dart +++ b/lib/services/tetrio_crud.dart @@ -5,6 +5,7 @@ import 'package:http/http.dart' as http; import 'package:tetra_stats/services/crud_exceptions.dart'; import 'package:tetra_stats/services/sqlite_db_controller.dart'; import 'package:tetra_stats/data_objects/tetrio.dart'; +import 'package:csv/csv.dart'; const String dbName = "TetraStats.db"; const String tetrioUsersTable = "tetrioUsers"; @@ -88,8 +89,49 @@ class TetrioService extends DB { Future getNicknameByID(String id) async { if (id.length <= 16) return id; - TetrioPlayer player = await getPlayer(id).then((value) => value.last); - return player.username; + try{ + return await getPlayer(id).then((value) => value.last.username); + } catch (e){ + return await fetchPlayer(id).then((value) => value.username); + } + } + + Future> fetchAndsaveTLHistory(String id) async { + var url = Uri.https('api.p1nkl0bst3r.xyz', 'tlhist/$id'); + final response = await http.get(url); + if (response.statusCode == 200) { + List> csv = const CsvToListConverter().convert(response.body)..removeAt(0); + List history = []; + String nick = await getNicknameByID(id); + for (List entry in csv){ + TetrioPlayer state = TetrioPlayer( + userId: id, + username: nick, + role: "p1nkl0bst3r", + state: DateTime.parse(entry[9]), + badges: [], + friendCount: -1, + gamesPlayed: -1, + gamesWon: -1, + gameTime: const Duration(seconds: -1), + xp: -1, + supporterTier: 0, + verified: false, + connections: Connections(), + tlSeason1: TetraLeagueAlpha(timestamp: DateTime.parse(entry[9]), apm: entry[6], pps: entry[7], vs: entry[8], glicko: entry[4], rd: noTrRd, gamesPlayed: entry[1], gamesWon: entry[2], bestRank: "z", decaying: false, rating: entry[3], rank: entry[5], percentileRank: entry[5], percentile: rankCutoffs[entry[5]]!, standing: -1, standingLocal: -1, nextAt: -1, prevAt: -1), + sprint: [], + blitz: [] + ); + history.add(state); + // _players.addEntries({state.userId: [state]}.entries); + await storeState(state, isFromHistory: true); + } + return history; + } + else { + developer.log("fetchTLHistory: Probably, history doesn't exist", name: "services/tetrio_crud", error: response.statusCode); + throw Exception('Failed to fetch player'); + } } Future fetchTLLeaderboard() async { @@ -277,25 +319,26 @@ class TetrioService extends DB { } } - Future storeState(TetrioPlayer tetrioPlayer) async { + Future storeState(TetrioPlayer tetrioPlayer, {bool isFromHistory = false}) async { ensureDbIsOpen(); final db = getDatabaseOrThrow(); late List states; try { - states = await getPlayer(tetrioPlayer.userId); - } on TetrioPlayerNotExist { + states = _players[tetrioPlayer.userId]!; + //states = await getPlayer(tetrioPlayer.userId); + } catch (e) { await createPlayer(tetrioPlayer); states = await getPlayer(tetrioPlayer.userId); } - bool test = _players[tetrioPlayer.userId]!.last.isSameState(tetrioPlayer); - if (test == false) states.add(tetrioPlayer); + bool test = isFromHistory ? _players[tetrioPlayer.userId]!.last.checkForRetrivedHistory(tetrioPlayer) : _players[tetrioPlayer.userId]!.last.isSameState(tetrioPlayer); + if (test == false) isFromHistory ? states.insert(0, tetrioPlayer) : states.add(tetrioPlayer); final Map statesJson = {}; for (var e in states) { statesJson.addEntries({e.state.millisecondsSinceEpoch.toString(): e.toJson()}.entries); } - db.update(tetrioUsersTable, {idCol: tetrioPlayer.userId, nickCol: tetrioPlayer.username, statesCol: jsonEncode(statesJson)}, + await db.update(tetrioUsersTable, {idCol: tetrioPlayer.userId, nickCol: tetrioPlayer.username, statesCol: jsonEncode(statesJson)}, where: '$idCol = ?', whereArgs: [tetrioPlayer.userId]); - _players[tetrioPlayer.userId]!.add(tetrioPlayer); + isFromHistory ? _players[tetrioPlayer.userId]!.insert(0, tetrioPlayer) : _players[tetrioPlayer.userId]!.add(tetrioPlayer); _tetrioStreamController.add(_players); } @@ -310,7 +353,7 @@ class TetrioService extends DB { for (var e in states) { statesJson.addEntries({e.state.millisecondsSinceEpoch.toString(): e.toJson()}.entries); } - db.update(tetrioUsersTable, {idCol: tetrioPlayer.userId, nickCol: tetrioPlayer.username, statesCol: jsonEncode(statesJson)}, + 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); @@ -322,7 +365,7 @@ class TetrioService extends DB { List states = []; final results = await db.query(tetrioUsersTable, limit: 1, where: '$idCol = ?', whereArgs: [id.toLowerCase()]); if (results.isEmpty) { - throw TetrioPlayerNotExist(); + return states; } else { dynamic rawStates = results.first['jsonStates'] as String; rawStates = json.decode(rawStates); diff --git a/lib/views/compare_view.dart b/lib/views/compare_view.dart index 69440a2..918fd08 100644 --- a/lib/views/compare_view.dart +++ b/lib/views/compare_view.dart @@ -78,6 +78,7 @@ class CompareState extends State { theRedSide = [null, null, TetraLeagueAlpha( + timestamp: DateTime.now(), apm: apm, pps: pps, vs: vs, @@ -153,6 +154,7 @@ class CompareState extends State { theGreenSide = [null, null, TetraLeagueAlpha( + timestamp: DateTime.now(), apm: apm, pps: pps, vs: vs, @@ -847,7 +849,7 @@ class CompareState extends State { ) ], ) - ] : [Text(t.compareViewNoValues)], + ] : [Text(t.compareViewNoValues(avgR: "\$avdR"))], // This is so fucked up holy shit ) ), ), diff --git a/lib/views/main_view.dart b/lib/views/main_view.dart index 0703955..4b34131 100644 --- a/lib/views/main_view.dart +++ b/lib/views/main_view.dart @@ -16,7 +16,7 @@ import 'package:tetra_stats/widgets/tl_thingy.dart'; import 'package:tetra_stats/widgets/user_thingy.dart'; late Future me; -String _searchFor = "dan63047"; +String _searchFor = "6098518e3d5155e6ec429cdc"; String _titleNickname = "dan63047"; final TetrioService teto = TetrioService(); late SharedPreferences prefs; @@ -103,15 +103,16 @@ class _MainState extends State with SingleTickerProviderStateMixin { prefs = await SharedPreferences.getInstance(); } - void changePlayer(String player) { + void changePlayer(String player, {bool fetchHistory = false}) { setState(() { _searchFor = player; - me = fetch(_searchFor); + me = fetch(_searchFor, fetchHistory: fetchHistory); }); } - Future fetch(String nickOrID) async { + Future fetch(String nickOrID, {bool fetchHistory = false}) async { TetrioPlayer me = await teto.fetchPlayer(nickOrID); + _searchFor = me.userId; setState((){_titleNickname = me.username;}); var tlStream = await teto.getTLStream(me.userId); List tlMatches = []; @@ -119,11 +120,9 @@ class _MainState extends State with SingleTickerProviderStateMixin { List states = []; TetraLeagueAlpha? compareWith; var uniqueTL = {}; - if (isTracking){ - teto.storeState(me); - teto.saveTLMatchesFromStream(await teto.getTLStream(me.userId)); - states.addAll(await teto.getPlayer(me.userId)); - for (var element in states) { + states.addAll(await teto.getPlayer(me.userId)); + if(fetchHistory) await teto.fetchAndsaveTLHistory(_searchFor); + for (var element in states) { if (uniqueTL.isNotEmpty && uniqueTL.last != element.tlSeason1) uniqueTL.add(element.tlSeason1); if (uniqueTL.isEmpty) uniqueTL.add(element.tlSeason1); } @@ -151,6 +150,9 @@ class _MainState extends State with SingleTickerProviderStateMixin { DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.estTr != null) FlSpot(tl.timestamp.millisecondsSinceEpoch.toDouble(), tl.estTr!.esttr)], child: Text(t.statCellNum.estOfTR.replaceAll(RegExp(r'\n'), " "))), DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.esttracc != null) FlSpot(tl.timestamp.millisecondsSinceEpoch.toDouble(), tl.esttracc!)], child: Text(t.statCellNum.accOfEst.replaceAll(RegExp(r'\n'), " "))), ]; + if (isTracking){ + await teto.storeState(me); + await teto.saveTLMatchesFromStream(await teto.getTLStream(me.userId)); tlMatches.addAll(await teto.getTLMatchesbyPlayerID(me.userId)); for (var match in tlStream.records) { if (!tlMatches.contains(match)) tlMatches.add(match); @@ -220,6 +222,10 @@ class _MainState extends State with SingleTickerProviderStateMixin { ), PopupMenuButton( itemBuilder: (BuildContext context) => [ + PopupMenuItem( + value: "test", + child: Text("fetchAndsaveTLHistory"), + ), PopupMenuItem( value: "refresh", child: Text(t.refresh), @@ -240,6 +246,8 @@ class _MainState extends State with SingleTickerProviderStateMixin { onSelected: (value) { if (value == "refresh") {changePlayer(_searchFor); return;} + if (value == "test"){changePlayer(_searchFor, fetchHistory: true); + return;} Navigator.pushNamed(context, value); }, ), diff --git a/lib/widgets/tl_thingy.dart b/lib/widgets/tl_thingy.dart index a31acdc..2675350 100644 --- a/lib/widgets/tl_thingy.dart +++ b/lib/widgets/tl_thingy.dart @@ -54,14 +54,14 @@ class TLThingy extends StatelessWidget { ), ), Text( - "${t.top} ${f2.format(tl.percentile * 100)}% (${tl.percentileRank.toUpperCase()}) • ${t.topRank}: ${tl.bestRank.toUpperCase()} • Glicko: ${f2.format(tl.glicko!)}±${f2.format(tl.rd!)}${tl.decaying ? ' • ${t.decaying}' : ''}", + "${t.top} ${f2.format(tl.percentile * 100)}% (${tl.percentileRank.toUpperCase()})${tl.bestRank != "z" ? " • ${t.topRank}: ${tl.bestRank.toUpperCase()}" : ""} • Glicko: ${f2.format(tl.glicko!)}±${f2.format(tl.rd!)}${tl.decaying ? ' • ${t.decaying}' : ''}", textAlign: TextAlign.center, ), ], ), ], ), - if (tl.gamesPlayed >= 10 && tl.rd! < 100) Padding( + if (tl.gamesPlayed >= 10 && tl.rd! < 100 && tl.nextAt >=0 && tl.prevAt >= 0) Padding( padding: const EdgeInsets.all(8.0), child: SfLinearGauge( minimum: tl.nextAt.toDouble(), diff --git a/lib/widgets/user_thingy.dart b/lib/widgets/user_thingy.dart index 751b0d2..a930d63 100644 --- a/lib/widgets/user_thingy.dart +++ b/lib/widgets/user_thingy.dart @@ -158,8 +158,8 @@ class UserThingy extends StatelessWidget { ]), ], ), - (player.role != "banned") - ? Wrap( + if (!["banned", "p1nkl0bst3r"].contains(player.role)) + Wrap( direction: Axis.horizontal, alignment: WrapAlignment.center, spacing: 25, @@ -200,8 +200,8 @@ class UserThingy extends StatelessWidget { playerStatLabel: t.statCellNum.friends, higherIsBetter: true,), ], - ) - : Text( + ), + if (player.role == "banned") Text( t.bigRedBanned, textAlign: TextAlign.center, style: TextStyle( @@ -211,6 +211,14 @@ class UserThingy extends StatelessWidget { fontSize: bigScreen ? 60 : 45, ), ), + if (player.role == "p1nkl0bst3r") Text( + t.p1nkl0bst3rAlert, + textAlign: TextAlign.center, + style: const TextStyle( + fontFamily: "Eurostile Round", + fontSize: 16, + ) + ), if (player.badstanding != null && player.badstanding!) Text( t.bigRedBadStanding, @@ -222,7 +230,7 @@ class UserThingy extends StatelessWidget { fontSize: bigScreen ? 60 : 45, ), ), - Row( + if (player.role != "p1nkl0bst3r") Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Expanded( diff --git a/pubspec.lock b/pubspec.lock index 0dd6dd4..9512e19 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -98,7 +98,7 @@ packages: source: hosted version: "3.0.3" csv: - dependency: transitive + dependency: "direct main" description: name: csv sha256: "016b31a51a913744a0a1655c74ff13c9379e1200e246a03d96c81c5d9ed297b5" diff --git a/pubspec.yaml b/pubspec.yaml index e418d23..2e3d456 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -35,6 +35,7 @@ dependencies: file_picker: ^5.3.2 slang: ^3.20.0 slang_flutter: ^3.20.0 + csv: ^5.0.2 dev_dependencies: flutter_test: diff --git a/res/i18n/strings.i18n.json b/res/i18n/strings.i18n.json index 9d1aefa..a348612 100644 --- a/res/i18n/strings.i18n.json +++ b/res/i18n/strings.i18n.json @@ -86,7 +86,7 @@ "winChance": "Win Chance", "byGlicko": "By Glicko", "byEstTR": "By Est. TR", - "compareViewNoValues": "Please, enter username, user ID, or APM-PPS-VS values (divider doesn't matter, only order matter) 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 of fields", "compareViewWrongValue": "Falied to assign ${value}", "mostRecentOne": "Most recent one", "yes": "Yes", @@ -101,6 +101,7 @@ "lbViewZeroEntrys": "Empty list. Looks like something is wrong...", "lbViewOneEntry": "There is only one player... What?", "lbViewManyEntrys": "There are ${numberOfPlayers} ranked players.", + "p1nkl0bst3rAlert": "That data was retrived from third party API maintained by p1nkl0bst3r", "statCellNum":{ "xpLevel": "XP Level", "xpProgress": "Progress to next level", diff --git a/res/i18n/strings_ru.i18n.json b/res/i18n/strings_ru.i18n.json index 742e46f..acda267 100644 --- a/res/i18n/strings_ru.i18n.json +++ b/res/i18n/strings_ru.i18n.json @@ -86,7 +86,7 @@ "winChance": "Шансы на победу", "byGlicko": "По Glicko", "byEstTR": "По расч. TR", - "compareViewNoValues": "Пожалуйста, введите никнейм, ID или значения APM-PPS-VS (неважно, какой разделитель, важен порядок) в оба поля", + "compareViewNoValues": "Пожалуйста, введите никнейм, ID, APM-PPS-VS (неважно, какой разделитель, важен порядок) или $avgR (где R это ранг), в оба поля", "compareViewWrongValue": "Не удалось получить ${value}", "mostRecentOne": "Самый последний", "yes": "Да", @@ -101,6 +101,7 @@ "lbViewZeroEntrys": "Рейтинговая таблица пуста. Похоже, что-то здесь не так...", "lbViewOneEntry": "В рейтинговой таблице всего один игрок... Чего?", "lbViewManyEntrys": "В рейтинговой таблице находится ${numberOfPlayers} игроков.", + "p1nkl0bst3rAlert": "Эти данные были получены из стороннего API, который поддерживается p1nkl0bst3r", "statCellNum": { "xpLevel": "Уровень\nопыта", "xpProgress": "Прогресс до следующего уровня",