Redoing local DB

- Retrieving history is slow rn because i save one entry at the time
- No check if entry is already here
- S1 history is not avaliable for now
- Maybe i should store it like i did that during S1 but idk
This commit is contained in:
dan63047 2024-09-02 00:44:19 +03:00
parent d710674973
commit 38ec643a01
16 changed files with 196 additions and 232 deletions

View File

@ -3,10 +3,10 @@
import 'dart:math';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:tetra_stats/data_objects/tetra_stats.dart';
import 'package:tetra_stats/gen/strings.g.dart';
import 'package:vector_math/vector_math.dart';
const int currentSeason = 2;
const double noTrRd = 60.9;
const double apmWeight = 1;
const double ppsWeight = 45;
@ -228,7 +228,6 @@ class TetrioPlayer {
bool? badstanding;
String? botmaster;
Connections? connections;
TetraLeague? tlSeason1;
TetrioZen? zen;
Distinguishment? distinguishment;
DateTime? cachedUntil;
@ -254,7 +253,6 @@ class TetrioPlayer {
this.badstanding,
this.botmaster,
required this.connections,
required this.tlSeason1,
this.zen,
this.distinguishment,
this.cachedUntil
@ -281,7 +279,6 @@ class TetrioPlayer {
country = json['country'];
supporterTier = json['supporter_tier'] ?? 0;
verified = json['verified'] ?? false;
tlSeason1 = json['league'] != null ? TetraLeague.fromJson(json['league'], stateTime) : null;
avatarRevision = json['avatar_revision'];
bannerRevision = json['banner_revision'];
bio = json['bio'];
@ -307,7 +304,6 @@ class TetrioPlayer {
if (country != null) data['country'] = country;
if (supporterTier > 0) data['supporter_tier'] = supporterTier;
if (verified) data['verified'] = verified;
data['league'] = tlSeason1?.toJson();
if (distinguishment != null) data['distinguishment'] = distinguishment?.toJson();
if (avatarRevision != null) data['avatar_revision'] = avatarRevision;
if (bannerRevision != null) data['banner_revision'] = bannerRevision;
@ -337,83 +333,15 @@ class TetrioPlayer {
if (badstanding != other.badstanding) return false;
if (botmaster != other.botmaster) return false;
if (connections != other.connections) return false;
if (tlSeason1 != other.tlSeason1) return false;
if (distinguishment != other.distinguishment) return false;
return true;
}
bool checkForRetrivedHistory(covariant TetrioPlayer other) {
return tlSeason1!.lessStrictCheck(other.tlSeason1!);
}
@override
String toString() {
return "$username ($state)";
}
num? getStatByEnum(Stats stat){
switch (stat) {
case Stats.tr:
return tlSeason1?.tr;
case Stats.glicko:
return tlSeason1?.glicko;
case Stats.gxe:
return tlSeason1?.gxe;
case Stats.s1tr:
return tlSeason1?.s1tr;
case Stats.rd:
return tlSeason1?.rd;
case Stats.gp:
return tlSeason1?.gamesPlayed;
case Stats.gw:
return tlSeason1?.gamesWon;
case Stats.wr:
return tlSeason1?.winrate;
case Stats.apm:
return tlSeason1?.apm;
case Stats.pps:
return tlSeason1?.pps;
case Stats.vs:
return tlSeason1?.vs;
case Stats.app:
return tlSeason1?.nerdStats?.app;
case Stats.dss:
return tlSeason1?.nerdStats?.dss;
case Stats.dsp:
return tlSeason1?.nerdStats?.dsp;
case Stats.appdsp:
return tlSeason1?.nerdStats?.appdsp;
case Stats.vsapm:
return tlSeason1?.nerdStats?.vsapm;
case Stats.cheese:
return tlSeason1?.nerdStats?.cheese;
case Stats.gbe:
return tlSeason1?.nerdStats?.gbe;
case Stats.nyaapp:
return tlSeason1?.nerdStats?.nyaapp;
case Stats.area:
return tlSeason1?.nerdStats?.area;
case Stats.eTR:
return tlSeason1?.estTr?.esttr;
case Stats.acceTR:
return tlSeason1?.esttracc;
case Stats.acceTRabs:
return tlSeason1?.esttracc?.abs();
case Stats.opener:
return tlSeason1?.playstyle?.opener;
case Stats.plonk:
return tlSeason1?.playstyle?.plonk;
case Stats.infDS:
return tlSeason1?.playstyle?.infds;
case Stats.stride:
return tlSeason1?.playstyle?.stride;
case Stats.stridemMinusPlonk:
return tlSeason1?.playstyle != null ? tlSeason1!.playstyle!.stride - tlSeason1!.playstyle!.plonk : null;
case Stats.openerMinusInfDS:
return tlSeason1?.playstyle != null ? tlSeason1!.playstyle!.opener - tlSeason1!.playstyle!.infds : null;
}
}
@override
int get hashCode => state.hashCode;
@ -444,7 +372,7 @@ class Summaries{
if (json['zenithex']['record'] != null) zenithEx = RecordSingle.fromJson(json['zenithex']['record'], json['zenithex']['rank'], json['zenithex']['rank_local']);
if (json['zenithex']['best']['record'] != null) zenithCareerBest = RecordSingle.fromJson(json['zenithex']['best']['record'], json['zenith']['best']['rank'], -1);
achievements = [for (var achievement in json['achievements']) Achievement.fromJson(achievement)];
league = TetraLeague.fromJson(json['league'], DateTime.now());
league = TetraLeague.fromJson(json['league'], DateTime.now(), currentSeason, i);
zen = TetrioZen.fromJson(json['zen']);
}
}
@ -1373,6 +1301,7 @@ class EndContextMulti {
}
class TetraLeague {
late String id;
late DateTime timestamp;
late int gamesPlayed;
late int gamesWon;
@ -1397,10 +1326,11 @@ class TetraLeague {
NerdStats? nerdStats;
EstTr? estTr;
Playstyle? playstyle;
List? records;
late int season;
TetraLeague(
{required this.timestamp,
{required this.id,
required this.timestamp,
required this.gamesPlayed,
required this.gamesWon,
required this.bestRank,
@ -1421,7 +1351,7 @@ class TetraLeague {
this.apm,
this.pps,
this.vs,
this.records}){
required this.season}){
nerdStats = (apm != null && pps != null && vs != null) ? NerdStats(apm!, pps!, vs!) : null;
estTr = (nerdStats != null) ? EstTr(apm!, pps!, vs!, nerdStats!.app, nerdStats!.dss, nerdStats!.dsp, nerdStats!.gbe) : null;
playstyle =(nerdStats != null) ? Playstyle(apm!, pps!, nerdStats!.app, nerdStats!.vsapm, nerdStats!.dsp, nerdStats!.gbe, estTr!.srarea, estTr!.statrank) : null;
@ -1430,8 +1360,10 @@ class TetraLeague {
double get winrate => gamesWon / gamesPlayed;
double get s1tr => gxe * 250;
TetraLeague.fromJson(Map<String, dynamic> json, ts) {
TetraLeague.fromJson(Map<String, dynamic> json, ts, int s, String i) {
timestamp = ts;
season = s;
id = i;
gamesPlayed = json['gamesplayed'] ?? 0;
gamesWon = json['gameswon'] ?? 0;
tr = json['tr'] != null ? json['tr'].toDouble() : json['rating'] != null ? json['rating'].toDouble() : -1;
@ -1470,25 +1402,29 @@ class TetraLeague {
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['id'] = id;
data['timestamp'] = timestamp.millisecondsSinceEpoch;
if (gamesPlayed > 0) data['gamesplayed'] = gamesPlayed;
if (gamesWon > 0) data['gameswon'] = gamesWon;
if (tr >= 0) data['tr'] = tr;
if (glicko != null) data['glicko'] = glicko;
if (gxe != -1) data['gxe'] = gxe;
if (rd != null && rd != noTrRd) data['rd'] = rd;
if (rank != 'z') data['rank'] = rank;
if (bestRank != 'z') data['bestrank'] = bestRank;
if (apm != null) data['apm'] = apm;
if (pps != null) data['pps'] = pps;
if (vs != null) data['vs'] = vs;
if (decaying) data['decaying'] = decaying;
if (decaying) data['decaying'] = decaying ? 1 : 0;
if (standing >= 0) data['standing'] = standing;
if (!rankCutoffs.containsValue(percentile)) data['percentile'] = percentile;
data['percentile'] = percentile;
if (standingLocal >= 0) data['standing_local'] = standingLocal;
if (prevRank != null) data['prev_rank'] = prevRank;
if (prevAt >= 0) data['prev_at'] = prevAt;
if (nextRank != null) data['next_rank'] = nextRank;
if (nextAt >= 0) data['next_at'] = nextAt;
if (percentileRank != rank) data['percentile_rank'] = percentileRank;
data['percentile_rank'] = percentileRank;
data['season'] = season;
return data;
}
}
@ -2193,7 +2129,7 @@ class TetrioPlayersLeaderboard {
avgInfDS /= filtredLeaderboard.length;
avgGamesPlayed = (totalGamesPlayed / filtredLeaderboard.length).floor();
avgGamesWon = (totalGamesWon / filtredLeaderboard.length).floor();
return [TetraLeague(timestamp: DateTime.now(), apm: avgAPM, pps: avgPPS, vs: avgVS, gxe: avgGlixare, glicko: avgGlicko, rd: avgRD, gamesPlayed: avgGamesPlayed, gamesWon: avgGamesWon, bestRank: rank, decaying: false, tr: avgTR, rank: rank == "" ? "z" : rank, percentileRank: rank, percentile: rankCutoffs[rank]!, standing: -1, standingLocal: -1, nextAt: -1, prevAt: -1),
return [TetraLeague(id: "", timestamp: DateTime.now(), apm: avgAPM, pps: avgPPS, vs: avgVS, gxe: avgGlixare, glicko: avgGlicko, rd: avgRD, gamesPlayed: avgGamesPlayed, gamesWon: avgGamesWon, bestRank: rank, decaying: false, tr: avgTR, rank: rank == "" ? "z" : rank, percentileRank: rank, percentile: rankCutoffs[rank]!, standing: -1, standingLocal: -1, nextAt: -1, prevAt: -1, season: currentSeason),
{
"everyone": rank == "",
"totalGamesPlayed": totalGamesPlayed,
@ -2375,7 +2311,7 @@ class TetrioPlayersLeaderboard {
"entries": filtredLeaderboard
}];
}else{
return [TetraLeague(timestamp: DateTime.now(), apm: 0, pps: 0, vs: 0, glicko: 0, rd: noTrRd, gamesPlayed: 0, gamesWon: 0, bestRank: rank, decaying: false, tr: 0, rank: rank, percentileRank: rank, gxe: -1, percentile: rankCutoffs[rank]!, standing: -1, standingLocal: -1, nextAt: -1, prevAt: -1),
return [TetraLeague(id: "", timestamp: DateTime.now(), apm: 0, pps: 0, vs: 0, glicko: 0, rd: noTrRd, gamesPlayed: 0, gamesWon: 0, bestRank: rank, decaying: false, tr: 0, rank: rank, percentileRank: rank, gxe: -1, percentile: rankCutoffs[rank]!, standing: -1, standingLocal: -1, nextAt: -1, prevAt: -1, season: currentSeason),
{"players": filtredLeaderboard.length, "lowestTR": 0, "toEnterTR": 0, "toEnterGlicko": 0}];
}
}

View File

@ -39,14 +39,14 @@ ThemeData theme = ThemeData(
shape: WidgetStatePropertyAll(RoundedRectangleBorder(borderRadius: BorderRadius.horizontal(left: Radius.circular(12.0), right: Radius.circular(12.0)))),
elevation: WidgetStatePropertyAll(8.0)
),
chipTheme: ChipThemeData(
chipTheme: const ChipThemeData(
side: BorderSide(color: Colors.transparent),
),
segmentedButtonTheme: SegmentedButtonThemeData(
style: ButtonStyle(
side: WidgetStatePropertyAll(BorderSide(color: Colors.transparent)),
surfaceTintColor: WidgetStatePropertyAll(Colors.cyanAccent),
iconColor: WidgetStatePropertyAll(Colors.cyanAccent),
side: const WidgetStatePropertyAll(BorderSide(color: Colors.transparent)),
surfaceTintColor: const WidgetStatePropertyAll(Colors.cyanAccent),
iconColor: const WidgetStatePropertyAll(Colors.cyanAccent),
shadowColor: WidgetStatePropertyAll(Colors.cyanAccent.shade200),
)
),

View File

@ -33,6 +33,7 @@ class DB {
await db.execute(createTetrioUsersToTrack);
await db.execute(createTetrioTLRecordsTable);
await db.execute(createTetrioTLReplayStats);
await db.execute(createTetrioLeagueTable);
} on MissingPlatformDirectoryException {
throw UnableToGetDocuments();
}

View File

@ -5,6 +5,7 @@ import 'dart:convert';
import 'dart:developer' as developer;
import 'dart:io';
import 'package:path_provider/path_provider.dart';
import 'package:sqflite/sql.dart';
import 'package:tetra_stats/data_objects/tetra_stats.dart';
import 'package:tetra_stats/data_objects/tetrio_multiplayer_replay.dart';
import 'package:tetra_stats/main.dart' show packageInfo;
@ -21,6 +22,7 @@ const String tetrioUsersTable = "tetrioUsers";
const String tetrioUsersToTrackTable = "tetrioUsersToTrack";
const String tetraLeagueMatchesTable = "tetrioAlphaLeagueMathces";
const String tetrioTLReplayStatsTable = "tetrioTLReplayStats";
const String tetrioLeagueTable = "tetrioLeague";
const String idCol = "id";
const String replayID = "replayId";
const String nickCol = "nickname";
@ -67,6 +69,33 @@ const String createTetrioTLReplayStats = '''
PRIMARY KEY("id")
)
''';
const String createTetrioLeagueTable = '''
CREATE TABLE IF NOT EXISTS "tetrioLeague" (
"id" TEXT NOT NULL,
"timestamp" INTEGER NOT NULL,
"gamesplayed" INTEGER NOT NULL DEFAULT 0,
"gameswon" INTEGER NOT NULL DEFAULT 0,
"tr" REAL,
"glicko" REAL,
"rd" REAL,
"gxe" REAL,
"rank" TEXT NOT NULL DEFAULT 'z',
"bestrank" TEXT NOT NULL DEFAULT 'z',
"apm" REAL,
"pps" REAL,
"vs" REAL,
"decaying" INTEGER NOT NULL DEFAULT 0,
"standing" INTEGER NOT NULL DEFAULT -1,
"standing_local" INTEGER NOT NULL DEFAULT -1,
"percentile" REAL NOT NULL,
"prev_rank" TEXT,
"prev_at" INTEGER NOT NULL DEFAULT -1,
"next_rank" TEXT,
"next_at" INTEGER NOT NULL DEFAULT -1,
"percentile_rank" TEXT NOT NULL DEFAULT 'z',
"season" INTEGER NOT NULL DEFAULT 1
)
''';
class CacheController {
late Map<String, dynamic> _cache;
@ -546,7 +575,7 @@ class TetrioService extends DB {
/// Retrieves Tetra League history from p1nkl0bst3r api for a player with given [id]. Returns a list of states
/// (state = instance of [TetrioPlayer] at some point of time). Can throw an exception if fails to retrieve data.
Future<List<TetrioPlayer>> fetchAndsaveTLHistory(String id) async {
Future<List<TetraLeague>> fetchAndsaveTLHistory(String id) async {
Uri url;
if (kIsWeb) {
url = Uri.https('ts.dan63.by', 'oskware_bridge.php', {"endpoint": "TLHistory", "user": id});
@ -558,27 +587,14 @@ class TetrioService extends DB {
switch (response.statusCode) {
case 200:
await ensureDbIsOpen();
final db = getDatabaseOrThrow();
// that one api returns csv instead of json
List<List<dynamic>> csv = const CsvToListConverter().convert(response.body)..removeAt(0);
List<TetrioPlayer> history = [];
// doesn't return nickname, need to retrieve it separately
String nick = await getNicknameByID(id);
List<TetraLeague> history = [];
for (List<dynamic> entry in csv){ // each entry is one state
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: null,
tlSeason1: TetraLeague(
TetraLeague state = TetraLeague(
id: id,
timestamp: DateTime.parse(entry[9]),
apm: entry[6] != '' ? entry[6] : null,
pps: entry[7] != '' ? entry[7] : null,
@ -597,24 +613,12 @@ class TetrioService extends DB {
standing: -1,
standingLocal: -1,
nextAt: -1,
prevAt: -1
),
prevAt: -1,
season: 1
);
history.add(state);
await db.insert(tetrioLeagueTable, state.toJson(), conflictAlgorithm: ConflictAlgorithm.replace);
}
// trying to dump it to local DB
await ensureDbIsOpen();
final db = getDatabaseOrThrow();
List<TetrioPlayer> states = await getPlayer(id);
if (states.isEmpty) await createPlayer(history.first);
states.insertAll(0, history.reversed);
final Map<String, dynamic> statesJson = {};
for (var e in states) { // making one big json out of this list
statesJson.addEntries({(e.state.millisecondsSinceEpoch ~/ 1000).toString(): e.toJson()}.entries);
}
// and putting it to local DB
await db.update(tetrioUsersTable, {idCol: id, nickCol: nick, statesCol: jsonEncode(statesJson)}, where: '$idCol = ?', whereArgs: [id]);
return history;
case 404:
developer.log("fetchTLHistory: Probably, history doesn't exist", name: "services/tetrio_crud", error: response.statusCode);
@ -1057,7 +1061,10 @@ class TetrioService extends DB {
if (results.isNotEmpty) {
throw TetrioPlayerAlreadyExist();
}
await db.insert(tetrioUsersTable, {idCol: tetrioPlayer.userId, nickCol: tetrioPlayer.username}, conflictAlgorithm: ConflictAlgorithm.replace);
db.insert(tetrioUsersToTrackTable, {idCol: tetrioPlayer.userId});
_players[tetrioPlayer.userId] = tetrioPlayer.username;
_tetrioStreamController.add(_players);
}
/// Returns bool, which tells whether is given [id] is in [tetrioUsersToTrackTable].
@ -1081,6 +1088,7 @@ class TetrioService extends DB {
await ensureDbIsOpen();
final db = getDatabaseOrThrow();
final deletedPlayer = await db.delete(tetrioUsersToTrackTable, where: '$idCol = ?', whereArgs: [id.toLowerCase()]);
await db.delete(tetrioUsersTable, where: '$idCol = ?', whereArgs: [id.toLowerCase()]);
if (deletedPlayer != 1) {
throw CouldNotDeletePlayer();
} else {
@ -1089,72 +1097,67 @@ class TetrioService extends DB {
}
}
/// Saves state (which is [tetrioPlayer]) to the local database.
Future<void> storeState(TetrioPlayer tetrioPlayer) async {
// if tetrio player doesn't have entry in database - just calling different function
List<TetrioPlayer> states = await getPlayer(tetrioPlayer.userId);
if (states.isEmpty) {
await createPlayer(tetrioPlayer);
return;
}
// we not going to add state, that is same, as the previous
if (!states.last.isSameState(tetrioPlayer)) states.add(tetrioPlayer);
// Making map of the states
final Map<String, dynamic> statesJson = {};
for (var e in states) {
// Saving in format: {"unix_seconds": json_of_state}
statesJson.addEntries({(e.state.millisecondsSinceEpoch ~/ 1000).toString(): e.toJson()}.entries);
}
// Rewrite our database
Future<List<TetraLeague>> getStates(String userID, int season) async {
await ensureDbIsOpen();
final db = getDatabaseOrThrow();
await db.update(tetrioUsersTable, {idCol: tetrioPlayer.userId, nickCol: tetrioPlayer.username, statesCol: jsonEncode(statesJson)},
where: '$idCol = ?', whereArgs: [tetrioPlayer.userId]);
List<Map> query = await db.query(tetrioLeagueTable, where: '"id" = ? AND "season" = ?', whereArgs: [userID, season]);
List<TetraLeague> result = [];
for (var entry in query){
result.add(TetraLeague.fromJson(entry as Map<String, dynamic>, entry["timestamp"], entry["season"], entry["id"]));
}
return result;
}
/// Saves state (which is [TetraLeague]) to the local database.
Future<void> storeState(TetraLeague league) async {
await ensureDbIsOpen();
final db = getDatabaseOrThrow();
List<Map> test = await db.query(tetrioLeagueTable, where: '"id" = ? AND "gamesplayed" = ? AND "rd" = ?', whereArgs: [league.id, league.gamesPlayed, league.rd]);
if (test.isEmpty) {
await db.insert(tetrioLeagueTable, league.toJson());
}
}
/// Remove state (which is [tetrioPlayer]) from the local database
Future<void> deleteState(TetrioPlayer tetrioPlayer) async {
await ensureDbIsOpen();
final db = getDatabaseOrThrow();
List<TetrioPlayer> states = await getPlayer(tetrioPlayer.userId);
// removing state from map that contain every state of each user
states.removeWhere((element) => element.state == tetrioPlayer.state);
// Future<void> deleteState(TetrioPlayer tetrioPlayer) async {
// await ensureDbIsOpen();
// final db = getDatabaseOrThrow();
// //List<TetrioPlayer> states = await getPlayer(tetrioPlayer.userId);
// // removing state from map that contain every state of each user
// states.removeWhere((element) => element.state == tetrioPlayer.state);
// Making map of the states (without deleted one)
final Map<String, dynamic> statesJson = {};
for (var e in states) {
statesJson.addEntries({(e.state.millisecondsSinceEpoch ~/ 1000).toString(): e.toJson()}.entries);
}
// Rewriting database entry with new json
await db.update(tetrioUsersTable, {idCol: tetrioPlayer.userId, nickCol: tetrioPlayer.username, statesCol: jsonEncode(statesJson)},
where: '$idCol = ?', whereArgs: [tetrioPlayer.userId]);
_tetrioStreamController.add(_players);
}
// // Making map of the states (without deleted one)
// final Map<String, dynamic> statesJson = {};
// // for (var e in states) {
// // statesJson.addEntries({(e.state.millisecondsSinceEpoch ~/ 1000).toString(): e.toJson()}.entries);
// // }
// // Rewriting database entry with new json
// await db.update(tetrioUsersTable, {idCol: tetrioPlayer.userId, nickCol: tetrioPlayer.username, statesCol: jsonEncode(statesJson)},
// where: '$idCol = ?', whereArgs: [tetrioPlayer.userId]);
// _tetrioStreamController.add(_players);
// }
/// Returns list of all states of player with given [id] from database. Can return empty list if player
/// was not found.
Future<List<TetrioPlayer>> getPlayer(String id) async {
await ensureDbIsOpen();
final db = getDatabaseOrThrow();
List<TetrioPlayer> states = [];
final results = await db.query(tetrioUsersTable, limit: 1, where: '$idCol = ?', whereArgs: [id.toLowerCase()]);
if (results.isEmpty) {
return states; // it empty
} else {
dynamic rawStates = results.first['jsonStates'] as String;
rawStates = json.decode(rawStates);
// recreating objects of states
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.last.username}.entries);
_tetrioStreamController.add(_players);
return states;
}
}
// Future<List<TetrioPlayer>> getPlayer(String id) async {
// await ensureDbIsOpen();
// final db = getDatabaseOrThrow();
// List<TetrioPlayer> states = [];
// final results = await db.query(tetrioUsersTable, limit: 1, where: '$idCol = ?', whereArgs: [id.toLowerCase()]);
// if (results.isEmpty) {
// return states; // it empty
// } else {
// dynamic rawStates = results.first['jsonStates'] as String;
// rawStates = json.decode(rawStates);
// // recreating objects of states
// 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.last.username}.entries);
// _tetrioStreamController.add(_players);
// return states;
// }
// }
/// Retrieves general stats of [user] (nickname or id) from Tetra Channel api. Returns [TetrioPlayer] object of this user.
/// If [isItDiscordID] is true, function expects [user] to be a discord user id. Throws an exception if fails to retrieve.

View File

@ -85,6 +85,7 @@ class CompareState extends State<CompareView> {
theRedSide = [null,
null,
Summaries(user, TetraLeague(
id: "",
timestamp: DateTime.now(),
apm: apm,
pps: pps,
@ -102,7 +103,7 @@ class CompareState extends State<CompareView> {
standing: -1,
standingLocal: -1,
nextAt: -1,
prevAt: -1), TetrioZen(level: 0, score: 0))];
prevAt: -1, season: currentSeason), TetrioZen(level: 0, score: 0))];
return setState(() {});
}
var player = await teto.fetchPlayer(user);
@ -132,9 +133,11 @@ class CompareState extends State<CompareView> {
_justUpdate();
}
void changeRedSide(TetrioPlayer user) {
setState(() {theRedSide[0] = user;
theRedSide[2].league = user.tlSeason1;});
void changeRedSide(TetraLeague user) {
setState(() {
//theRedSide[0] = user;
theRedSide[2].league = user;
});
}
void fetchGreenSide(String user) async {
@ -161,6 +164,7 @@ class CompareState extends State<CompareView> {
theGreenSide = [null,
null,
Summaries(user, TetraLeague(
id: "",
timestamp: DateTime.now(),
apm: apm,
pps: pps,
@ -178,7 +182,7 @@ class CompareState extends State<CompareView> {
standing: -1,
standingLocal: -1,
nextAt: -1,
prevAt: -1), TetrioZen(level: 0, score: 0))];
prevAt: -1, season: currentSeason), TetrioZen(level: 0, score: 0))];
return setState(() {});
}
var player = await teto.fetchPlayer(user);
@ -208,9 +212,11 @@ class CompareState extends State<CompareView> {
_justUpdate();
}
void changeGreenSide(TetrioPlayer user) {
setState(() {theGreenSide[0] = user;
theGreenSide[2].league = user.tlSeason1;});
void changeGreenSide(TetraLeague user) {
setState(() {
//theGreenSide[0] = user;
theGreenSide[2].league = user;
});
}
double getWinrateByTR(double yourGlicko, double yourRD, double notyourGlicko,double notyourRD) {
@ -955,7 +961,7 @@ class CompareState extends State<CompareView> {
const Divider(),
Padding(
padding: const EdgeInsets.only(bottom: 16),
child: Text("${t.quickPlay} ${t.expert} ${t.nerdStats}", style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 28)),
child: Text("${t.quickPlay} ${t.expert} ${t.nerdStats}", style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 28)),
),
CompareThingy(
label: "APP",

View File

@ -216,15 +216,15 @@ class _MainState extends State<MainView> with TickerProviderStateMixin {
if (everyone != null && summaries.league.gamesPlayed > 9) rankAverages = everyone?.averages[summaries.league.percentileRank]?[0];
// Making list of Tetra League matches
//bool isTracking = await teto.isPlayerTracking(me.userId);
bool isTracking = await teto.isPlayerTracking(me.userId);
List<TetrioPlayer> states = [];
TetraLeague? compareWith;
Set<TetraLeague> uniqueTL = {};
List<TetraLeagueAlphaRecord> 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);
// }
if (isTracking){ // if tracked - save data to local DB
await teto.storeState(summaries.league);
//await teto.saveTLMatchesFromStream(tlStream);
}
TetraLeagueAlphaStream? oldMatches;
// building list of TL matches
if(fetchTLmatches) {
@ -271,11 +271,11 @@ class _MainState extends State<MainView> with TickerProviderStateMixin {
}
}
states.addAll(await teto.getPlayer(me.userId));
for (var element in states) { // For graphs I need only unique entries
if (element.tlSeason1 != null && uniqueTL.isNotEmpty && uniqueTL.last != element.tlSeason1) uniqueTL.add(element.tlSeason1!);
if (uniqueTL.isEmpty) uniqueTL.add(summaries.league);
}
//states.addAll(await teto.getPlayer(me.userId));
// for (var element in states) { // For graphs I need only unique entries
// if (element.tlSeason1 != null && uniqueTL.isNotEmpty && uniqueTL.last != element.tlSeason1) uniqueTL.add(element.tlSeason1!);
// if (uniqueTL.isEmpty) uniqueTL.add(summaries.league);
// }
// Also i need previous Tetra League State for comparison if avaliable
if (uniqueTL.length >= 2){
compareWith = uniqueTL.toList().elementAtOrNull(uniqueTL.length - 2);

View File

@ -6,6 +6,7 @@ import 'package:flutter_svg/flutter_svg.dart';
import 'package:intl/intl.dart';
import 'package:syncfusion_flutter_charts/charts.dart';
import 'package:syncfusion_flutter_gauges/gauges.dart';
import 'package:tetra_stats/data_objects/tetra_stats.dart';
import 'package:tetra_stats/gen/strings.g.dart';
import 'package:tetra_stats/services/crud_exceptions.dart';
import 'package:tetra_stats/utils/colors_functions.dart';
@ -286,11 +287,11 @@ class _DestinationGraphsState extends State<DestinationGraphs> {
}
}
states.addAll(await teto.getPlayer(widget.searchFor));
for (var element in states) {
if (element.tlSeason1 != null && uniqueTL.isNotEmpty && uniqueTL.last != element.tlSeason1) uniqueTL.add(element.tlSeason1!);
if (uniqueTL.isEmpty) uniqueTL.add(element.tlSeason1!);
}
//states.addAll(await teto.getPlayer(widget.searchFor));
// for (var element in states) {
// if (element.tlSeason1 != null && uniqueTL.isNotEmpty && uniqueTL.last != element.tlSeason1) uniqueTL.add(element.tlSeason1!);
// if (uniqueTL.isEmpty) uniqueTL.add(element.tlSeason1!);
// }
if (uniqueTL.length >= 2){
chartsData = <DropdownMenuItem<List<_HistoryChartSpot>>>[ // Dumping charts data into dropdown menu items, while cheking if every entry is valid
@ -511,9 +512,10 @@ class FetchResults{
bool success;
TetrioPlayer? player;
Summaries? summaries;
Cutoffs? cutoffs;
Exception? exception;
FetchResults(this.success, this.player, this.summaries, this.exception);
FetchResults(this.success, this.player, this.summaries, this.cutoffs, this.exception);
}
class RecordSummary extends StatelessWidget{
@ -613,10 +615,17 @@ class _DestinationHomeState extends State<DestinationHome> {
player = await teto.fetchPlayer(widget.searchFor); // Otherwise it's probably a user id or username
}
}on TetrioPlayerNotExist{
return FetchResults(false, null, null, TetrioPlayerNotExist());
return FetchResults(false, null, null, null, TetrioPlayerNotExist());
}
Summaries summaries = await teto.fetchSummaries(player.userId);
return FetchResults(true, player, summaries, null);
late Summaries summaries;
late Cutoffs cutoffs;
List<dynamic> requests = await Future.wait([
teto.fetchSummaries(player.userId),
teto.fetchCutoffsBeanserver(),
]);
summaries = requests[0];
cutoffs = requests[1];
return FetchResults(true, player, summaries, cutoffs, null);
}
Widget getOverviewCard(Summaries summaries){
@ -645,7 +654,7 @@ class _DestinationHomeState extends State<DestinationHome> {
children: [
const Text("Tetra League", style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 28, height: 0.9)),
const Divider(color: Color.fromARGB(50, 158, 158, 158)),
TLRatingThingy(userID: "", tlData: summaries.league),
TLRatingThingy(userID: "", tlData: summaries.league, showPositions: true),
const Divider(color: Color.fromARGB(50, 158, 158, 158)),
Text("${summaries.league.apm != null ? f2.format(summaries.league.apm) : "-.--"} APM • ${summaries.league.pps != null ? f2.format(summaries.league.pps) : "-.--"} PPS • ${summaries.league.vs != null ? f2.format(summaries.league.vs) : "-.--"} VS • ${summaries.league.nerdStats != null ? f2.format(summaries.league.nerdStats!.app) : "-.--"} APP • ${summaries.league.nerdStats != null ? f2.format(summaries.league.nerdStats!.vsapm) : "-.--"} VS/APM", style: const TextStyle(color: Colors.grey))
],
@ -827,7 +836,7 @@ class _DestinationHomeState extends State<DestinationHome> {
);
}
Widget getTetraLeagueCard(TetraLeague data){
Widget getTetraLeagueCard(TetraLeague data, Cutoffs? cutoffs){
return Column(
children: [
Card(
@ -845,7 +854,7 @@ class _DestinationHomeState extends State<DestinationHome> {
),
),
),
TetraLeagueThingy(league: data),
TetraLeagueThingy(league: data, cutoffs: cutoffs),
if (data.nerdStats != null) Card(
child: Row(
mainAxisSize: MainAxisSize.min,
@ -1514,7 +1523,7 @@ class _DestinationHomeState extends State<DestinationHome> {
child: switch (rightCard){
Cards.overview => getOverviewCard(snapshot.data!.summaries!),
Cards.tetraLeague => switch (cardMod){
CardMod.info => getTetraLeagueCard(snapshot.data!.summaries!.league),
CardMod.info => getTetraLeagueCard(snapshot.data!.summaries!.league, snapshot.data!.cutoffs),
CardMod.records => getRecentTLrecords(widget.constraints),
_ => const Center(child: Text("huh?"))
},
@ -2340,8 +2349,9 @@ class _SearchDrawerState extends State<SearchDrawer> {
class TetraLeagueThingy extends StatelessWidget{
final TetraLeague league;
final Cutoffs? cutoffs;
const TetraLeagueThingy({super.key, required this.league});
const TetraLeagueThingy({super.key, required this.league, this.cutoffs});
@override
Widget build(BuildContext context) {
@ -2349,7 +2359,15 @@ class TetraLeagueThingy extends StatelessWidget{
child: Column(
children: [
TLRatingThingy(userID: "w", tlData: league),
TLProgress(tlData: league,),
TLProgress(
tlData: league,
previousRankTRcutoff: cutoffs != null ? cutoffs!.tr[league.rank != "z" ? league.rank : league.percentileRank] : null,
nextRankTRcutoff: cutoffs != null ? (league.rank != "z" ? league.rank == "x+" : league.percentileRank == "x+") ? 25000 : cutoffs!.tr[ranks.elementAtOrNull(ranks.indexOf(league.rank != "z" ? league.rank : league.percentileRank)+1)] : null,
nextRankTRcutoffTarget: league.rank != "z" ? rankTargets[league.rank] : null,
previousRankTRcutoffTarget: (league.rank != "z" && league.rank != "x+") ? rankTargets[ranks.elementAtOrNull(ranks.indexOf(league.rank)+1)] : null,
previousGlickoCutoff: cutoffs != null ? cutoffs!.glicko[league.rank != "z" ? league.rank : league.percentileRank] : null,
nextRankGlickoCutoff: cutoffs != null ? (league.rank != "z" ? league.rank == "x+" : league.percentileRank == "x+") ? 25000 : cutoffs!.glicko[ranks.elementAtOrNull(ranks.indexOf(league.rank != "z" ? league.rank : league.percentileRank)+1)] : null,
),
Row(
// spacing: 25.0,
// alignment: WrapAlignment.spaceAround,
@ -2809,9 +2827,10 @@ class TLRatingThingy extends StatelessWidget{
final TetraLeague tlData;
final TetraLeague? oldTl;
final double? topTR;
final bool? showPositions;
final DateTime? lastMatchPlayed;
const TLRatingThingy({super.key, required this.userID, required this.tlData, this.oldTl, this.topTR, this.lastMatchPlayed});
const TLRatingThingy({super.key, required this.userID, required this.tlData, this.oldTl, this.topTR, this.lastMatchPlayed, this.showPositions});
@override
Widget build(BuildContext context) {
@ -2893,7 +2912,7 @@ class TLRatingThingy extends StatelessWidget{
),
],
),
RichText(
if (showPositions == true) RichText(
textAlign: TextAlign.start,
text: TextSpan(
text: "",

View File

@ -523,7 +523,7 @@ class _ListEntry extends StatelessWidget {
children: [
Text(f.format(value),
style: const TextStyle(fontSize: 22, height: 0.9)),
if (id.isNotEmpty) Text(t.forPlayer(username: username), style: TextStyle(color: Colors.grey, fontWeight: FontWeight.w100),)
if (id.isNotEmpty) Text(t.forPlayer(username: username), style: const TextStyle(color: Colors.grey, fontWeight: FontWeight.w100),)
],
),
onTap: id.isNotEmpty

View File

@ -4,7 +4,7 @@ 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/widgets/tl_thingy.dart';
//import 'package:tetra_stats/widgets/tl_thingy.dart';
import 'package:tetra_stats/widgets/user_thingy.dart';
import 'package:window_manager/window_manager.dart';
@ -58,6 +58,6 @@ class StateState extends State<StateView> {
headerSliverBuilder: (context, value) {
return [SliverToBoxAdapter(child: UserThingy(player: widget.state, showStateTimestamp: true, setState: _justUpdate))];
},
body: TLThingy(tl: widget.state.tlSeason1!, userID: widget.state.userId, states: const []))));
body: Container())));
}
}

View File

@ -61,14 +61,14 @@ class StatesState extends State<StatesView> {
itemBuilder: (context, index) {
return ListTile(
title: Text(timestamp(widget.states[index].state)),
subtitle: Text(t.statesViewEntry(level: widget.states[index].level.toStringAsFixed(2), gameTime: widget.states[index].gameTime, friends: widget.states[index].friendCount, rd: NumberFormat.compact().format(widget.states[index].tlSeason1?.rd??0))),
subtitle: Text(t.statesViewEntry(level: widget.states[index].level.toStringAsFixed(2), gameTime: widget.states[index].gameTime, friends: widget.states[index].friendCount, rd: 0)),
trailing: IconButton(
icon: const Icon(Icons.delete_forever),
onPressed: () {
DateTime nn = widget.states[index].state;
teto.deleteState(widget.states[index]).then((value) => setState(() {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.stateRemoved(date: timestamp(nn)))));
}));
// teto.deleteState(widget.states[index]).then((value) => setState(() {
// ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.stateRemoved(date: timestamp(nn)))));
// }));
},
),
onTap: () {

View File

@ -210,7 +210,7 @@ class TLLeaderboardState extends State<TLLeaderboardView> {
)
);
}
return Text("end of FutureBuilder");
return const Text("end of FutureBuilder");
}
})),
);

View File

@ -51,7 +51,7 @@ class TLProgress extends StatelessWidget{
]
)
),
Spacer(),
const Spacer(),
RichText(
textAlign: TextAlign.right,
text: TextSpan(

View File

@ -59,7 +59,7 @@ class TLRatingThingy extends StatelessWidget{
if (formatedTR.elementAtOrNull(1) != null) TextSpan(text: decimalSeparator + formatedTR[1]),
TextSpan(text: " TR", style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28))
],
} : [TextSpan(text: "---\n", style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28, color: Colors.grey)), TextSpan(text: t.gamesUntilRanked(left: 10-tlData.gamesPlayed), style: TextStyle(color: Colors.grey, fontSize: 14)),]
} : [TextSpan(text: "---\n", style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28, color: Colors.grey)), TextSpan(text: t.gamesUntilRanked(left: 10-tlData.gamesPlayed), style: const TextStyle(color: Colors.grey, fontSize: 14)),]
)
),
if (oldTl != null) Text(

View File

@ -22,7 +22,7 @@ var intFDiff = NumberFormat("+#,###.000;-#,###.000");
class TLThingy extends StatefulWidget {
final TetraLeague tl;
final String userID;
final List<TetrioPlayer> states;
final List<TetraLeague> states;
final bool showTitle;
final bool bot;
final bool guest;
@ -47,13 +47,13 @@ class _TLThingyState extends State<TLThingy> with TickerProviderStateMixin {
late TetraLeague? oldTl;
late TetraLeague currentTl;
late RangeValues _currentRangeValues;
late List<TetrioPlayer> sortedStates;
late List<TetraLeague> sortedStates;
@override
void initState() {
_currentRangeValues = const RangeValues(0, 1);
sortedStates = widget.states.reversed.toList();
oldTl = sortedStates.elementAtOrNull(1)?.tlSeason1;
oldTl = sortedStates.elementAtOrNull(1);
currentTl = widget.tl;
super.initState();
}
@ -95,12 +95,12 @@ class _TLThingyState extends State<TLThingy> with TickerProviderStateMixin {
if (values.start.round() == 0){
currentTl = widget.tl;
}else{
currentTl = sortedStates[values.start.round()-1].tlSeason1!;
currentTl = sortedStates[values.start.round()-1]!;
}
if (values.end.round() == 0){
oldTl = widget.tl;
}else{
oldTl = sortedStates[values.end.round()-1].tlSeason1;
oldTl = sortedStates[values.end.round()-1];
}
});
},

View File

@ -182,7 +182,6 @@ class UserThingy extends StatelessWidget {
],),
onPressed: () {
teto.addPlayerToTrack(player).then((value) => setState());
teto.storeState(player);
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.becameTracked)));
},
),
@ -213,7 +212,7 @@ class UserThingy extends StatelessWidget {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => CompareView(greenSide: [player, null, player.tlSeason1], redSide: const [null, null, null], greenMode: Mode.player, redMode: Mode.player),
builder: (context) => CompareView(greenSide: [player, null, null], redSide: const [null, null, null], greenMode: Mode.player, redMode: Mode.player),
),
);
},

View File

@ -148,7 +148,7 @@ class _ZenithThingyState extends State<ZenithThingy> {
const Positioned(left: 25, top: 20, child: Text("otal time", style: TextStyle(fontFamily: "Eurostile Round Extended"))),
Padding(
padding: const EdgeInsets.only(left: 10.0),
child: Text("${getMoreNormalTime(record!.stats.finalTime)}", style: TextStyle(
child: Text(getMoreNormalTime(record!.stats.finalTime), style: const TextStyle(
shadows: textShadow,
fontFamily: "Eurostile Round Extended",
fontSize: 36,