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": "Прогресс до следующего уровня",