Now I can retrieve history thx to p1nkl0bst3r api
But it's slow (because my code is shit)
This commit is contained in:
parent
c69ba0a90f
commit
501832c9aa
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
<center>Track your and other players stats in TETR.IO</center>
|
<center>Track your and other players stats in TETR.IO</center>
|
||||||
|
|
||||||
![Screenshot of the app](https://imgur.com/GGL0fux.png)
|
![Screenshot of the app](https://imgur.com/eAtFeBF.png)
|
||||||
|
|
||||||
# Development Roadmap
|
# Development Roadmap
|
||||||
- ~~Ability to fetch player~~
|
- ~~Ability to fetch player~~
|
||||||
|
@ -21,10 +21,13 @@
|
||||||
- ~~Ability to compare player with avgRank~~
|
- ~~Ability to compare player with avgRank~~
|
||||||
- UI Animations *lol*
|
- UI Animations *lol*
|
||||||
- ~~i18n, EN and RU locales~~ *dev build are here*
|
- ~~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*
|
- RELEASE ??? *that will be v1.0.0*
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
Special thanks to kerrmunism for formulas
|
Special thanks to kerrmunism for formulas
|
||||||
|
|
||||||
and to osk for TETR.IO
|
and to osk for TETR.IO
|
|
@ -127,7 +127,11 @@ class TetrioPlayer {
|
||||||
userId = json['_id'];
|
userId = json['_id'];
|
||||||
username = json['username'];
|
username = json['username'];
|
||||||
state = stateTime;
|
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;
|
registrationTime = json['ts'] != null ? DateTime.parse(json['ts']) : null;
|
||||||
if (json['badges'] != null) {
|
if (json['badges'] != null) {
|
||||||
json['badges'].forEach((v) {
|
json['badges'].forEach((v) {
|
||||||
|
@ -179,7 +183,7 @@ class TetrioPlayer {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isSameState(TetrioPlayer other) {
|
bool isSameState(covariant TetrioPlayer other) {
|
||||||
if (userId != other.userId) return false;
|
if (userId != other.userId) return false;
|
||||||
if (username != other.username) return false;
|
if (username != other.username) return false;
|
||||||
if (role != other.role) return false;
|
if (role != other.role) return false;
|
||||||
|
@ -201,6 +205,10 @@ class TetrioPlayer {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool checkForRetrivedHistory(covariant TetrioPlayer other) {
|
||||||
|
return tlSeason1.lessStrictCheck(other.tlSeason1);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return "$username ($state)";
|
return "$username ($state)";
|
||||||
|
@ -712,7 +720,8 @@ class TetraLeagueAlpha {
|
||||||
List? records;
|
List? records;
|
||||||
|
|
||||||
TetraLeagueAlpha(
|
TetraLeagueAlpha(
|
||||||
{required this.gamesPlayed,
|
{required this.timestamp,
|
||||||
|
required this.gamesPlayed,
|
||||||
required this.gamesWon,
|
required this.gamesWon,
|
||||||
required this.bestRank,
|
required this.bestRank,
|
||||||
required this.decaying,
|
required this.decaying,
|
||||||
|
@ -769,6 +778,8 @@ class TetraLeagueAlpha {
|
||||||
@override
|
@override
|
||||||
bool operator ==(covariant TetraLeagueAlpha other) => gamesPlayed == other.gamesPlayed && rd == other.rd;
|
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;
|
double? get esttracc => (estTr != null) ? estTr!.esttr - rating : null;
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
|
@ -911,7 +922,7 @@ class TetrioPlayersLeaderboard {
|
||||||
avgRD /= filtredLeaderboard.length;
|
avgRD /= filtredLeaderboard.length;
|
||||||
avgGamesPlayed = (totalGamesPlayed / filtredLeaderboard.length).floor();
|
avgGamesPlayed = (totalGamesPlayed / filtredLeaderboard.length).floor();
|
||||||
avgGamesWon = (totalGamesWon / 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}];
|
{"totalGamesPlayed": totalGamesPlayed, "totalGamesWon": totalGamesWon, "players": filtredLeaderboard.length, "lowestTR": lowestTR, "toEnterTR": leaderboard[(leaderboard.length * rankCutoffs[rank]!).floor()-1].rating}];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
/// Generated file. Do not edit.
|
/// Generated file. Do not edit.
|
||||||
///
|
///
|
||||||
/// Locales: 2
|
/// 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
|
// coverage:ignore-file
|
||||||
// ignore_for_file: type=lint
|
// ignore_for_file: type=lint
|
||||||
|
@ -234,7 +234,7 @@ class _StringsEn implements BaseTranslations<AppLocale, _StringsEn> {
|
||||||
String get winChance => 'Win Chance';
|
String get winChance => 'Win Chance';
|
||||||
String get byGlicko => 'By Glicko';
|
String get byGlicko => 'By Glicko';
|
||||||
String get byEstTR => 'By Est. TR';
|
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 compareViewWrongValue({required Object value}) => 'Falied to assign ${value}';
|
||||||
String get mostRecentOne => 'Most recent one';
|
String get mostRecentOne => 'Most recent one';
|
||||||
String get yes => 'Yes';
|
String get yes => 'Yes';
|
||||||
|
@ -249,6 +249,7 @@ class _StringsEn implements BaseTranslations<AppLocale, _StringsEn> {
|
||||||
String get lbViewZeroEntrys => 'Empty list. Looks like something is wrong...';
|
String get lbViewZeroEntrys => 'Empty list. Looks like something is wrong...';
|
||||||
String get lbViewOneEntry => 'There is only one player... What?';
|
String get lbViewOneEntry => 'There is only one player... What?';
|
||||||
String lbViewManyEntrys({required Object numberOfPlayers}) => 'There are ${numberOfPlayers} ranked players.';
|
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);
|
late final _StringsStatCellNumEn statCellNum = _StringsStatCellNumEn._(_root);
|
||||||
Map<String, String> get playerRole => {
|
Map<String, String> get playerRole => {
|
||||||
'user': 'User',
|
'user': 'User',
|
||||||
|
@ -731,7 +732,7 @@ class _StringsRu implements _StringsEn {
|
||||||
@override String get winChance => 'Шансы на победу';
|
@override String get winChance => 'Шансы на победу';
|
||||||
@override String get byGlicko => 'По Glicko';
|
@override String get byGlicko => 'По Glicko';
|
||||||
@override String get byEstTR => 'По расч. TR';
|
@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 compareViewWrongValue({required Object value}) => 'Не удалось получить ${value}';
|
||||||
@override String get mostRecentOne => 'Самый последний';
|
@override String get mostRecentOne => 'Самый последний';
|
||||||
@override String get yes => 'Да';
|
@override String get yes => 'Да';
|
||||||
|
@ -746,6 +747,7 @@ class _StringsRu implements _StringsEn {
|
||||||
@override String get lbViewZeroEntrys => 'Рейтинговая таблица пуста. Похоже, что-то здесь не так...';
|
@override String get lbViewZeroEntrys => 'Рейтинговая таблица пуста. Похоже, что-то здесь не так...';
|
||||||
@override String get lbViewOneEntry => 'В рейтинговой таблице всего один игрок... Чего?';
|
@override String get lbViewOneEntry => 'В рейтинговой таблице всего один игрок... Чего?';
|
||||||
@override String lbViewManyEntrys({required Object numberOfPlayers}) => 'В рейтинговой таблице находится ${numberOfPlayers} игроков.';
|
@override String lbViewManyEntrys({required Object numberOfPlayers}) => 'В рейтинговой таблице находится ${numberOfPlayers} игроков.';
|
||||||
|
@override String get p1nkl0bst3rAlert => 'Эти данные были получены из стороннего API, который поддерживается p1nkl0bst3r';
|
||||||
@override late final _StringsStatCellNumRu statCellNum = _StringsStatCellNumRu._(_root);
|
@override late final _StringsStatCellNumRu statCellNum = _StringsStatCellNumRu._(_root);
|
||||||
@override Map<String, String> get playerRole => {
|
@override Map<String, String> get playerRole => {
|
||||||
'user': 'Пользователь',
|
'user': 'Пользователь',
|
||||||
|
@ -1207,7 +1209,7 @@ extension on _StringsEn {
|
||||||
case 'winChance': return 'Win Chance';
|
case 'winChance': return 'Win Chance';
|
||||||
case 'byGlicko': return 'By Glicko';
|
case 'byGlicko': return 'By Glicko';
|
||||||
case 'byEstTR': return 'By Est. TR';
|
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 'compareViewWrongValue': return ({required Object value}) => 'Falied to assign ${value}';
|
||||||
case 'mostRecentOne': return 'Most recent one';
|
case 'mostRecentOne': return 'Most recent one';
|
||||||
case 'yes': return 'Yes';
|
case 'yes': return 'Yes';
|
||||||
|
@ -1222,6 +1224,7 @@ extension on _StringsEn {
|
||||||
case 'lbViewZeroEntrys': return 'Empty list. Looks like something is wrong...';
|
case 'lbViewZeroEntrys': return 'Empty list. Looks like something is wrong...';
|
||||||
case 'lbViewOneEntry': return 'There is only one player... What?';
|
case 'lbViewOneEntry': return 'There is only one player... What?';
|
||||||
case 'lbViewManyEntrys': return ({required Object numberOfPlayers}) => 'There are ${numberOfPlayers} ranked players.';
|
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.xpLevel': return 'XP Level';
|
||||||
case 'statCellNum.xpProgress': return 'Progress to next level';
|
case 'statCellNum.xpProgress': return 'Progress to next level';
|
||||||
case 'statCellNum.xpFrom0To5000': return 'Progress from 0 XP to level 5000';
|
case 'statCellNum.xpFrom0To5000': return 'Progress from 0 XP to level 5000';
|
||||||
|
@ -1639,7 +1642,7 @@ extension on _StringsRu {
|
||||||
case 'winChance': return 'Шансы на победу';
|
case 'winChance': return 'Шансы на победу';
|
||||||
case 'byGlicko': return 'По Glicko';
|
case 'byGlicko': return 'По Glicko';
|
||||||
case 'byEstTR': return 'По расч. TR';
|
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 'compareViewWrongValue': return ({required Object value}) => 'Не удалось получить ${value}';
|
||||||
case 'mostRecentOne': return 'Самый последний';
|
case 'mostRecentOne': return 'Самый последний';
|
||||||
case 'yes': return 'Да';
|
case 'yes': return 'Да';
|
||||||
|
@ -1654,6 +1657,7 @@ extension on _StringsRu {
|
||||||
case 'lbViewZeroEntrys': return 'Рейтинговая таблица пуста. Похоже, что-то здесь не так...';
|
case 'lbViewZeroEntrys': return 'Рейтинговая таблица пуста. Похоже, что-то здесь не так...';
|
||||||
case 'lbViewOneEntry': return 'В рейтинговой таблице всего один игрок... Чего?';
|
case 'lbViewOneEntry': return 'В рейтинговой таблице всего один игрок... Чего?';
|
||||||
case 'lbViewManyEntrys': return ({required Object numberOfPlayers}) => 'В рейтинговой таблице находится ${numberOfPlayers} игроков.';
|
case 'lbViewManyEntrys': return ({required Object numberOfPlayers}) => 'В рейтинговой таблице находится ${numberOfPlayers} игроков.';
|
||||||
|
case 'p1nkl0bst3rAlert': return 'Эти данные были получены из стороннего API, который поддерживается p1nkl0bst3r';
|
||||||
case 'statCellNum.xpLevel': return 'Уровень\nопыта';
|
case 'statCellNum.xpLevel': return 'Уровень\nопыта';
|
||||||
case 'statCellNum.xpProgress': return 'Прогресс до следующего уровня';
|
case 'statCellNum.xpProgress': return 'Прогресс до следующего уровня';
|
||||||
case 'statCellNum.xpFrom0To5000': return 'Прогресс от 0 XP до 5000 уровня';
|
case 'statCellNum.xpFrom0To5000': return 'Прогресс от 0 XP до 5000 уровня';
|
||||||
|
|
|
@ -5,6 +5,7 @@ import 'package:http/http.dart' as http;
|
||||||
import 'package:tetra_stats/services/crud_exceptions.dart';
|
import 'package:tetra_stats/services/crud_exceptions.dart';
|
||||||
import 'package:tetra_stats/services/sqlite_db_controller.dart';
|
import 'package:tetra_stats/services/sqlite_db_controller.dart';
|
||||||
import 'package:tetra_stats/data_objects/tetrio.dart';
|
import 'package:tetra_stats/data_objects/tetrio.dart';
|
||||||
|
import 'package:csv/csv.dart';
|
||||||
|
|
||||||
const String dbName = "TetraStats.db";
|
const String dbName = "TetraStats.db";
|
||||||
const String tetrioUsersTable = "tetrioUsers";
|
const String tetrioUsersTable = "tetrioUsers";
|
||||||
|
@ -88,8 +89,49 @@ class TetrioService extends DB {
|
||||||
|
|
||||||
Future<String> getNicknameByID(String id) async {
|
Future<String> getNicknameByID(String id) async {
|
||||||
if (id.length <= 16) return id;
|
if (id.length <= 16) return id;
|
||||||
TetrioPlayer player = await getPlayer(id).then((value) => value.last);
|
try{
|
||||||
return player.username;
|
return await getPlayer(id).then((value) => value.last.username);
|
||||||
|
} catch (e){
|
||||||
|
return await fetchPlayer(id).then((value) => value.username);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<TetrioPlayer>> fetchAndsaveTLHistory(String id) async {
|
||||||
|
var url = Uri.https('api.p1nkl0bst3r.xyz', 'tlhist/$id');
|
||||||
|
final response = await http.get(url);
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
List<List<dynamic>> csv = const CsvToListConverter().convert(response.body)..removeAt(0);
|
||||||
|
List<TetrioPlayer> history = [];
|
||||||
|
String nick = await getNicknameByID(id);
|
||||||
|
for (List<dynamic> 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<TetrioPlayersLeaderboard> fetchTLLeaderboard() async {
|
Future<TetrioPlayersLeaderboard> fetchTLLeaderboard() async {
|
||||||
|
@ -277,25 +319,26 @@ class TetrioService extends DB {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> storeState(TetrioPlayer tetrioPlayer) async {
|
Future<void> storeState(TetrioPlayer tetrioPlayer, {bool isFromHistory = false}) async {
|
||||||
ensureDbIsOpen();
|
ensureDbIsOpen();
|
||||||
final db = getDatabaseOrThrow();
|
final db = getDatabaseOrThrow();
|
||||||
late List<TetrioPlayer> states;
|
late List<TetrioPlayer> states;
|
||||||
try {
|
try {
|
||||||
states = await getPlayer(tetrioPlayer.userId);
|
states = _players[tetrioPlayer.userId]!;
|
||||||
} on TetrioPlayerNotExist {
|
//states = await getPlayer(tetrioPlayer.userId);
|
||||||
|
} catch (e) {
|
||||||
await createPlayer(tetrioPlayer);
|
await createPlayer(tetrioPlayer);
|
||||||
states = await getPlayer(tetrioPlayer.userId);
|
states = await getPlayer(tetrioPlayer.userId);
|
||||||
}
|
}
|
||||||
bool test = _players[tetrioPlayer.userId]!.last.isSameState(tetrioPlayer);
|
bool test = isFromHistory ? _players[tetrioPlayer.userId]!.last.checkForRetrivedHistory(tetrioPlayer) : _players[tetrioPlayer.userId]!.last.isSameState(tetrioPlayer);
|
||||||
if (test == false) states.add(tetrioPlayer);
|
if (test == false) isFromHistory ? states.insert(0, tetrioPlayer) : states.add(tetrioPlayer);
|
||||||
final Map<String, dynamic> statesJson = {};
|
final Map<String, dynamic> statesJson = {};
|
||||||
for (var e in states) {
|
for (var e in states) {
|
||||||
statesJson.addEntries({e.state.millisecondsSinceEpoch.toString(): e.toJson()}.entries);
|
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]);
|
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);
|
_tetrioStreamController.add(_players);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -310,7 +353,7 @@ class TetrioService extends DB {
|
||||||
for (var e in states) {
|
for (var e in states) {
|
||||||
statesJson.addEntries({e.state.millisecondsSinceEpoch.toString(): e.toJson()}.entries);
|
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]);
|
where: '$idCol = ?', whereArgs: [tetrioPlayer.userId]);
|
||||||
_players[tetrioPlayer.userId]!.add(tetrioPlayer);
|
_players[tetrioPlayer.userId]!.add(tetrioPlayer);
|
||||||
_tetrioStreamController.add(_players);
|
_tetrioStreamController.add(_players);
|
||||||
|
@ -322,7 +365,7 @@ class TetrioService extends DB {
|
||||||
List<TetrioPlayer> states = [];
|
List<TetrioPlayer> states = [];
|
||||||
final results = await db.query(tetrioUsersTable, limit: 1, where: '$idCol = ?', whereArgs: [id.toLowerCase()]);
|
final results = await db.query(tetrioUsersTable, limit: 1, where: '$idCol = ?', whereArgs: [id.toLowerCase()]);
|
||||||
if (results.isEmpty) {
|
if (results.isEmpty) {
|
||||||
throw TetrioPlayerNotExist();
|
return states;
|
||||||
} else {
|
} else {
|
||||||
dynamic rawStates = results.first['jsonStates'] as String;
|
dynamic rawStates = results.first['jsonStates'] as String;
|
||||||
rawStates = json.decode(rawStates);
|
rawStates = json.decode(rawStates);
|
||||||
|
|
|
@ -78,6 +78,7 @@ class CompareState extends State<CompareView> {
|
||||||
theRedSide = [null,
|
theRedSide = [null,
|
||||||
null,
|
null,
|
||||||
TetraLeagueAlpha(
|
TetraLeagueAlpha(
|
||||||
|
timestamp: DateTime.now(),
|
||||||
apm: apm,
|
apm: apm,
|
||||||
pps: pps,
|
pps: pps,
|
||||||
vs: vs,
|
vs: vs,
|
||||||
|
@ -153,6 +154,7 @@ class CompareState extends State<CompareView> {
|
||||||
theGreenSide = [null,
|
theGreenSide = [null,
|
||||||
null,
|
null,
|
||||||
TetraLeagueAlpha(
|
TetraLeagueAlpha(
|
||||||
|
timestamp: DateTime.now(),
|
||||||
apm: apm,
|
apm: apm,
|
||||||
pps: pps,
|
pps: pps,
|
||||||
vs: vs,
|
vs: vs,
|
||||||
|
@ -847,7 +849,7 @@ class CompareState extends State<CompareView> {
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
] : [Text(t.compareViewNoValues)],
|
] : [Text(t.compareViewNoValues(avgR: "\$avdR"))], // This is so fucked up holy shit
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -16,7 +16,7 @@ import 'package:tetra_stats/widgets/tl_thingy.dart';
|
||||||
import 'package:tetra_stats/widgets/user_thingy.dart';
|
import 'package:tetra_stats/widgets/user_thingy.dart';
|
||||||
|
|
||||||
late Future<List> me;
|
late Future<List> me;
|
||||||
String _searchFor = "dan63047";
|
String _searchFor = "6098518e3d5155e6ec429cdc";
|
||||||
String _titleNickname = "dan63047";
|
String _titleNickname = "dan63047";
|
||||||
final TetrioService teto = TetrioService();
|
final TetrioService teto = TetrioService();
|
||||||
late SharedPreferences prefs;
|
late SharedPreferences prefs;
|
||||||
|
@ -103,15 +103,16 @@ class _MainState extends State<MainView> with SingleTickerProviderStateMixin {
|
||||||
prefs = await SharedPreferences.getInstance();
|
prefs = await SharedPreferences.getInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
void changePlayer(String player) {
|
void changePlayer(String player, {bool fetchHistory = false}) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_searchFor = player;
|
_searchFor = player;
|
||||||
me = fetch(_searchFor);
|
me = fetch(_searchFor, fetchHistory: fetchHistory);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List> fetch(String nickOrID) async {
|
Future<List> fetch(String nickOrID, {bool fetchHistory = false}) async {
|
||||||
TetrioPlayer me = await teto.fetchPlayer(nickOrID);
|
TetrioPlayer me = await teto.fetchPlayer(nickOrID);
|
||||||
|
_searchFor = me.userId;
|
||||||
setState((){_titleNickname = me.username;});
|
setState((){_titleNickname = me.username;});
|
||||||
var tlStream = await teto.getTLStream(me.userId);
|
var tlStream = await teto.getTLStream(me.userId);
|
||||||
List<TetraLeagueAlphaRecord> tlMatches = [];
|
List<TetraLeagueAlphaRecord> tlMatches = [];
|
||||||
|
@ -119,11 +120,9 @@ class _MainState extends State<MainView> with SingleTickerProviderStateMixin {
|
||||||
List<TetrioPlayer> states = [];
|
List<TetrioPlayer> states = [];
|
||||||
TetraLeagueAlpha? compareWith;
|
TetraLeagueAlpha? compareWith;
|
||||||
var uniqueTL = <dynamic>{};
|
var uniqueTL = <dynamic>{};
|
||||||
if (isTracking){
|
states.addAll(await teto.getPlayer(me.userId));
|
||||||
teto.storeState(me);
|
if(fetchHistory) await teto.fetchAndsaveTLHistory(_searchFor);
|
||||||
teto.saveTLMatchesFromStream(await teto.getTLStream(me.userId));
|
for (var element in states) {
|
||||||
states.addAll(await teto.getPlayer(me.userId));
|
|
||||||
for (var element in states) {
|
|
||||||
if (uniqueTL.isNotEmpty && uniqueTL.last != element.tlSeason1) uniqueTL.add(element.tlSeason1);
|
if (uniqueTL.isNotEmpty && uniqueTL.last != element.tlSeason1) uniqueTL.add(element.tlSeason1);
|
||||||
if (uniqueTL.isEmpty) uniqueTL.add(element.tlSeason1);
|
if (uniqueTL.isEmpty) uniqueTL.add(element.tlSeason1);
|
||||||
}
|
}
|
||||||
|
@ -151,6 +150,9 @@ class _MainState extends State<MainView> 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.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'), " "))),
|
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));
|
tlMatches.addAll(await teto.getTLMatchesbyPlayerID(me.userId));
|
||||||
for (var match in tlStream.records) {
|
for (var match in tlStream.records) {
|
||||||
if (!tlMatches.contains(match)) tlMatches.add(match);
|
if (!tlMatches.contains(match)) tlMatches.add(match);
|
||||||
|
@ -220,6 +222,10 @@ class _MainState extends State<MainView> with SingleTickerProviderStateMixin {
|
||||||
),
|
),
|
||||||
PopupMenuButton(
|
PopupMenuButton(
|
||||||
itemBuilder: (BuildContext context) => <PopupMenuEntry>[
|
itemBuilder: (BuildContext context) => <PopupMenuEntry>[
|
||||||
|
PopupMenuItem(
|
||||||
|
value: "test",
|
||||||
|
child: Text("fetchAndsaveTLHistory"),
|
||||||
|
),
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
value: "refresh",
|
value: "refresh",
|
||||||
child: Text(t.refresh),
|
child: Text(t.refresh),
|
||||||
|
@ -240,6 +246,8 @@ class _MainState extends State<MainView> with SingleTickerProviderStateMixin {
|
||||||
onSelected: (value) {
|
onSelected: (value) {
|
||||||
if (value == "refresh") {changePlayer(_searchFor);
|
if (value == "refresh") {changePlayer(_searchFor);
|
||||||
return;}
|
return;}
|
||||||
|
if (value == "test"){changePlayer(_searchFor, fetchHistory: true);
|
||||||
|
return;}
|
||||||
Navigator.pushNamed(context, value);
|
Navigator.pushNamed(context, value);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
|
@ -54,14 +54,14 @@ class TLThingy extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
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,
|
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),
|
padding: const EdgeInsets.all(8.0),
|
||||||
child: SfLinearGauge(
|
child: SfLinearGauge(
|
||||||
minimum: tl.nextAt.toDouble(),
|
minimum: tl.nextAt.toDouble(),
|
||||||
|
|
|
@ -158,8 +158,8 @@ class UserThingy extends StatelessWidget {
|
||||||
]),
|
]),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
(player.role != "banned")
|
if (!["banned", "p1nkl0bst3r"].contains(player.role))
|
||||||
? Wrap(
|
Wrap(
|
||||||
direction: Axis.horizontal,
|
direction: Axis.horizontal,
|
||||||
alignment: WrapAlignment.center,
|
alignment: WrapAlignment.center,
|
||||||
spacing: 25,
|
spacing: 25,
|
||||||
|
@ -200,8 +200,8 @@ class UserThingy extends StatelessWidget {
|
||||||
playerStatLabel: t.statCellNum.friends,
|
playerStatLabel: t.statCellNum.friends,
|
||||||
higherIsBetter: true,),
|
higherIsBetter: true,),
|
||||||
],
|
],
|
||||||
)
|
),
|
||||||
: Text(
|
if (player.role == "banned") Text(
|
||||||
t.bigRedBanned,
|
t.bigRedBanned,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
|
@ -211,6 +211,14 @@ class UserThingy extends StatelessWidget {
|
||||||
fontSize: bigScreen ? 60 : 45,
|
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!)
|
if (player.badstanding != null && player.badstanding!)
|
||||||
Text(
|
Text(
|
||||||
t.bigRedBadStanding,
|
t.bigRedBadStanding,
|
||||||
|
@ -222,7 +230,7 @@ class UserThingy extends StatelessWidget {
|
||||||
fontSize: bigScreen ? 60 : 45,
|
fontSize: bigScreen ? 60 : 45,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Row(
|
if (player.role != "p1nkl0bst3r") Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
|
|
|
@ -98,7 +98,7 @@ packages:
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.3"
|
version: "3.0.3"
|
||||||
csv:
|
csv:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: csv
|
name: csv
|
||||||
sha256: "016b31a51a913744a0a1655c74ff13c9379e1200e246a03d96c81c5d9ed297b5"
|
sha256: "016b31a51a913744a0a1655c74ff13c9379e1200e246a03d96c81c5d9ed297b5"
|
||||||
|
|
|
@ -35,6 +35,7 @@ dependencies:
|
||||||
file_picker: ^5.3.2
|
file_picker: ^5.3.2
|
||||||
slang: ^3.20.0
|
slang: ^3.20.0
|
||||||
slang_flutter: ^3.20.0
|
slang_flutter: ^3.20.0
|
||||||
|
csv: ^5.0.2
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
|
@ -86,7 +86,7 @@
|
||||||
"winChance": "Win Chance",
|
"winChance": "Win Chance",
|
||||||
"byGlicko": "By Glicko",
|
"byGlicko": "By Glicko",
|
||||||
"byEstTR": "By Est. TR",
|
"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}",
|
"compareViewWrongValue": "Falied to assign ${value}",
|
||||||
"mostRecentOne": "Most recent one",
|
"mostRecentOne": "Most recent one",
|
||||||
"yes": "Yes",
|
"yes": "Yes",
|
||||||
|
@ -101,6 +101,7 @@
|
||||||
"lbViewZeroEntrys": "Empty list. Looks like something is wrong...",
|
"lbViewZeroEntrys": "Empty list. Looks like something is wrong...",
|
||||||
"lbViewOneEntry": "There is only one player... What?",
|
"lbViewOneEntry": "There is only one player... What?",
|
||||||
"lbViewManyEntrys": "There are ${numberOfPlayers} ranked players.",
|
"lbViewManyEntrys": "There are ${numberOfPlayers} ranked players.",
|
||||||
|
"p1nkl0bst3rAlert": "That data was retrived from third party API maintained by p1nkl0bst3r",
|
||||||
"statCellNum":{
|
"statCellNum":{
|
||||||
"xpLevel": "XP Level",
|
"xpLevel": "XP Level",
|
||||||
"xpProgress": "Progress to next level",
|
"xpProgress": "Progress to next level",
|
||||||
|
|
|
@ -86,7 +86,7 @@
|
||||||
"winChance": "Шансы на победу",
|
"winChance": "Шансы на победу",
|
||||||
"byGlicko": "По Glicko",
|
"byGlicko": "По Glicko",
|
||||||
"byEstTR": "По расч. TR",
|
"byEstTR": "По расч. TR",
|
||||||
"compareViewNoValues": "Пожалуйста, введите никнейм, ID или значения APM-PPS-VS (неважно, какой разделитель, важен порядок) в оба поля",
|
"compareViewNoValues": "Пожалуйста, введите никнейм, ID, APM-PPS-VS (неважно, какой разделитель, важен порядок) или $avgR (где R это ранг), в оба поля",
|
||||||
"compareViewWrongValue": "Не удалось получить ${value}",
|
"compareViewWrongValue": "Не удалось получить ${value}",
|
||||||
"mostRecentOne": "Самый последний",
|
"mostRecentOne": "Самый последний",
|
||||||
"yes": "Да",
|
"yes": "Да",
|
||||||
|
@ -101,6 +101,7 @@
|
||||||
"lbViewZeroEntrys": "Рейтинговая таблица пуста. Похоже, что-то здесь не так...",
|
"lbViewZeroEntrys": "Рейтинговая таблица пуста. Похоже, что-то здесь не так...",
|
||||||
"lbViewOneEntry": "В рейтинговой таблице всего один игрок... Чего?",
|
"lbViewOneEntry": "В рейтинговой таблице всего один игрок... Чего?",
|
||||||
"lbViewManyEntrys": "В рейтинговой таблице находится ${numberOfPlayers} игроков.",
|
"lbViewManyEntrys": "В рейтинговой таблице находится ${numberOfPlayers} игроков.",
|
||||||
|
"p1nkl0bst3rAlert": "Эти данные были получены из стороннего API, который поддерживается p1nkl0bst3r",
|
||||||
"statCellNum": {
|
"statCellNum": {
|
||||||
"xpLevel": "Уровень\nопыта",
|
"xpLevel": "Уровень\nопыта",
|
||||||
"xpProgress": "Прогресс до следующего уровня",
|
"xpProgress": "Прогресс до следующего уровня",
|
||||||
|
|
Loading…
Reference in New Issue