New cache management system
I do hope, that code looks more clean now
This commit is contained in:
parent
523038b9c8
commit
3b0eb4009d
|
@ -0,0 +1,15 @@
|
||||||
|
// p1nkl0bst3r data objects
|
||||||
|
|
||||||
|
class Cutoffs{
|
||||||
|
Map<String, double> tr;
|
||||||
|
Map<String, double> glicko;
|
||||||
|
|
||||||
|
Cutoffs(this.tr, this.glicko);
|
||||||
|
}
|
||||||
|
|
||||||
|
class TopTr{
|
||||||
|
String id;
|
||||||
|
double? tr;
|
||||||
|
|
||||||
|
TopTr(this.id, this.tr);
|
||||||
|
}
|
|
@ -1164,6 +1164,15 @@ class TetrioZen {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class UserRecords{
|
||||||
|
String id;
|
||||||
|
RecordSingle? sprint;
|
||||||
|
RecordSingle? blitz;
|
||||||
|
TetrioZen zen;
|
||||||
|
|
||||||
|
UserRecords(this.id, this.sprint, this.blitz, this.zen);
|
||||||
|
}
|
||||||
|
|
||||||
class Distinguishment {
|
class Distinguishment {
|
||||||
late String type;
|
late String type;
|
||||||
String? detail;
|
String? detail;
|
||||||
|
@ -1192,18 +1201,28 @@ class Distinguishment {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class News {
|
class News{
|
||||||
late String id;
|
late String id;
|
||||||
late String stream;
|
late List<NewsEntry> news;
|
||||||
|
|
||||||
|
News(this.id, this.news);
|
||||||
|
|
||||||
|
News.fromJson(Map<String, dynamic> json, String? userID){
|
||||||
|
id = userID != null ? "user_${userID}" : json['news'].first['stream'];
|
||||||
|
news = [for (var entry in json['news']) NewsEntry.fromJson(entry)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NewsEntry {
|
||||||
|
//late String id; do i need it?
|
||||||
late String type;
|
late String type;
|
||||||
late Map<String, dynamic> data;
|
late Map<String, dynamic> data;
|
||||||
late DateTime timestamp;
|
late DateTime timestamp;
|
||||||
|
|
||||||
News({required this.type, required this.id, required this.stream, required this.data, required this.timestamp});
|
NewsEntry({required this.type, required this.data, required this.timestamp});
|
||||||
|
|
||||||
News.fromJson(Map<String, dynamic> json){
|
NewsEntry.fromJson(Map<String, dynamic> json){
|
||||||
id = json["_id"];
|
//id = json["_id"];
|
||||||
stream = json["stream"];
|
|
||||||
type = json["type"];
|
type = json["type"];
|
||||||
data = json["data"];
|
data = json["data"];
|
||||||
timestamp = DateTime.parse(json['ts']);
|
timestamp = DateTime.parse(json['ts']);
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'tetrio.dart';
|
import 'tetrio.dart';
|
||||||
|
|
||||||
|
@ -57,6 +58,14 @@ class Garbage{ // charsys where???
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class RawReplay{
|
||||||
|
String id;
|
||||||
|
Uint8List asBytes;
|
||||||
|
String asString;
|
||||||
|
|
||||||
|
RawReplay(this.id, this.asBytes, this.asString);
|
||||||
|
}
|
||||||
|
|
||||||
class ReplayStats{
|
class ReplayStats{
|
||||||
late int seed;
|
late int seed;
|
||||||
late int linesCleared;
|
late int linesCleared;
|
||||||
|
|
|
@ -3,6 +3,7 @@ import 'dart:convert';
|
||||||
import 'dart:developer' as developer;
|
import 'dart:developer' as developer;
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
import 'package:tetra_stats/data_objects/tetra_stats.dart';
|
||||||
import 'package:tetra_stats/data_objects/tetrio_multiplayer_replay.dart';
|
import 'package:tetra_stats/data_objects/tetrio_multiplayer_replay.dart';
|
||||||
import 'package:tetra_stats/main.dart' show packageInfo;
|
import 'package:tetra_stats/main.dart' show packageInfo;
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
@ -65,22 +66,77 @@ const String createTetrioTLReplayStats = '''
|
||||||
)
|
)
|
||||||
''';
|
''';
|
||||||
|
|
||||||
|
class CacheController {
|
||||||
|
late Map<String, dynamic> _cache;
|
||||||
|
late Map<String, String> _nicknames;
|
||||||
|
|
||||||
|
CacheController.init(){
|
||||||
|
_cache = {};
|
||||||
|
_nicknames = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
String _getObjectId(dynamic object){
|
||||||
|
switch (object.runtimeType){
|
||||||
|
case TetrioPlayer:
|
||||||
|
object as TetrioPlayer;
|
||||||
|
_nicknames[object.username] = object.userId;
|
||||||
|
return object.userId;
|
||||||
|
case TetrioPlayersLeaderboard:
|
||||||
|
return object.runtimeType.toString()+object.type;
|
||||||
|
case Cutoffs:
|
||||||
|
return object.runtimeType.toString();
|
||||||
|
case TetrioPlayerFromLeaderboard: // i may be a little stupid
|
||||||
|
return object.runtimeType.toString()+"topone";
|
||||||
|
case TetraLeagueAlphaStream:
|
||||||
|
return object.runtimeType.toString()+object.userId;
|
||||||
|
default:
|
||||||
|
return object.runtimeType.toString()+object.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void store(dynamic object, int? cachedUntil) async {
|
||||||
|
String key = _getObjectId(object) + cachedUntil!.toString();
|
||||||
|
_cache[key] = object;
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamic get(String id, Type datatype){
|
||||||
|
if (_cache.isEmpty) return null;
|
||||||
|
MapEntry<String, dynamic>? objectEntry;
|
||||||
|
try{
|
||||||
|
switch (datatype){
|
||||||
|
case TetrioPlayer:
|
||||||
|
objectEntry = id.length <= 16 ? _cache.entries.firstWhere((element) => element.key.startsWith(_nicknames[id]??"huh?")) : _cache.entries.firstWhere((element) => element.key.startsWith(id));
|
||||||
|
if (id.length <= 16) id = _nicknames[id]??"huh?";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
objectEntry = _cache.entries.firstWhere((element) => element.key.startsWith(datatype.toString()+id));
|
||||||
|
id = datatype.toString()+id;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} on StateError{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (int.parse(objectEntry.key.substring(id.length)) <= DateTime.now().millisecondsSinceEpoch){
|
||||||
|
_cache.remove(objectEntry.key);
|
||||||
|
return null;
|
||||||
|
}else{
|
||||||
|
return objectEntry.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeOld() async {
|
||||||
|
_cache.removeWhere((key, value) => DateTime.fromMillisecondsSinceEpoch(int.parse(key.substring(_getObjectId(value).length)), isUtc: true).isAfter(DateTime.now()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset(){
|
||||||
|
_cache.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class TetrioService extends DB {
|
class TetrioService extends DB {
|
||||||
final Map<String, String> _players = {};
|
final Map<String, String> _players = {};
|
||||||
|
final _cache = CacheController.init(); // I'm trying to send as less requests, as possible, so i'm caching the results of those requests.
|
||||||
// I'm trying to send as less requests, as possible, so i'm caching the results of those requests.
|
final Map<String, PlayerLeaderboardPosition> _lbPositions = {}; // separate one because attached to the leaderboard
|
||||||
// Usually those maps looks like this: {"cached_until_unix_milliseconds": Object}
|
|
||||||
// TODO: Make a proper caching system
|
|
||||||
final Map<String, TetrioPlayer> _playersCache = {};
|
|
||||||
final Map<String, Map<String, dynamic>> _recordsCache = {};
|
|
||||||
final Map<String, dynamic> _replaysCache = {}; // the only one is different: {"replayID": [replayString, replayBytes]}
|
|
||||||
final Map<String, TetrioPlayersLeaderboard> _leaderboardsCache = {};
|
|
||||||
final Map<String, PlayerLeaderboardPosition> _lbPositions = {};
|
|
||||||
final Map<String, List<News>> _newsCache = {};
|
|
||||||
final Map<String, Map<String, double?>> _topTRcache = {};
|
|
||||||
final Map<String, List<Map<String, double>>> _cutoffsCache = {};
|
|
||||||
final Map<String, TetrioPlayerFromLeaderboard> _topOneFromLB = {};
|
|
||||||
final Map<String, TetraLeagueAlphaStream> _tlStreamsCache = {};
|
|
||||||
/// Thing, that sends every request to the API endpoints
|
/// Thing, that sends every request to the API endpoints
|
||||||
final client = kDebugMode ? UserAgentClient("Kagari-chan loves osk (Tetra Stats dev build)", http.Client()) : UserAgentClient("Tetra Stats v${packageInfo.version} (dm @dan63047 if someone abuse that software)", http.Client());
|
final client = kDebugMode ? UserAgentClient("Kagari-chan loves osk (Tetra Stats dev build)", http.Client()) : UserAgentClient("Tetra Stats v${packageInfo.version} (dm @dan63047 if someone abuse that software)", http.Client());
|
||||||
/// We should have only one instanse of this service
|
/// We should have only one instanse of this service
|
||||||
|
@ -157,14 +213,20 @@ class TetrioService extends DB {
|
||||||
/// Downloads replay from inoue (szy API). Requiers [replayID]. If request have
|
/// Downloads replay from inoue (szy API). Requiers [replayID]. If request have
|
||||||
/// different from 200 statusCode, it will throw an excepction. Returns list, that contains same replay
|
/// different from 200 statusCode, it will throw an excepction. Returns list, that contains same replay
|
||||||
/// as string and as binary.
|
/// as string and as binary.
|
||||||
Future<List<dynamic>> szyGetReplay(String replayID) async {
|
Future<RawReplay> szyGetReplay(String replayID) async {
|
||||||
try{ // read from cache
|
// Trying to get it from cache first
|
||||||
var cached = _replaysCache.entries.firstWhere((element) => element.key == replayID);
|
RawReplay? cached = _cache.get(replayID, RawReplay);
|
||||||
return cached.value;
|
if (cached != null) return cached;
|
||||||
}catch (e){
|
|
||||||
// actually going to obtain
|
// If failed, trying to obtain replay from download directory
|
||||||
|
if (!kIsWeb){ // can't obtain download directory on web
|
||||||
|
var downloadPath = await getDownloadsDirectory();
|
||||||
|
downloadPath ??= Platform.isAndroid ? Directory("/storage/emulated/0/Download") : await getApplicationDocumentsDirectory();
|
||||||
|
var replayFile = File("${downloadPath.path}/$replayID.ttrm");
|
||||||
|
if (replayFile.existsSync()) return RawReplay(replayID, replayFile.readAsBytesSync(), replayFile.readAsStringSync());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If failed, actually trying to retrieve
|
||||||
Uri url;
|
Uri url;
|
||||||
if (kIsWeb) { // Web version sends every request through my php script at the same domain, where Tetra Stats located because of CORS
|
if (kIsWeb) { // Web version sends every request through my php script at the same domain, where Tetra Stats located because of CORS
|
||||||
url = Uri.https('ts.dan63.by', 'oskware_bridge.php', {"endpoint": "tetrioReplay", "replayid": replayID});
|
url = Uri.https('ts.dan63.by', 'oskware_bridge.php', {"endpoint": "tetrioReplay", "replayid": replayID});
|
||||||
|
@ -172,22 +234,16 @@ class TetrioService extends DB {
|
||||||
url = Uri.https('inoue.szy.lol', '/api/replay/$replayID');
|
url = Uri.https('inoue.szy.lol', '/api/replay/$replayID');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trying to obtain replay from download directory first
|
|
||||||
if (!kIsWeb){ // can't obtain download directory on web
|
|
||||||
var downloadPath = await getDownloadsDirectory();
|
|
||||||
downloadPath ??= Platform.isAndroid ? Directory("/storage/emulated/0/Download") : await getApplicationDocumentsDirectory();
|
|
||||||
var replayFile = File("${downloadPath.path}/$replayID.ttrm");
|
|
||||||
if (replayFile.existsSync()) return [replayFile.readAsStringSync(), replayFile.readAsBytesSync()];
|
|
||||||
}
|
|
||||||
|
|
||||||
try{
|
try{
|
||||||
final response = await client.get(url);
|
final response = await client.get(url);
|
||||||
|
|
||||||
switch (response.statusCode) {
|
switch (response.statusCode) {
|
||||||
case 200:
|
case 200:
|
||||||
developer.log("szyDownload: Replay downloaded", name: "services/tetrio_crud", error: response.statusCode);
|
developer.log("szyDownload: Replay $replayID downloaded", name: "services/tetrio_crud");
|
||||||
_replaysCache[replayID] = [response.body, response.bodyBytes]; // Puts results into the cache
|
RawReplay replay = RawReplay(replayID, response.bodyBytes, response.body);
|
||||||
return [response.body, response.bodyBytes];
|
DateTime now = DateTime.now();
|
||||||
|
_cache.store(replay, now.millisecondsSinceEpoch + 3600000);
|
||||||
|
return replay;
|
||||||
// if not 200 - throw a unique for each code exception
|
// if not 200 - throw a unique for each code exception
|
||||||
case 404:
|
case 404:
|
||||||
throw SzyNotFound();
|
throw SzyNotFound();
|
||||||
|
@ -203,7 +259,7 @@ class TetrioService extends DB {
|
||||||
case 504:
|
case 504:
|
||||||
throw SzyInternalProblem();
|
throw SzyInternalProblem();
|
||||||
default:
|
default:
|
||||||
developer.log("szyDownload: Failed to download a replay", name: "services/tetrio_crud", error: response.statusCode);
|
developer.log("szyDownload: Failed to download a replay $replayID", name: "services/tetrio_crud", error: response.statusCode);
|
||||||
throw ConnectionIssue(response.statusCode, response.reasonPhrase??"No reason");
|
throw ConnectionIssue(response.statusCode, response.reasonPhrase??"No reason");
|
||||||
}
|
}
|
||||||
} on http.ClientException catch (e, s) { // If local http client fails
|
} on http.ClientException catch (e, s) { // If local http client fails
|
||||||
|
@ -219,8 +275,8 @@ class TetrioService extends DB {
|
||||||
downloadPath ??= Platform.isAndroid ? Directory("/storage/emulated/0/Download") : await getApplicationDocumentsDirectory();
|
downloadPath ??= Platform.isAndroid ? Directory("/storage/emulated/0/Download") : await getApplicationDocumentsDirectory();
|
||||||
var replayFile = File("${downloadPath.path}/$replayID.ttrm");
|
var replayFile = File("${downloadPath.path}/$replayID.ttrm");
|
||||||
if (replayFile.existsSync()) throw TetrioReplayAlreadyExist();
|
if (replayFile.existsSync()) throw TetrioReplayAlreadyExist();
|
||||||
var replay = await szyGetReplay(replayID);
|
RawReplay replay = await szyGetReplay(replayID);
|
||||||
await replayFile.writeAsBytes(replay[1]);
|
await replayFile.writeAsBytes(replay.asBytes);
|
||||||
return replayFile.path;
|
return replayFile.path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -235,7 +291,7 @@ class TetrioService extends DB {
|
||||||
if (!isAvailable) throw ReplayNotAvalable(); // if replay too old
|
if (!isAvailable) throw ReplayNotAvalable(); // if replay too old
|
||||||
|
|
||||||
// otherwise, actually going to download a replay and analyze it
|
// otherwise, actually going to download a replay and analyze it
|
||||||
String replay = (await szyGetReplay(replayID))[0];
|
String replay = (await szyGetReplay(replayID)).asString;
|
||||||
Map<String, dynamic> toAnalyze = jsonDecode(replay);
|
Map<String, dynamic> toAnalyze = jsonDecode(replay);
|
||||||
ReplayData data = ReplayData.fromJson(toAnalyze);
|
ReplayData data = ReplayData.fromJson(toAnalyze);
|
||||||
saveReplayStats(data); // saving to DB for later
|
saveReplayStats(data); // saving to DB for later
|
||||||
|
@ -244,19 +300,10 @@ class TetrioService extends DB {
|
||||||
|
|
||||||
/// Gets and returns Top TR for a player with given [id]. May return null if player top tr is unknown
|
/// Gets and returns Top TR for a player with given [id]. May return null if player top tr is unknown
|
||||||
/// or api is unavaliable (404). May throw an exception, if something else happens.
|
/// or api is unavaliable (404). May throw an exception, if something else happens.
|
||||||
Future<double?> fetchTopTR(String id) async {
|
Future<TopTr?> fetchTopTR(String id) async {
|
||||||
try{ // read from cache
|
// Trying to get it from cache first
|
||||||
var cached = _topTRcache.entries.firstWhere((element) => element.value.keys.first == id);
|
TopTr? cached = _cache.get(id, TopTr);
|
||||||
if (DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true).isAfter(DateTime.now())){ // if not expired
|
if (cached != null) return cached;
|
||||||
developer.log("fetchTopTR: Top TR retrieved from cache, that expires ${DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true)}", name: "services/tetrio_crud");
|
|
||||||
return cached.value.values.first;
|
|
||||||
}else{ // if cache expired
|
|
||||||
_topTRcache.remove(cached.key);
|
|
||||||
developer.log("fetchTopTR: Top TR expired (${DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true)})", name: "services/tetrio_crud");
|
|
||||||
}
|
|
||||||
}catch(e){ // actually going to obtain
|
|
||||||
developer.log("fetchTopTR: Trying to retrieve Top TR", name: "services/tetrio_crud");
|
|
||||||
}
|
|
||||||
|
|
||||||
Uri url;
|
Uri url;
|
||||||
if (kIsWeb) { // Web version sends every request through my php script at the same domain, where Tetra Stats located because of CORS
|
if (kIsWeb) { // Web version sends every request through my php script at the same domain, where Tetra Stats located because of CORS
|
||||||
|
@ -269,12 +316,15 @@ class TetrioService extends DB {
|
||||||
|
|
||||||
switch (response.statusCode) {
|
switch (response.statusCode) {
|
||||||
case 200: // ok - return the value
|
case 200: // ok - return the value
|
||||||
_topTRcache[(DateTime.now().millisecondsSinceEpoch + 300000).toString()] = {id: double.tryParse(response.body)};
|
TopTr result = TopTr(id, double.tryParse(response.body));
|
||||||
return double.tryParse(response.body);
|
_cache.store(result, DateTime.now().millisecondsSinceEpoch + 300000);
|
||||||
|
return result;
|
||||||
case 404: // not found - return null
|
case 404: // not found - return null
|
||||||
|
TopTr result = TopTr(id, null);
|
||||||
developer.log("fetchTopTR: Probably, player doesn't have top TR", name: "services/tetrio_crud", error: response.statusCode);
|
developer.log("fetchTopTR: Probably, player doesn't have top TR", name: "services/tetrio_crud", error: response.statusCode);
|
||||||
_topTRcache[(DateTime.now().millisecondsSinceEpoch + 300000).toString()] = {id: null};
|
_cache.store(result, DateTime.now().millisecondsSinceEpoch + 300000);
|
||||||
return null;
|
//_topTRcache[(DateTime.now().millisecondsSinceEpoch + 300000).toString()] = {id: null};
|
||||||
|
return result;
|
||||||
// if not 200 or 404 - throw a unique for each code exception
|
// if not 200 or 404 - throw a unique for each code exception
|
||||||
case 403:
|
case 403:
|
||||||
throw P1nkl0bst3rForbidden();
|
throw P1nkl0bst3rForbidden();
|
||||||
|
@ -300,19 +350,9 @@ class TetrioService extends DB {
|
||||||
// Sidenote: as you can see, fetch functions looks and works pretty much same way, as described above,
|
// Sidenote: as you can see, fetch functions looks and works pretty much same way, as described above,
|
||||||
// so i'm going to document only unique differences between them
|
// so i'm going to document only unique differences between them
|
||||||
|
|
||||||
Future<List<Map<String, double>>> fetchCutoffs() async {
|
Future<Cutoffs?> fetchCutoffs() async {
|
||||||
try{
|
Cutoffs? cached = _cache.get("", Cutoffs);
|
||||||
var cached = _cutoffsCache.entries.first;
|
if (cached != null) return cached;
|
||||||
if (DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true).isAfter(DateTime.now())){ // if not expired
|
|
||||||
developer.log("fetchCutoffs: Cutoffs retrieved from cache, that expires ${DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true)}", name: "services/tetrio_crud");
|
|
||||||
return cached.value;
|
|
||||||
}else{ // if cache expired
|
|
||||||
_topTRcache.remove(cached.key);
|
|
||||||
developer.log("fetchCutoffs: Cutoffs expired (${DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true)})", name: "services/tetrio_crud");
|
|
||||||
}
|
|
||||||
}catch(e){ // actually going to obtain
|
|
||||||
developer.log("fetchCutoffs: Trying to retrieve Cutoffs", name: "services/tetrio_crud");
|
|
||||||
}
|
|
||||||
|
|
||||||
Uri url;
|
Uri url;
|
||||||
if (kIsWeb) {
|
if (kIsWeb) {
|
||||||
|
@ -328,17 +368,16 @@ class TetrioService extends DB {
|
||||||
case 200:
|
case 200:
|
||||||
Map<String, dynamic> rawData = jsonDecode(response.body);
|
Map<String, dynamic> rawData = jsonDecode(response.body);
|
||||||
Map<String, dynamic> data = rawData["cutoffs"] as Map<String, dynamic>;
|
Map<String, dynamic> data = rawData["cutoffs"] as Map<String, dynamic>;
|
||||||
Map<String, double> trCutoffs = {};
|
Cutoffs result = Cutoffs({}, {});
|
||||||
Map<String, double> glickoCutoffs = {};
|
|
||||||
for (String rank in data.keys){
|
for (String rank in data.keys){
|
||||||
trCutoffs[rank] = data[rank]["rating"];
|
result.tr[rank] = data[rank]["rating"];
|
||||||
glickoCutoffs[rank] = data[rank]["glicko"];
|
result.glicko[rank] = data[rank]["glicko"];
|
||||||
}
|
}
|
||||||
_cutoffsCache[(rawData["ts"] + 300000).toString()] = [trCutoffs, glickoCutoffs];
|
_cache.store(result, rawData["ts"] + 300000);
|
||||||
return [trCutoffs, glickoCutoffs];
|
return result;
|
||||||
case 404:
|
case 404:
|
||||||
developer.log("fetchCutoffs: Cutoffs are gone", name: "services/tetrio_crud", error: response.statusCode);
|
developer.log("fetchCutoffs: Cutoffs are gone", name: "services/tetrio_crud", error: response.statusCode);
|
||||||
return [];
|
return null;
|
||||||
// if not 200 or 404 - throw a unique for each code exception
|
// if not 200 or 404 - throw a unique for each code exception
|
||||||
case 403:
|
case 403:
|
||||||
throw P1nkl0bst3rForbidden();
|
throw P1nkl0bst3rForbidden();
|
||||||
|
@ -362,18 +401,8 @@ class TetrioService extends DB {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<TetrioPlayerFromLeaderboard> fetchTopOneFromTheLeaderboard() async {
|
Future<TetrioPlayerFromLeaderboard> fetchTopOneFromTheLeaderboard() async {
|
||||||
try{
|
TetrioPlayerFromLeaderboard? cached = _cache.get("topone", TetrioPlayerFromLeaderboard);
|
||||||
var cached = _topOneFromLB.entries.first;
|
if (cached != null) return cached;
|
||||||
if (DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true).isAfter(DateTime.now())){ // if not expired
|
|
||||||
developer.log("fetchTopOneFromTheLeaderboard: Leader retrieved from cache, that expires ${DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true)}", name: "services/tetrio_crud");
|
|
||||||
return cached.value;
|
|
||||||
}else{ // if cache expired
|
|
||||||
_topTRcache.remove(cached.key);
|
|
||||||
developer.log("fetchTopOneFromTheLeaderboard: Leader expired (${DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true)})", name: "services/tetrio_crud");
|
|
||||||
}
|
|
||||||
}catch(e){ // actually going to obtain
|
|
||||||
developer.log("fetchTopOneFromTheLeaderboard: Trying to retrieve leader", name: "services/tetrio_crud");
|
|
||||||
}
|
|
||||||
|
|
||||||
Uri url;
|
Uri url;
|
||||||
if (kIsWeb) {
|
if (kIsWeb) {
|
||||||
|
@ -388,7 +417,9 @@ class TetrioService extends DB {
|
||||||
switch (response.statusCode) {
|
switch (response.statusCode) {
|
||||||
case 200:
|
case 200:
|
||||||
var rawJson = jsonDecode(response.body);
|
var rawJson = jsonDecode(response.body);
|
||||||
return TetrioPlayerFromLeaderboard.fromJson(rawJson["data"]["users"][0], DateTime.fromMillisecondsSinceEpoch(rawJson["cache"]["cached_at"]));
|
TetrioPlayerFromLeaderboard result = TetrioPlayerFromLeaderboard.fromJson(rawJson["data"]["users"][0], DateTime.fromMillisecondsSinceEpoch(rawJson["cache"]["cached_at"]));
|
||||||
|
_cache.store(result, rawJson["cache"]["cached_until"]);
|
||||||
|
return result;
|
||||||
case 404:
|
case 404:
|
||||||
throw TetrioPlayerNotExist();
|
throw TetrioPlayerNotExist();
|
||||||
// if not 200 or 404 - throw a unique for each code exception
|
// if not 200 or 404 - throw a unique for each code exception
|
||||||
|
@ -606,18 +637,9 @@ class TetrioService extends DB {
|
||||||
|
|
||||||
/// Retrieves full Tetra League leaderboard from Tetra Channel api. Returns a leaderboard object. Throws an exception if fails to retrieve.
|
/// Retrieves full Tetra League leaderboard from Tetra Channel api. Returns a leaderboard object. Throws an exception if fails to retrieve.
|
||||||
Future<TetrioPlayersLeaderboard> fetchTLLeaderboard() async {
|
Future<TetrioPlayersLeaderboard> fetchTLLeaderboard() async {
|
||||||
try{
|
TetrioPlayersLeaderboard? cached = _cache.get("league", TetrioPlayersLeaderboard);
|
||||||
var cached = _leaderboardsCache.entries.firstWhere((element) => element.value.type == "league");
|
if (cached != null) return cached;
|
||||||
if (DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true).isAfter(DateTime.now())){
|
|
||||||
developer.log("fetchTLLeaderboard: Leaderboard retrieved from cache, that expires ${DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true)}", name: "services/tetrio_crud");
|
|
||||||
return cached.value;
|
|
||||||
}else{
|
|
||||||
_leaderboardsCache.remove(cached.key);
|
|
||||||
developer.log("fetchTLLeaderboard: Leaderboard expired (${DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true)})", name: "services/tetrio_crud");
|
|
||||||
}
|
|
||||||
}catch(e){
|
|
||||||
developer.log("fetchTLLeaderboard: Trying to retrieve leaderboard", name: "services/tetrio_crud");
|
|
||||||
}
|
|
||||||
Uri url;
|
Uri url;
|
||||||
if (kIsWeb) {
|
if (kIsWeb) {
|
||||||
url = Uri.https('ts.dan63.by', 'oskware_bridge.php', {"endpoint": "TLLeaderboard"});
|
url = Uri.https('ts.dan63.by', 'oskware_bridge.php', {"endpoint": "TLLeaderboard"});
|
||||||
|
@ -634,7 +656,8 @@ class TetrioService extends DB {
|
||||||
if (rawJson['success']) { // if api confirmed that everything ok
|
if (rawJson['success']) { // if api confirmed that everything ok
|
||||||
TetrioPlayersLeaderboard leaderboard = TetrioPlayersLeaderboard.fromJson(rawJson['data']['users'], "league", DateTime.fromMillisecondsSinceEpoch(rawJson['cache']['cached_at']));
|
TetrioPlayersLeaderboard leaderboard = TetrioPlayersLeaderboard.fromJson(rawJson['data']['users'], "league", DateTime.fromMillisecondsSinceEpoch(rawJson['cache']['cached_at']));
|
||||||
developer.log("fetchTLLeaderboard: Leaderboard retrieved and cached", name: "services/tetrio_crud");
|
developer.log("fetchTLLeaderboard: Leaderboard retrieved and cached", name: "services/tetrio_crud");
|
||||||
_leaderboardsCache[rawJson['cache']['cached_until'].toString()] = leaderboard;
|
//_leaderboardsCache[rawJson['cache']['cached_until'].toString()] = leaderboard;
|
||||||
|
_cache.store(leaderboard, rawJson['cache']['cached_until']);
|
||||||
return leaderboard;
|
return leaderboard;
|
||||||
} else { // idk how to hit that one
|
} else { // idk how to hit that one
|
||||||
developer.log("fetchTLLeaderboard: Bruh", name: "services/tetrio_crud", error: rawJson);
|
developer.log("fetchTLLeaderboard: Bruh", name: "services/tetrio_crud", error: rawJson);
|
||||||
|
@ -662,25 +685,13 @@ class TetrioService extends DB {
|
||||||
}
|
}
|
||||||
|
|
||||||
TetrioPlayersLeaderboard? getCachedLeaderboard(){
|
TetrioPlayersLeaderboard? getCachedLeaderboard(){
|
||||||
return _leaderboardsCache.entries.firstOrNull?.value;
|
return _cache.get("league", TetrioPlayersLeaderboard);
|
||||||
// That function will break if i decide to recive other leaderboards
|
|
||||||
// TODO: Think about better solution
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieves and returns 100 latest news entries from Tetra Channel api for given [userID]. Throws an exception if fails to retrieve.
|
/// Retrieves and returns 100 latest news entries from Tetra Channel api for given [userID]. Throws an exception if fails to retrieve.
|
||||||
Future<List<News>> fetchNews(String userID) async{
|
Future<News> fetchNews(String userID) async{
|
||||||
try{
|
News? cached = _cache.get(userID, News);
|
||||||
var cached = _newsCache.entries.firstWhere((element) => element.value[0].stream == "user_$userID");
|
if (cached != null) return cached;
|
||||||
if (DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true).isAfter(DateTime.now())){
|
|
||||||
developer.log("fetchNews: News for $userID retrieved from cache, that expires ${DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true)}", name: "services/tetrio_crud");
|
|
||||||
return cached.value;
|
|
||||||
}else{
|
|
||||||
_newsCache.remove(cached.key);
|
|
||||||
developer.log("fetchNews: Cached news for $userID expired (${DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true)})", name: "services/tetrio_crud");
|
|
||||||
}
|
|
||||||
}catch(e){
|
|
||||||
developer.log("fetchNews: Trying to retrieve news for $userID", name: "services/tetrio_crud");
|
|
||||||
}
|
|
||||||
|
|
||||||
Uri url;
|
Uri url;
|
||||||
if (kIsWeb) {
|
if (kIsWeb) {
|
||||||
|
@ -695,8 +706,8 @@ class TetrioService extends DB {
|
||||||
case 200:
|
case 200:
|
||||||
var payload = jsonDecode(response.body);
|
var payload = jsonDecode(response.body);
|
||||||
if (payload['success']) { // if api confirmed that everything ok
|
if (payload['success']) { // if api confirmed that everything ok
|
||||||
List<News> news = [for (var entry in payload['data']['news']) News.fromJson(entry)];
|
News news = News.fromJson(payload['data'], userID);
|
||||||
_newsCache[payload['cache']['cached_until'].toString()] = news;
|
_cache.store(news, payload['cache']['cached_until']);
|
||||||
developer.log("fetchNews: $userID news retrieved and cached", name: "services/tetrio_crud");
|
developer.log("fetchNews: $userID news retrieved and cached", name: "services/tetrio_crud");
|
||||||
return news;
|
return news;
|
||||||
} else {
|
} else {
|
||||||
|
@ -727,18 +738,8 @@ class TetrioService extends DB {
|
||||||
/// Retrieves avaliable Tetra League matches from Tetra Channel api. Returns stream object (fake stream).
|
/// Retrieves avaliable Tetra League matches from Tetra Channel api. Returns stream object (fake stream).
|
||||||
/// Throws an exception if fails to retrieve.
|
/// Throws an exception if fails to retrieve.
|
||||||
Future<TetraLeagueAlphaStream> fetchTLStream(String userID) async {
|
Future<TetraLeagueAlphaStream> fetchTLStream(String userID) async {
|
||||||
try{
|
TetraLeagueAlphaStream? cached = _cache.get(userID, TetraLeagueAlphaStream);
|
||||||
var cached = _tlStreamsCache.entries.firstWhere((element) => element.value.userId == userID);
|
if (cached != null) return cached;
|
||||||
if (DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true).isAfter(DateTime.now())){
|
|
||||||
developer.log("fetchTLStream: Stream $userID retrieved from cache, that expires ${DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true)}", name: "services/tetrio_crud");
|
|
||||||
return cached.value;
|
|
||||||
}else{
|
|
||||||
_tlStreamsCache.remove(cached.key);
|
|
||||||
developer.log("fetchTLStream: Cached stream $userID expired (${DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true)})", name: "services/tetrio_crud");
|
|
||||||
}
|
|
||||||
}catch(e){
|
|
||||||
developer.log("fetchTLStream: Trying to retrieve stream $userID", name: "services/tetrio_crud");
|
|
||||||
}
|
|
||||||
|
|
||||||
Uri url;
|
Uri url;
|
||||||
if (kIsWeb) {
|
if (kIsWeb) {
|
||||||
|
@ -753,7 +754,7 @@ class TetrioService extends DB {
|
||||||
case 200:
|
case 200:
|
||||||
if (jsonDecode(response.body)['success']) {
|
if (jsonDecode(response.body)['success']) {
|
||||||
TetraLeagueAlphaStream stream = TetraLeagueAlphaStream.fromJson(jsonDecode(response.body)['data']['records'], userID);
|
TetraLeagueAlphaStream stream = TetraLeagueAlphaStream.fromJson(jsonDecode(response.body)['data']['records'], userID);
|
||||||
_tlStreamsCache[jsonDecode(response.body)['cache']['cached_until'].toString()] = stream;
|
_cache.store(stream, jsonDecode(response.body)['cache']['cached_until']);
|
||||||
developer.log("fetchTLStream: $userID stream retrieved and cached", name: "services/tetrio_crud");
|
developer.log("fetchTLStream: $userID stream retrieved and cached", name: "services/tetrio_crud");
|
||||||
return stream;
|
return stream;
|
||||||
} else {
|
} else {
|
||||||
|
@ -864,21 +865,11 @@ class TetrioService extends DB {
|
||||||
await db.delete(tetrioTLReplayStatsTable, where: '$idCol = ?', whereArgs: [rID]);
|
await db.delete(tetrioTLReplayStatsTable, where: '$idCol = ?', whereArgs: [rID]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieves Blitz, 40 Lines and Zen records for a given [userID] from Tetra Channel api. Returns Map, which contains user id (`user`),
|
/// Retrieves Blitz, 40 Lines and Zen records for a given [userID] from Tetra Channel api. Returns `UserRecords`.
|
||||||
/// Blitz (`blitz`) and 40 Lines (`sprint`) record objects and Zen object (`zen`). Throws an exception if fails to retrieve.
|
/// Throws an exception if fails to retrieve.
|
||||||
Future<Map<String, dynamic>> fetchRecords(String userID) async {
|
Future<UserRecords> fetchRecords(String userID) async {
|
||||||
try{
|
UserRecords? cached = _cache.get(userID, UserRecords);
|
||||||
var cached = _recordsCache.entries.firstWhere((element) => element.value['user'] == userID);
|
if (cached != null) return cached;
|
||||||
if (DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true).isAfter(DateTime.now())){
|
|
||||||
developer.log("fetchRecords: $userID records retrieved from cache, that expires ${DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true)}", name: "services/tetrio_crud");
|
|
||||||
return cached.value;
|
|
||||||
}else{
|
|
||||||
_recordsCache.remove(cached.key);
|
|
||||||
developer.log("fetchRecords: $userID records expired (${DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true)})", name: "services/tetrio_crud");
|
|
||||||
}
|
|
||||||
}catch(e){
|
|
||||||
developer.log("fetchRecords: Trying to retrieve $userID records", name: "services/tetrio_crud");
|
|
||||||
}
|
|
||||||
|
|
||||||
Uri url;
|
Uri url;
|
||||||
if (kIsWeb) {
|
if (kIsWeb) {
|
||||||
|
@ -892,7 +883,7 @@ class TetrioService extends DB {
|
||||||
switch (response.statusCode) {
|
switch (response.statusCode) {
|
||||||
case 200:
|
case 200:
|
||||||
if (jsonDecode(response.body)['success']) {
|
if (jsonDecode(response.body)['success']) {
|
||||||
Map jsonRecords = jsonDecode(response.body);
|
Map jsonRecords = jsonDecode(response.body);
|
||||||
var sprint = jsonRecords['data']['records']['40l']['record'] != null
|
var sprint = jsonRecords['data']['records']['40l']['record'] != null
|
||||||
? RecordSingle.fromJson(jsonRecords['data']['records']['40l']['record'], jsonRecords['data']['records']['40l']['rank'])
|
? RecordSingle.fromJson(jsonRecords['data']['records']['40l']['record'], jsonRecords['data']['records']['40l']['rank'])
|
||||||
: null;
|
: null;
|
||||||
|
@ -900,10 +891,10 @@ class TetrioService extends DB {
|
||||||
? RecordSingle.fromJson(jsonRecords['data']['records']['blitz']['record'], jsonRecords['data']['records']['blitz']['rank'])
|
? RecordSingle.fromJson(jsonRecords['data']['records']['blitz']['record'], jsonRecords['data']['records']['blitz']['rank'])
|
||||||
: null;
|
: null;
|
||||||
var zen = TetrioZen.fromJson(jsonRecords['data']['zen']);
|
var zen = TetrioZen.fromJson(jsonRecords['data']['zen']);
|
||||||
Map<String, dynamic> map = {"user": userID.toLowerCase().trim(), "sprint": sprint, "blitz": blitz, "zen": zen};
|
UserRecords result = UserRecords(userID, sprint, blitz, zen);
|
||||||
_recordsCache[jsonDecode(response.body)['cache']['cached_until'].toString()] = map;
|
_cache.store(result, jsonDecode(response.body)['cache']['cached_until']);
|
||||||
developer.log("fetchRecords: $userID records retrieved and cached", name: "services/tetrio_crud");
|
developer.log("fetchRecords: $userID records retrieved and cached", name: "services/tetrio_crud");
|
||||||
return map;
|
return result;
|
||||||
} else {
|
} else {
|
||||||
developer.log("fetchRecords User dosen't exist", name: "services/tetrio_crud", error: response.body);
|
developer.log("fetchRecords User dosen't exist", name: "services/tetrio_crud", error: response.body);
|
||||||
throw TetrioPlayerNotExist();
|
throw TetrioPlayerNotExist();
|
||||||
|
@ -997,8 +988,7 @@ class TetrioService extends DB {
|
||||||
}
|
}
|
||||||
|
|
||||||
// we not going to add state, that is same, as the previous
|
// we not going to add state, that is same, as the previous
|
||||||
bool test = states.last.isSameState(tetrioPlayer);
|
if (!states.last.isSameState(tetrioPlayer)) states.add(tetrioPlayer);
|
||||||
if (test == false) states.add(tetrioPlayer);
|
|
||||||
|
|
||||||
// Making map of the states
|
// Making map of the states
|
||||||
final Map<String, dynamic> statesJson = {};
|
final Map<String, dynamic> statesJson = {};
|
||||||
|
@ -1058,18 +1048,8 @@ class TetrioService extends DB {
|
||||||
/// Retrieves general stats of [user] (nickname or id) from Tetra Channel api. Returns [TetrioPlayer] object of this user.
|
/// 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.
|
/// If [isItDiscordID] is true, function expects [user] to be a discord user id. Throws an exception if fails to retrieve.
|
||||||
Future<TetrioPlayer> fetchPlayer(String user, {bool isItDiscordID = false}) async {
|
Future<TetrioPlayer> fetchPlayer(String user, {bool isItDiscordID = false}) async {
|
||||||
try{
|
TetrioPlayer? cached = _cache.get(user, TetrioPlayer);
|
||||||
var cached = _playersCache.entries.firstWhere((element) => element.value.userId == user || element.value.username == user);
|
if (cached != null) return cached;
|
||||||
if (DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true).isAfter(DateTime.now())){
|
|
||||||
developer.log("fetchPlayer: User $user retrieved from cache, that expires ${DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true)}", name: "services/tetrio_crud");
|
|
||||||
return cached.value;
|
|
||||||
}else{
|
|
||||||
_playersCache.remove(cached.key);
|
|
||||||
developer.log("fetchPlayer: Cached user $user expired (${DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true)})", name: "services/tetrio_crud");
|
|
||||||
}
|
|
||||||
}catch(e){
|
|
||||||
developer.log("fetchPlayer: Trying to retrieve $user", name: "services/tetrio_crud");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isItDiscordID){
|
if (isItDiscordID){
|
||||||
// trying to find player with given discord id
|
// trying to find player with given discord id
|
||||||
|
@ -1131,7 +1111,7 @@ class TetrioService extends DB {
|
||||||
if (json['success']) {
|
if (json['success']) {
|
||||||
// parse and count stats
|
// parse and count stats
|
||||||
TetrioPlayer player = TetrioPlayer.fromJson(json['data']['user'], DateTime.fromMillisecondsSinceEpoch(json['cache']['cached_at'], isUtc: true), json['data']['user']['_id'], json['data']['user']['username']);
|
TetrioPlayer player = TetrioPlayer.fromJson(json['data']['user'], DateTime.fromMillisecondsSinceEpoch(json['cache']['cached_at'], isUtc: true), json['data']['user']['_id'], json['data']['user']['username']);
|
||||||
_playersCache[jsonDecode(response.body)['cache']['cached_until'].toString()] = player;
|
_cache.store(player, json['cache']['cached_until']);
|
||||||
developer.log("fetchPlayer: $user retrieved and cached", name: "services/tetrio_crud");
|
developer.log("fetchPlayer: $user retrieved and cached", name: "services/tetrio_crud");
|
||||||
return player;
|
return player;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -10,6 +10,7 @@ import 'package:intl/intl.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:syncfusion_flutter_charts/charts.dart';
|
import 'package:syncfusion_flutter_charts/charts.dart';
|
||||||
|
import 'package:tetra_stats/data_objects/tetra_stats.dart';
|
||||||
import 'package:tetra_stats/data_objects/tetrio.dart';
|
import 'package:tetra_stats/data_objects/tetrio.dart';
|
||||||
import 'package:tetra_stats/gen/strings.g.dart';
|
import 'package:tetra_stats/gen/strings.g.dart';
|
||||||
import 'package:tetra_stats/services/tetrio_crud.dart';
|
import 'package:tetra_stats/services/tetrio_crud.dart';
|
||||||
|
@ -174,23 +175,23 @@ class _MainState extends State<MainView> with TickerProviderStateMixin {
|
||||||
// Requesting Tetra League (alpha), records, news and top TR of player
|
// Requesting Tetra League (alpha), records, news and top TR of player
|
||||||
late List<dynamic> requests;
|
late List<dynamic> requests;
|
||||||
late TetraLeagueAlphaStream tlStream;
|
late TetraLeagueAlphaStream tlStream;
|
||||||
late Map<String, dynamic> records;
|
late UserRecords records;
|
||||||
late List<News> news;
|
late News news;
|
||||||
late TetrioPlayerFromLeaderboard? topOne;
|
late TetrioPlayerFromLeaderboard? topOne;
|
||||||
late double? topTR;
|
late TopTr? topTR;
|
||||||
requests = await Future.wait([ // all at once
|
requests = await Future.wait([ // all at once
|
||||||
teto.fetchTLStream(_searchFor),
|
teto.fetchTLStream(_searchFor),
|
||||||
teto.fetchRecords(_searchFor),
|
teto.fetchRecords(_searchFor),
|
||||||
teto.fetchNews(_searchFor),
|
teto.fetchNews(_searchFor),
|
||||||
prefs.getBool("showPositions") != true ? teto.fetchCutoffs() : Future.delayed(Duration.zero, ()=><Map<String, double>>[]),
|
prefs.getBool("showPositions") != true ? teto.fetchCutoffs() : Future.delayed(Duration.zero, ()=><Map<String, double>>[]),
|
||||||
(me.tlSeason1.rank != "z" ? me.tlSeason1.rank == "x" : me.tlSeason1.percentileRank == "x") ? teto.fetchTopOneFromTheLeaderboard() : Future.delayed(Duration.zero, ()=>null),
|
(me.tlSeason1.rank != "z" ? me.tlSeason1.rank == "x" : me.tlSeason1.percentileRank == "x") ? teto.fetchTopOneFromTheLeaderboard() : Future.delayed(Duration.zero, ()=>null),
|
||||||
if (me.tlSeason1.gamesPlayed > 9) teto.fetchTopTR(_searchFor) // can retrieve this only if player has TR
|
(me.tlSeason1.gamesPlayed > 9) ? teto.fetchTopTR(_searchFor) : Future.delayed(Duration.zero, () => null) // can retrieve this only if player has TR
|
||||||
]);
|
]);
|
||||||
tlStream = requests[0] as TetraLeagueAlphaStream;
|
tlStream = requests[0] as TetraLeagueAlphaStream;
|
||||||
records = requests[1] as Map<String, dynamic>;
|
records = requests[1] as UserRecords;
|
||||||
news = requests[2] as List<News>;
|
news = requests[2] as News;
|
||||||
topOne = requests[4] as TetrioPlayerFromLeaderboard?;
|
topOne = requests[4] as TetrioPlayerFromLeaderboard?;
|
||||||
topTR = requests.elementAtOrNull(5) as double?; // No TR - no Top TR
|
topTR = requests[5] as TopTr?; // No TR - no Top TR
|
||||||
|
|
||||||
meAmongEveryone = teto.getCachedLeaderboardPositions(me.userId);
|
meAmongEveryone = teto.getCachedLeaderboardPositions(me.userId);
|
||||||
if (prefs.getBool("showPositions") == true){
|
if (prefs.getBool("showPositions") == true){
|
||||||
|
@ -202,8 +203,8 @@ class _MainState extends State<MainView> with TickerProviderStateMixin {
|
||||||
if (meAmongEveryone != null) teto.cacheLeaderboardPositions(me.userId, meAmongEveryone!);
|
if (meAmongEveryone != null) teto.cacheLeaderboardPositions(me.userId, meAmongEveryone!);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Map<String, double>? cutoffs = prefs.getBool("showPositions") == true ? everyone!.cutoffs : (requests[3] as List<Map<String, double>>).elementAtOrNull(0);
|
Map<String, double>? cutoffs = prefs.getBool("showPositions") == true ? everyone!.cutoffs : (requests[3] as Cutoffs?)?.tr;
|
||||||
Map<String, double>? cutoffsGlicko = prefs.getBool("showPositions") == true ? everyone!.cutoffsGlicko : (requests[3] as List<Map<String, double>>).elementAtOrNull(1);
|
Map<String, double>? cutoffsGlicko = prefs.getBool("showPositions") == true ? everyone!.cutoffsGlicko : (requests[3] as Cutoffs?)?.glicko;
|
||||||
|
|
||||||
if (me.tlSeason1.gamesPlayed > 9) {
|
if (me.tlSeason1.gamesPlayed > 9) {
|
||||||
thatRankCutoff = cutoffs?[me.tlSeason1.rank != "z" ? me.tlSeason1.rank : me.tlSeason1.percentileRank];
|
thatRankCutoff = cutoffs?[me.tlSeason1.rank != "z" ? me.tlSeason1.rank : me.tlSeason1.percentileRank];
|
||||||
|
@ -459,7 +460,7 @@ class _MainState extends State<MainView> with TickerProviderStateMixin {
|
||||||
tl: snapshot.data![0].tlSeason1,
|
tl: snapshot.data![0].tlSeason1,
|
||||||
userID: snapshot.data![0].userId,
|
userID: snapshot.data![0].userId,
|
||||||
states: snapshot.data![2],
|
states: snapshot.data![2],
|
||||||
topTR: snapshot.data![7],
|
topTR: snapshot.data![7]?.tr,
|
||||||
bot: snapshot.data![0].role == "bot",
|
bot: snapshot.data![0].role == "bot",
|
||||||
guest: snapshot.data![0].role == "anon",
|
guest: snapshot.data![0].role == "anon",
|
||||||
thatRankCutoff: thatRankCutoff,
|
thatRankCutoff: thatRankCutoff,
|
||||||
|
@ -478,14 +479,14 @@ class _MainState extends State<MainView> with TickerProviderStateMixin {
|
||||||
),
|
),
|
||||||
],),
|
],),
|
||||||
_History(chartsData: chartsData, changePlayer: changePlayer, userID: _searchFor, update: _justUpdate, wasActiveInTL: snapshot.data![0].tlSeason1.gamesPlayed > 0),
|
_History(chartsData: chartsData, changePlayer: changePlayer, userID: _searchFor, update: _justUpdate, wasActiveInTL: snapshot.data![0].tlSeason1.gamesPlayed > 0),
|
||||||
_TwoRecordsThingy(sprint: snapshot.data![1]['sprint'], blitz: snapshot.data![1]['blitz'], rank: snapshot.data![0].tlSeason1.percentileRank,),
|
_TwoRecordsThingy(sprint: snapshot.data![1].sprint, blitz: snapshot.data![1].blitz, rank: snapshot.data![0].tlSeason1.percentileRank,),
|
||||||
_OtherThingy(zen: snapshot.data![1]['zen'], bio: snapshot.data![0].bio, distinguishment: snapshot.data![0].distinguishment, newsletter: snapshot.data![6],)
|
_OtherThingy(zen: snapshot.data![1].zen, bio: snapshot.data![0].bio, distinguishment: snapshot.data![0].distinguishment, newsletter: snapshot.data![6],)
|
||||||
] : [
|
] : [
|
||||||
TLThingy(
|
TLThingy(
|
||||||
tl: snapshot.data![0].tlSeason1,
|
tl: snapshot.data![0].tlSeason1,
|
||||||
userID: snapshot.data![0].userId,
|
userID: snapshot.data![0].userId,
|
||||||
states: snapshot.data![2],
|
states: snapshot.data![2],
|
||||||
topTR: snapshot.data![7],
|
topTR: snapshot.data![7]?.tr,
|
||||||
bot: snapshot.data![0].role == "bot",
|
bot: snapshot.data![0].role == "bot",
|
||||||
guest: snapshot.data![0].role == "anon",
|
guest: snapshot.data![0].role == "anon",
|
||||||
thatRankCutoff: thatRankCutoff,
|
thatRankCutoff: thatRankCutoff,
|
||||||
|
@ -499,9 +500,9 @@ class _MainState extends State<MainView> with TickerProviderStateMixin {
|
||||||
),
|
),
|
||||||
_TLRecords(userID: snapshot.data![0].userId, changePlayer: changePlayer, data: snapshot.data![3], wasActiveInTL: snapshot.data![0].tlSeason1.gamesPlayed > 0, oldMathcesHere: _TLHistoryWasFetched),
|
_TLRecords(userID: snapshot.data![0].userId, changePlayer: changePlayer, data: snapshot.data![3], wasActiveInTL: snapshot.data![0].tlSeason1.gamesPlayed > 0, oldMathcesHere: _TLHistoryWasFetched),
|
||||||
_History(chartsData: chartsData, changePlayer: changePlayer, userID: _searchFor, update: _justUpdate, wasActiveInTL: snapshot.data![0].tlSeason1.gamesPlayed > 0),
|
_History(chartsData: chartsData, changePlayer: changePlayer, userID: _searchFor, update: _justUpdate, wasActiveInTL: snapshot.data![0].tlSeason1.gamesPlayed > 0),
|
||||||
_RecordThingy(record: snapshot.data![1]['sprint'], rank: snapshot.data![0].tlSeason1.percentileRank),
|
_RecordThingy(record: snapshot.data![1].sprint, rank: snapshot.data![0].tlSeason1.percentileRank),
|
||||||
_RecordThingy(record: snapshot.data![1]['blitz'], rank: snapshot.data![0].tlSeason1.percentileRank),
|
_RecordThingy(record: snapshot.data![1].blitz, rank: snapshot.data![0].tlSeason1.percentileRank),
|
||||||
_OtherThingy(zen: snapshot.data![1]['zen'], bio: snapshot.data![0].bio, distinguishment: snapshot.data![0].distinguishment, newsletter: snapshot.data![6],)
|
_OtherThingy(zen: snapshot.data![1].zen, bio: snapshot.data![0].bio, distinguishment: snapshot.data![0].distinguishment, newsletter: snapshot.data![6],)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -1288,7 +1289,7 @@ class _OtherThingy extends StatelessWidget {
|
||||||
final TetrioZen? zen;
|
final TetrioZen? zen;
|
||||||
final String? bio;
|
final String? bio;
|
||||||
final Distinguishment? distinguishment;
|
final Distinguishment? distinguishment;
|
||||||
final List<News>? newsletter;
|
final News? newsletter;
|
||||||
|
|
||||||
/// Widget, that shows players [distinguishment], [bio], [zen] and [newsletter]
|
/// Widget, that shows players [distinguishment], [bio], [zen] and [newsletter]
|
||||||
const _OtherThingy({required this.zen, required this.bio, required this.distinguishment, this.newsletter});
|
const _OtherThingy({required this.zen, required this.bio, required this.distinguishment, this.newsletter});
|
||||||
|
@ -1337,7 +1338,7 @@ class _OtherThingy extends StatelessWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handles [news] entry and returns widget that contains this entry
|
/// Handles [news] entry and returns widget that contains this entry
|
||||||
ListTile getNewsTile(News news){
|
ListTile getNewsTile(NewsEntry news){
|
||||||
Map<String, String> gametypes = {
|
Map<String, String> gametypes = {
|
||||||
"40l": t.sprint,
|
"40l": t.sprint,
|
||||||
"blitz": t.blitz,
|
"blitz": t.blitz,
|
||||||
|
@ -1519,7 +1520,7 @@ class _OtherThingy extends StatelessWidget {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (newsletter != null && newsletter!.isNotEmpty && showNewsTitle)
|
if (newsletter != null && newsletter!.news.isNotEmpty && showNewsTitle)
|
||||||
Text(t.news, style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)),
|
Text(t.news, style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
@ -1535,9 +1536,9 @@ class _OtherThingy extends StatelessWidget {
|
||||||
SizedBox(width: 450, child: getShit(context, true, false)),
|
SizedBox(width: 450, child: getShit(context, true, false)),
|
||||||
SizedBox(width: constraints.maxWidth - 450, child: ListView.builder(
|
SizedBox(width: constraints.maxWidth - 450, child: ListView.builder(
|
||||||
physics: const AlwaysScrollableScrollPhysics(),
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
itemCount: newsletter!.length+1,
|
itemCount: newsletter!.news.length+1,
|
||||||
itemBuilder: (BuildContext context, int index) {
|
itemBuilder: (BuildContext context, int index) {
|
||||||
return index == 0 ? Center(child: Text(t.news, style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 42))) : getNewsTile(newsletter![index-1]);
|
return index == 0 ? Center(child: Text(t.news, style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 42))) : getNewsTile(newsletter!.news[index-1]);
|
||||||
}
|
}
|
||||||
))
|
))
|
||||||
]
|
]
|
||||||
|
@ -1546,9 +1547,9 @@ class _OtherThingy extends StatelessWidget {
|
||||||
else {
|
else {
|
||||||
return ListView.builder(
|
return ListView.builder(
|
||||||
physics: const AlwaysScrollableScrollPhysics(),
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
itemCount: newsletter!.length+1,
|
itemCount: newsletter!.news.length+1,
|
||||||
itemBuilder: (BuildContext context, int index) {
|
itemBuilder: (BuildContext context, int index) {
|
||||||
return index == 0 ? getShit(context, bigScreen, true) : getNewsTile(newsletter![index-1]);
|
return index == 0 ? getShit(context, bigScreen, true) : getNewsTile(newsletter!.news[index-1]);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,7 +70,7 @@ class TLProgress extends StatelessWidget{
|
||||||
if (tlData.nextAt > 0 && nextRankTRcutoff != null) const TextSpan(text: "\n"),
|
if (tlData.nextAt > 0 && nextRankTRcutoff != null) const TextSpan(text: "\n"),
|
||||||
if (nextRankTRcutoff != null) TextSpan(text: "${f2.format(nextRankTRcutoff)} (${comparef2.format(nextRankTRcutoff!-tlData.rating)}) TR"),
|
if (nextRankTRcutoff != null) TextSpan(text: "${f2.format(nextRankTRcutoff)} (${comparef2.format(nextRankTRcutoff!-tlData.rating)}) TR"),
|
||||||
if ((tlData.nextAt > 0 || nextRankTRcutoff != null) && nextRankGlickoCutoff != null) const TextSpan(text: "\n"),
|
if ((tlData.nextAt > 0 || nextRankTRcutoff != null) && nextRankGlickoCutoff != null) const TextSpan(text: "\n"),
|
||||||
if (nextRankGlickoCutoff != null) TextSpan(text: (tlData.standing < tlData.nextAt || ((nextRankGlickoCutoff!-tlData.glicko!)/glickoForWin < 0.5 && tlData.percentileRank != "x")) ? t.promotionOnNextWin : t.numOfVictories(wins: f2.format((nextRankGlickoCutoff!-tlData.glicko!)/glickoForWin)), style: TextStyle(color: (tlData.standing < tlData.nextAt || ((nextRankGlickoCutoff!-tlData.glicko!)/glickoForWin < 0.5 && tlData.percentileRank != "x")) ? Colors.greenAccent : null))
|
if (nextRankGlickoCutoff != null) TextSpan(text: (tlData.standing < tlData.nextAt || ((nextRankGlickoCutoff!-tlData.glicko!)/glickoForWin < 0.5 && ((tlData.rank != "x" && tlData.rank != "z") || tlData.percentileRank != "x"))) ? t.promotionOnNextWin : t.numOfVictories(wins: f2.format((nextRankGlickoCutoff!-tlData.glicko!)/glickoForWin)), style: TextStyle(color: (tlData.standing < tlData.nextAt || ((nextRankGlickoCutoff!-tlData.glicko!)/glickoForWin < 0.5 && tlData.percentileRank != "x")) ? Colors.greenAccent : null))
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
|
Loading…
Reference in New Issue