diff --git a/lib/data_objects/tetrio.dart b/lib/data_objects/tetrio.dart index 70e8a0f..25092c2 100644 --- a/lib/data_objects/tetrio.dart +++ b/lib/data_objects/tetrio.dart @@ -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 json, ts) { + TetraLeague.fromJson(Map 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 toJson() { final Map data = {}; + 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}]; } } diff --git a/lib/main.dart b/lib/main.dart index 3f81e0f..bbba219 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -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), ) ), diff --git a/lib/services/sqlite_db_controller.dart b/lib/services/sqlite_db_controller.dart index 1af160c..b1bb348 100644 --- a/lib/services/sqlite_db_controller.dart +++ b/lib/services/sqlite_db_controller.dart @@ -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(); } diff --git a/lib/services/tetrio_crud.dart b/lib/services/tetrio_crud.dart index d39419c..e97d16c 100644 --- a/lib/services/tetrio_crud.dart +++ b/lib/services/tetrio_crud.dart @@ -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 _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> fetchAndsaveTLHistory(String id) async { + Future> 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> csv = const CsvToListConverter().convert(response.body)..removeAt(0); - List history = []; - // doesn't return nickname, need to retrieve it separately - String nick = await getNicknameByID(id); + List history = []; for (List 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 states = await getPlayer(id); - if (states.isEmpty) await createPlayer(history.first); - states.insertAll(0, history.reversed); - final Map statesJson = {}; - for (var e in states) { // making one big json out of this list - 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 storeState(TetrioPlayer tetrioPlayer) async { - // if tetrio player doesn't have entry in database - just calling different function - List states = await getPlayer(tetrioPlayer.userId); - if (states.isEmpty) { - await createPlayer(tetrioPlayer); - return; - } - - // we not going to add state, that is same, as the previous - if (!states.last.isSameState(tetrioPlayer)) states.add(tetrioPlayer); - - // Making map of the states - final Map 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> 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 query = await db.query(tetrioLeagueTable, where: '"id" = ? AND "season" = ?', whereArgs: [userID, season]); + List result = []; + for (var entry in query){ + result.add(TetraLeague.fromJson(entry as Map, entry["timestamp"], entry["season"], entry["id"])); + } + return result; + } + + /// Saves state (which is [TetraLeague]) to the local database. + Future storeState(TetraLeague league) async { + await ensureDbIsOpen(); + final db = getDatabaseOrThrow(); + List 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 deleteState(TetrioPlayer tetrioPlayer) async { - await ensureDbIsOpen(); - final db = getDatabaseOrThrow(); - List states = await getPlayer(tetrioPlayer.userId); - // removing state from map that contain every state of each user - states.removeWhere((element) => element.state == tetrioPlayer.state); + // Future deleteState(TetrioPlayer tetrioPlayer) async { + // await ensureDbIsOpen(); + // final db = getDatabaseOrThrow(); + // //List 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 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 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> getPlayer(String id) async { - await ensureDbIsOpen(); - final db = getDatabaseOrThrow(); - List 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> getPlayer(String id) async { + // await ensureDbIsOpen(); + // final db = getDatabaseOrThrow(); + // List 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. diff --git a/lib/views/compare_view.dart b/lib/views/compare_view.dart index 8c0df87..333df80 100644 --- a/lib/views/compare_view.dart +++ b/lib/views/compare_view.dart @@ -85,6 +85,7 @@ class CompareState extends State { theRedSide = [null, null, Summaries(user, TetraLeague( + id: "", timestamp: DateTime.now(), apm: apm, pps: pps, @@ -102,7 +103,7 @@ class CompareState extends State { 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 { _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 { theGreenSide = [null, null, Summaries(user, TetraLeague( + id: "", timestamp: DateTime.now(), apm: apm, pps: pps, @@ -178,7 +182,7 @@ class CompareState extends State { 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 { _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 { 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", diff --git a/lib/views/main_view.dart b/lib/views/main_view.dart index 9b06306..2d4fc1d 100644 --- a/lib/views/main_view.dart +++ b/lib/views/main_view.dart @@ -216,15 +216,15 @@ class _MainState extends State 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 states = []; TetraLeague? compareWith; Set uniqueTL = {}; List storedRecords = await teto.getTLMatchesbyPlayerID(me.userId); // get old matches - // if (isTracking){ // if tracked - save data to local DB - // await teto.storeState(me); - // //await teto.saveTLMatchesFromStream(tlStream); - // } + 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 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); diff --git a/lib/views/main_view_tiles.dart b/lib/views/main_view_tiles.dart index 83d57ab..c749d5b 100644 --- a/lib/views/main_view_tiles.dart +++ b/lib/views/main_view_tiles.dart @@ -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 { } } - 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 = >>[ // 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 { 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 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 { 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 { ); } - Widget getTetraLeagueCard(TetraLeague data){ + Widget getTetraLeagueCard(TetraLeague data, Cutoffs? cutoffs){ return Column( children: [ Card( @@ -845,7 +854,7 @@ class _DestinationHomeState extends State { ), ), ), - 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 { 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 { 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: "", diff --git a/lib/views/rank_averages_view.dart b/lib/views/rank_averages_view.dart index 557bda3..14c6ad0 100644 --- a/lib/views/rank_averages_view.dart +++ b/lib/views/rank_averages_view.dart @@ -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 diff --git a/lib/views/state_view.dart b/lib/views/state_view.dart index 19b190e..9be1ccf 100644 --- a/lib/views/state_view.dart +++ b/lib/views/state_view.dart @@ -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 { 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()))); } } diff --git a/lib/views/states_view.dart b/lib/views/states_view.dart index a7cab43..f5a0f2c 100644 --- a/lib/views/states_view.dart +++ b/lib/views/states_view.dart @@ -61,14 +61,14 @@ class StatesState extends State { 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: () { diff --git a/lib/views/tl_leaderboard_view.dart b/lib/views/tl_leaderboard_view.dart index 0a3d87e..d7699ad 100644 --- a/lib/views/tl_leaderboard_view.dart +++ b/lib/views/tl_leaderboard_view.dart @@ -210,7 +210,7 @@ class TLLeaderboardState extends State { ) ); } - return Text("end of FutureBuilder"); + return const Text("end of FutureBuilder"); } })), ); diff --git a/lib/widgets/tl_progress_bar.dart b/lib/widgets/tl_progress_bar.dart index 8a1be3c..bc8f94f 100644 --- a/lib/widgets/tl_progress_bar.dart +++ b/lib/widgets/tl_progress_bar.dart @@ -51,7 +51,7 @@ class TLProgress extends StatelessWidget{ ] ) ), - Spacer(), + const Spacer(), RichText( textAlign: TextAlign.right, text: TextSpan( diff --git a/lib/widgets/tl_rating_thingy.dart b/lib/widgets/tl_rating_thingy.dart index 3fc69be..ac7d4c3 100644 --- a/lib/widgets/tl_rating_thingy.dart +++ b/lib/widgets/tl_rating_thingy.dart @@ -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( diff --git a/lib/widgets/tl_thingy.dart b/lib/widgets/tl_thingy.dart index 0731e10..853adde 100644 --- a/lib/widgets/tl_thingy.dart +++ b/lib/widgets/tl_thingy.dart @@ -22,7 +22,7 @@ var intFDiff = NumberFormat("+#,###.000;-#,###.000"); class TLThingy extends StatefulWidget { final TetraLeague tl; final String userID; - final List states; + final List states; final bool showTitle; final bool bot; final bool guest; @@ -47,13 +47,13 @@ class _TLThingyState extends State with TickerProviderStateMixin { late TetraLeague? oldTl; late TetraLeague currentTl; late RangeValues _currentRangeValues; - late List sortedStates; + late List 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 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]; } }); }, diff --git a/lib/widgets/user_thingy.dart b/lib/widgets/user_thingy.dart index 62d12c4..f27e7a3 100644 --- a/lib/widgets/user_thingy.dart +++ b/lib/widgets/user_thingy.dart @@ -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), ), ); }, diff --git a/lib/widgets/zenith_thingy.dart b/lib/widgets/zenith_thingy.dart index 0a9394c..b8c8535 100644 --- a/lib/widgets/zenith_thingy.dart +++ b/lib/widgets/zenith_thingy.dart @@ -148,7 +148,7 @@ class _ZenithThingyState extends State { 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,