|
@ -42,3 +42,6 @@ app.*.map.json
|
||||||
/android/app/debug
|
/android/app/debug
|
||||||
/android/app/profile
|
/android/app/profile
|
||||||
/android/app/release
|
/android/app/release
|
||||||
|
|
||||||
|
# My shit
|
||||||
|
lib/views/tl_match_view web.dart
|
||||||
|
|
|
@ -175,6 +175,7 @@ class TetrioService extends DB {
|
||||||
developer.log("szyDownload: Replay downloaded", name: "services/tetrio_crud", error: response.statusCode);
|
developer.log("szyDownload: Replay downloaded", name: "services/tetrio_crud", error: response.statusCode);
|
||||||
_replaysCache[replayID] = [response.body, response.bodyBytes]; // Puts results into the cache
|
_replaysCache[replayID] = [response.body, response.bodyBytes]; // Puts results into the cache
|
||||||
return [response.body, response.bodyBytes];
|
return [response.body, response.bodyBytes];
|
||||||
|
// if not 200 - throw a unique for each code exception
|
||||||
case 404:
|
case 404:
|
||||||
throw SzyNotFound();
|
throw SzyNotFound();
|
||||||
case 403:
|
case 403:
|
||||||
|
@ -192,12 +193,14 @@ class TetrioService extends DB {
|
||||||
developer.log("szyDownload: Failed to download a replay", name: "services/tetrio_crud", error: response.statusCode);
|
developer.log("szyDownload: Failed to download a replay", 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) {
|
} on http.ClientException catch (e, s) { // If local http client fails
|
||||||
developer.log("$e, $s");
|
developer.log("$e, $s");
|
||||||
throw http.ClientException(e.message, e.uri);
|
throw http.ClientException(e.message, e.uri); // just assuming, that our end user don't have acess to the internet
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Saves replay with given [replayID] to Download or Documents directory as [replayID].ttrm. Throws an exception,
|
||||||
|
/// if file with name [replayID].ttrm exist, if it fails to get replay or unable to save replay
|
||||||
Future<String> saveReplay(String replayID) async {
|
Future<String> saveReplay(String replayID) async {
|
||||||
var downloadPath = await getDownloadsDirectory();
|
var downloadPath = await getDownloadsDirectory();
|
||||||
downloadPath ??= Platform.isAndroid ? Directory("/storage/emulated/0/Download") : await getApplicationDocumentsDirectory();
|
downloadPath ??= Platform.isAndroid ? Directory("/storage/emulated/0/Download") : await getApplicationDocumentsDirectory();
|
||||||
|
@ -208,49 +211,58 @@ class TetrioService extends DB {
|
||||||
return replayFile.path;
|
return replayFile.path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets replay with given [replayID] and returns some stats about it. If [isAvailable] is false
|
||||||
|
/// or unable to get replay, it will throw an exception
|
||||||
Future<ReplayData> analyzeReplay(String replayID, bool isAvailable) async{
|
Future<ReplayData> analyzeReplay(String replayID, bool isAvailable) async{
|
||||||
|
// trying retirieve existing stats from DB first
|
||||||
await ensureDbIsOpen();
|
await ensureDbIsOpen();
|
||||||
final db = getDatabaseOrThrow();
|
final db = getDatabaseOrThrow();
|
||||||
final results = await db.query(tetrioTLReplayStatsTable, where: '$idCol = ?', whereArgs: [replayID]);
|
final results = await db.query(tetrioTLReplayStatsTable, where: '$idCol = ?', whereArgs: [replayID]);
|
||||||
if (results.isNotEmpty) return ReplayData.fromJson(jsonDecode(results.first["data"].toString()));
|
if (results.isNotEmpty) return ReplayData.fromJson(jsonDecode(results.first["data"].toString())); // if success
|
||||||
if (!isAvailable) throw ReplayNotAvalable();
|
if (!isAvailable) throw ReplayNotAvalable(); // if replay too old
|
||||||
Map<String, dynamic> toAnalyze = jsonDecode((await szyGetReplay(replayID))[0]);
|
|
||||||
|
// otherwise, actually going to download a replay and analyze it
|
||||||
|
String replay = (await szyGetReplay(replayID))[0];
|
||||||
|
Map<String, dynamic> toAnalyze = jsonDecode(replay);
|
||||||
ReplayData data = ReplayData.fromJson(toAnalyze);
|
ReplayData data = ReplayData.fromJson(toAnalyze);
|
||||||
saveReplayStats(data);
|
saveReplayStats(data); // saving to DB for later
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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.
|
||||||
Future<double?> fetchTopTR(String id) async {
|
Future<double?> fetchTopTR(String id) async {
|
||||||
try{
|
try{ // read from cache
|
||||||
var cached = _topTRcache.entries.firstWhere((element) => element.value.keys.first == id);
|
var cached = _topTRcache.entries.firstWhere((element) => element.value.keys.first == id);
|
||||||
if (DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true).isAfter(DateTime.now())){
|
if (DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true).isAfter(DateTime.now())){ // if not expired
|
||||||
developer.log("fetchTopTR: Top TR retrieved from cache, that expires ${DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true)}", name: "services/tetrio_crud");
|
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;
|
return cached.value.values.first;
|
||||||
}else{
|
}else{ // if cache expired
|
||||||
_topTRcache.remove(cached.key);
|
_topTRcache.remove(cached.key);
|
||||||
developer.log("fetchTopTR: Top TR expired (${DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true)})", name: "services/tetrio_crud");
|
developer.log("fetchTopTR: Top TR expired (${DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true)})", name: "services/tetrio_crud");
|
||||||
}
|
}
|
||||||
}catch(e){
|
}catch(e){ // actually going to obtain
|
||||||
developer.log("fetchTopTR: Trying to retrieve Top TR", name: "services/tetrio_crud");
|
developer.log("fetchTopTR: Trying to retrieve Top TR", name: "services/tetrio_crud");
|
||||||
}
|
}
|
||||||
|
|
||||||
Uri url;
|
Uri url;
|
||||||
if (kIsWeb) {
|
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": "PeakTR", "user": id});
|
url = Uri.https('ts.dan63.by', 'oskware_bridge.php', {"endpoint": "PeakTR", "user": id});
|
||||||
} else {
|
} else { // Actually going to hit p1nkl0bst3r api
|
||||||
url = Uri.https('api.p1nkl0bst3r.xyz', 'toptr/$id');
|
url = Uri.https('api.p1nkl0bst3r.xyz', 'toptr/$id');
|
||||||
}
|
}
|
||||||
try{
|
try{
|
||||||
final response = await client.get(url);
|
final response = await client.get(url);
|
||||||
|
|
||||||
switch (response.statusCode) {
|
switch (response.statusCode) {
|
||||||
case 200:
|
case 200: // ok - return the value
|
||||||
_topTRcache[(DateTime.now().millisecondsSinceEpoch + 300000).toString()] = {id: double.tryParse(response.body)};
|
_topTRcache[(DateTime.now().millisecondsSinceEpoch + 300000).toString()] = {id: double.tryParse(response.body)};
|
||||||
return double.tryParse(response.body);
|
return double.tryParse(response.body);
|
||||||
case 404:
|
case 404: // not found - return 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};
|
_topTRcache[(DateTime.now().millisecondsSinceEpoch + 300000).toString()] = {id: null};
|
||||||
return null;
|
return null;
|
||||||
|
// if not 200 or 404 - throw a unique for each code exception
|
||||||
case 403:
|
case 403:
|
||||||
throw P1nkl0bst3rForbidden();
|
throw P1nkl0bst3rForbidden();
|
||||||
case 429:
|
case 429:
|
||||||
|
@ -266,12 +278,17 @@ class TetrioService extends DB {
|
||||||
developer.log("fetchTopTR: Failed to fetch top TR", name: "services/tetrio_crud", error: response.statusCode);
|
developer.log("fetchTopTR: Failed to fetch top TR", 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) {
|
} on http.ClientException catch (e, s) { // If local http client fails
|
||||||
developer.log("$e, $s");
|
developer.log("$e, $s");
|
||||||
throw http.ClientException(e.message, e.uri);
|
throw http.ClientException(e.message, e.uri); // just assuming, that our end user don't have acess to the internet
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
|
||||||
|
/// Retrieves Tetra League history from p1nkl0bst3r api for a player with given [id]. Returns a list of states
|
||||||
|
/// (state = instance of [TetrioPlayer] at some point of time). Can throw an exception if fails to retrieve data.
|
||||||
Future<List<TetrioPlayer>> fetchAndsaveTLHistory(String id) async {
|
Future<List<TetrioPlayer>> fetchAndsaveTLHistory(String id) async {
|
||||||
Uri url;
|
Uri url;
|
||||||
if (kIsWeb) {
|
if (kIsWeb) {
|
||||||
|
@ -284,10 +301,12 @@ class TetrioService extends DB {
|
||||||
|
|
||||||
switch (response.statusCode) {
|
switch (response.statusCode) {
|
||||||
case 200:
|
case 200:
|
||||||
|
// that one api returns csv instead of json
|
||||||
List<List<dynamic>> csv = const CsvToListConverter().convert(response.body)..removeAt(0);
|
List<List<dynamic>> csv = const CsvToListConverter().convert(response.body)..removeAt(0);
|
||||||
List<TetrioPlayer> history = [];
|
List<TetrioPlayer> history = [];
|
||||||
|
// doesn't return nickname, need to retrieve it separately
|
||||||
String nick = await getNicknameByID(id);
|
String nick = await getNicknameByID(id);
|
||||||
for (List<dynamic> entry in csv){
|
for (List<dynamic> entry in csv){ // each entry is one state
|
||||||
TetrioPlayer state = TetrioPlayer(
|
TetrioPlayer state = TetrioPlayer(
|
||||||
userId: id,
|
userId: id,
|
||||||
username: nick,
|
username: nick,
|
||||||
|
@ -302,27 +321,51 @@ class TetrioService extends DB {
|
||||||
supporterTier: 0,
|
supporterTier: 0,
|
||||||
verified: false,
|
verified: false,
|
||||||
connections: null,
|
connections: null,
|
||||||
tlSeason1: TetraLeagueAlpha(timestamp: DateTime.parse(entry[9]), apm: entry[6] != '' ? entry[6] : null, pps: entry[7] != '' ? entry[7] : null, vs: entry[8] != '' ? entry[8] : null, glicko: entry[4], rd: noTrRd, gamesPlayed: entry[1], gamesWon: entry[2], bestRank: "z", decaying: false, rating: entry[3], rank: entry[5], percentileRank: entry[5], percentile: rankCutoffs[entry[5]]!, standing: -1, standingLocal: -1, nextAt: -1, prevAt: -1),
|
tlSeason1: TetraLeagueAlpha(
|
||||||
|
timestamp: DateTime.parse(entry[9]),
|
||||||
|
apm: entry[6] != '' ? entry[6] : null,
|
||||||
|
pps: entry[7] != '' ? entry[7] : null,
|
||||||
|
vs: entry[8] != '' ? entry[8] : null,
|
||||||
|
glicko: entry[4],
|
||||||
|
rd: noTrRd,
|
||||||
|
gamesPlayed: entry[1],
|
||||||
|
gamesWon: entry[2],
|
||||||
|
bestRank: "z",
|
||||||
|
decaying: false,
|
||||||
|
rating: entry[3],
|
||||||
|
rank: entry[5],
|
||||||
|
percentileRank: entry[5],
|
||||||
|
percentile: rankCutoffs[entry[5]]!,
|
||||||
|
standing: -1,
|
||||||
|
standingLocal: -1,
|
||||||
|
nextAt: -1,
|
||||||
|
prevAt: -1
|
||||||
|
),
|
||||||
sprint: [],
|
sprint: [],
|
||||||
blitz: []
|
blitz: []
|
||||||
);
|
);
|
||||||
history.add(state);
|
history.add(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// trying to dump it to local DB
|
||||||
await ensureDbIsOpen();
|
await ensureDbIsOpen();
|
||||||
final db = getDatabaseOrThrow();
|
final db = getDatabaseOrThrow();
|
||||||
late List<TetrioPlayer> states;
|
late List<TetrioPlayer> states;
|
||||||
try{
|
try{
|
||||||
|
// checking if tetra stats aware about that player TODO: is it necessary?
|
||||||
states = _players[id]!;
|
states = _players[id]!;
|
||||||
}catch(e){
|
}catch(e){
|
||||||
|
// if somehow not - create it
|
||||||
var player = await fetchPlayer(id);
|
var player = await fetchPlayer(id);
|
||||||
await createPlayer(player);
|
await createPlayer(player);
|
||||||
states = _players[id]!;
|
states = _players[id]!;
|
||||||
}
|
}
|
||||||
states.insertAll(0, history.reversed);
|
states.insertAll(0, history.reversed);
|
||||||
final Map<String, dynamic> statesJson = {};
|
final Map<String, dynamic> statesJson = {};
|
||||||
for (var e in states) {
|
for (var e in states) { // making one big json out of this list
|
||||||
statesJson.addEntries({(e.state.millisecondsSinceEpoch ~/ 1000).toString(): e.toJson()}.entries);
|
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]);
|
await db.update(tetrioUsersTable, {idCol: id, nickCol: nick, statesCol: jsonEncode(statesJson)}, where: '$idCol = ?', whereArgs: [id]);
|
||||||
_tetrioStreamController.add(_players);
|
_tetrioStreamController.add(_players);
|
||||||
return history;
|
return history;
|
||||||
|
@ -350,6 +393,7 @@ class TetrioService extends DB {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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{
|
try{
|
||||||
var cached = _leaderboardsCache.entries.firstWhere((element) => element.value.type == "league");
|
var cached = _leaderboardsCache.entries.firstWhere((element) => element.value.type == "league");
|
||||||
|
@ -375,14 +419,14 @@ class TetrioService extends DB {
|
||||||
switch (response.statusCode) {
|
switch (response.statusCode) {
|
||||||
case 200:
|
case 200:
|
||||||
var rawJson = jsonDecode(response.body);
|
var rawJson = jsonDecode(response.body);
|
||||||
if (rawJson['success']) {
|
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;
|
||||||
return leaderboard;
|
return leaderboard;
|
||||||
} else {
|
} 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);
|
||||||
throw Exception("Failed to get leaderboard (problems on the tetr.io side)");
|
throw Exception("Failed to get leaderboard (problems on the tetr.io side)"); // will it be on tetr.io side?
|
||||||
}
|
}
|
||||||
case 403:
|
case 403:
|
||||||
throw TetrioForbidden();
|
throw TetrioForbidden();
|
||||||
|
@ -405,6 +449,7 @@ class TetrioService extends DB {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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<List<News>> fetchNews(String userID) async{
|
||||||
try{
|
try{
|
||||||
var cached = _newsCache.entries.firstWhere((element) => element.value[0].stream == "user_$userID");
|
var cached = _newsCache.entries.firstWhere((element) => element.value[0].stream == "user_$userID");
|
||||||
|
@ -431,10 +476,10 @@ class TetrioService extends DB {
|
||||||
switch (response.statusCode) {
|
switch (response.statusCode) {
|
||||||
case 200:
|
case 200:
|
||||||
var payload = jsonDecode(response.body);
|
var payload = jsonDecode(response.body);
|
||||||
if (payload['success']) {
|
if (payload['success']) { // if api confirmed that everything ok
|
||||||
List<News> news = [for (var entry in payload['data']['news']) News.fromJson(entry)];
|
List<News> news = [for (var entry in payload['data']['news']) News.fromJson(entry)];
|
||||||
developer.log("fetchNews: $userID news retrieved and cached", name: "services/tetrio_crud");
|
|
||||||
_newsCache[payload['cache']['cached_until'].toString()] = news;
|
_newsCache[payload['cache']['cached_until'].toString()] = news;
|
||||||
|
developer.log("fetchNews: $userID news retrieved and cached", name: "services/tetrio_crud");
|
||||||
return news;
|
return news;
|
||||||
} else {
|
} else {
|
||||||
developer.log("fetchNews: User dosen't exist", name: "services/tetrio_crud", error: response.body);
|
developer.log("fetchNews: User dosen't exist", name: "services/tetrio_crud", error: response.body);
|
||||||
|
@ -461,18 +506,20 @@ class TetrioService extends DB {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<TetraLeagueAlphaStream> getTLStream(String userID) async {
|
/// Retrieves avaliable Tetra League matches from Tetra Channel api. Returns stream object (fake stream).
|
||||||
|
/// Throws an exception if fails to retrieve.
|
||||||
|
Future<TetraLeagueAlphaStream> fetchTLStream(String userID) async {
|
||||||
try{
|
try{
|
||||||
var cached = _tlStreamsCache.entries.firstWhere((element) => element.value.userId == userID);
|
var cached = _tlStreamsCache.entries.firstWhere((element) => element.value.userId == userID);
|
||||||
if (DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true).isAfter(DateTime.now())){
|
if (DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true).isAfter(DateTime.now())){
|
||||||
developer.log("getTLStream: Stream $userID retrieved from cache, that expires ${DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true)}", name: "services/tetrio_crud");
|
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;
|
return cached.value;
|
||||||
}else{
|
}else{
|
||||||
_tlStreamsCache.remove(cached.key);
|
_tlStreamsCache.remove(cached.key);
|
||||||
developer.log("getTLStream: Cached stream $userID expired (${DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true)})", name: "services/tetrio_crud");
|
developer.log("fetchTLStream: Cached stream $userID expired (${DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true)})", name: "services/tetrio_crud");
|
||||||
}
|
}
|
||||||
}catch(e){
|
}catch(e){
|
||||||
developer.log("getTLStream: Trying to retrieve stream $userID", name: "services/tetrio_crud");
|
developer.log("fetchTLStream: Trying to retrieve stream $userID", name: "services/tetrio_crud");
|
||||||
}
|
}
|
||||||
|
|
||||||
Uri url;
|
Uri url;
|
||||||
|
@ -488,11 +535,11 @@ 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);
|
||||||
developer.log("getTLStream: $userID stream retrieved and cached", name: "services/tetrio_crud");
|
|
||||||
_tlStreamsCache[jsonDecode(response.body)['cache']['cached_until'].toString()] = stream;
|
_tlStreamsCache[jsonDecode(response.body)['cache']['cached_until'].toString()] = stream;
|
||||||
|
developer.log("fetchTLStream: $userID stream retrieved and cached", name: "services/tetrio_crud");
|
||||||
return stream;
|
return stream;
|
||||||
} else {
|
} else {
|
||||||
developer.log("getTLStream User dosen't exist", name: "services/tetrio_crud", error: response.body);
|
developer.log("fetchTLStream User dosen't exist", name: "services/tetrio_crud", error: response.body);
|
||||||
throw TetrioPlayerNotExist();
|
throw TetrioPlayerNotExist();
|
||||||
}
|
}
|
||||||
case 403:
|
case 403:
|
||||||
|
@ -507,7 +554,7 @@ class TetrioService extends DB {
|
||||||
case 504:
|
case 504:
|
||||||
throw TetrioInternalProblem();
|
throw TetrioInternalProblem();
|
||||||
default:
|
default:
|
||||||
developer.log("getTLStream Failed to fetch stream", name: "services/tetrio_crud", error: response.statusCode);
|
developer.log("fetchTLStream Failed to fetch stream", 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) {
|
} on http.ClientException catch (e, s) {
|
||||||
|
@ -516,16 +563,26 @@ class TetrioService extends DB {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Saves Tetra League Matches from [stream] to the local DB.
|
||||||
Future<void> saveTLMatchesFromStream(TetraLeagueAlphaStream stream) async {
|
Future<void> saveTLMatchesFromStream(TetraLeagueAlphaStream stream) async {
|
||||||
await ensureDbIsOpen();
|
await ensureDbIsOpen();
|
||||||
final db = getDatabaseOrThrow();
|
final db = getDatabaseOrThrow();
|
||||||
for (TetraLeagueAlphaRecord match in stream.records) {
|
for (TetraLeagueAlphaRecord match in stream.records) { // putting then one by one
|
||||||
final results = await db.query(tetraLeagueMatchesTable, where: '$replayID = ?', whereArgs: [match.replayId]);
|
final results = await db.query(tetraLeagueMatchesTable, where: '$replayID = ?', whereArgs: [match.replayId]);
|
||||||
if (results.isNotEmpty) continue;
|
if (results.isNotEmpty) continue; // if match alreay exist - skip
|
||||||
db.insert(tetraLeagueMatchesTable, {idCol: match.ownId, replayID: match.replayId, timestamp: match.timestamp.toString(), player1id: match.endContext.first.userId, player2id: match.endContext.last.userId, endContext1: jsonEncode(match.endContext.first.toJson()), endContext2: jsonEncode(match.endContext.last.toJson())});
|
db.insert(tetraLeagueMatchesTable, {
|
||||||
|
idCol: match.ownId,
|
||||||
|
replayID: match.replayId,
|
||||||
|
timestamp: match.timestamp.toString(),
|
||||||
|
player1id: match.endContext.first.userId,
|
||||||
|
player2id: match.endContext.last.userId,
|
||||||
|
endContext1: jsonEncode(match.endContext.first.toJson()),
|
||||||
|
endContext2: jsonEncode(match.endContext.last.toJson())
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Deletes duplicate entries of Tetra League matches from local DB.
|
||||||
Future<void> removeDuplicatesFromTLMatches() async{
|
Future<void> removeDuplicatesFromTLMatches() async{
|
||||||
await ensureDbIsOpen();
|
await ensureDbIsOpen();
|
||||||
final db = getDatabaseOrThrow();
|
final db = getDatabaseOrThrow();
|
||||||
|
@ -548,17 +605,28 @@ class TetrioService extends DB {
|
||||||
""");
|
""");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets and returns a list of matches from local DB for a given [playerID].
|
||||||
Future<List<TetraLeagueAlphaRecord>> getTLMatchesbyPlayerID(String playerID) async {
|
Future<List<TetraLeagueAlphaRecord>> getTLMatchesbyPlayerID(String playerID) async {
|
||||||
await ensureDbIsOpen();
|
await ensureDbIsOpen();
|
||||||
final db = getDatabaseOrThrow();
|
final db = getDatabaseOrThrow();
|
||||||
List<TetraLeagueAlphaRecord> matches = [];
|
List<TetraLeagueAlphaRecord> matches = [];
|
||||||
final results = await db.query(tetraLeagueMatchesTable, where: '($player1id = ?) OR ($player2id = ?)', whereArgs: [playerID, playerID]);
|
final results = await db.query(tetraLeagueMatchesTable, where: '($player1id = ?) OR ($player2id = ?)', whereArgs: [playerID, playerID]);
|
||||||
for (var match in results){
|
for (var match in results){
|
||||||
matches.add(TetraLeagueAlphaRecord(ownId: match[idCol].toString(), replayId: match[replayID].toString(), timestamp: DateTime.parse(match[timestamp].toString()), endContext:[EndContextMulti.fromJson(jsonDecode(match[endContext1].toString())), EndContextMulti.fromJson(jsonDecode(match[endContext2].toString()))], replayAvalable: false));
|
matches.add(TetraLeagueAlphaRecord(
|
||||||
|
ownId: match[idCol].toString(),
|
||||||
|
replayId: match[replayID].toString(),
|
||||||
|
timestamp: DateTime.parse(match[timestamp].toString()),
|
||||||
|
endContext:[
|
||||||
|
EndContextMulti.fromJson(jsonDecode(match[endContext1].toString())),
|
||||||
|
EndContextMulti.fromJson(jsonDecode(match[endContext2].toString()))
|
||||||
|
],
|
||||||
|
replayAvalable: false
|
||||||
|
));
|
||||||
}
|
}
|
||||||
return matches;
|
return matches;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Deletes match and stats of that match with given [matchID] from local DB. Throws an exception if fails.
|
||||||
Future<void> deleteTLMatch(String matchID) async {
|
Future<void> deleteTLMatch(String matchID) async {
|
||||||
await ensureDbIsOpen();
|
await ensureDbIsOpen();
|
||||||
final db = getDatabaseOrThrow();
|
final db = getDatabaseOrThrow();
|
||||||
|
@ -570,6 +638,8 @@ 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 [playerID] from Tetra Channel api. Returns Map, which contains user id (`user`),
|
||||||
|
/// Blitz (`blitz`) and 40 Lines (`sprint`) record objects and Zen object (`zen`). Throws an exception if fails to retrieve.
|
||||||
Future<Map<String, dynamic>> fetchRecords(String userID) async {
|
Future<Map<String, dynamic>> fetchRecords(String userID) async {
|
||||||
try{
|
try{
|
||||||
var cached = _recordsCache.entries.firstWhere((element) => element.value['user'] == userID);
|
var cached = _recordsCache.entries.firstWhere((element) => element.value['user'] == userID);
|
||||||
|
@ -598,15 +668,15 @@ class TetrioService extends DB {
|
||||||
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;
|
||||||
var blitz = jsonRecords['data']['records']['blitz']['record'] != null
|
var blitz = jsonRecords['data']['records']['blitz']['record'] != null
|
||||||
? [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;
|
||||||
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};
|
Map<String, dynamic> map = {"user": userID.toLowerCase().trim(), "sprint": sprint, "blitz": blitz, "zen": zen};
|
||||||
developer.log("fetchRecords: $userID records retrieved and cached", name: "services/tetrio_crud");
|
|
||||||
_recordsCache[jsonDecode(response.body)['cache']['cached_until'].toString()] = map;
|
_recordsCache[jsonDecode(response.body)['cache']['cached_until'].toString()] = map;
|
||||||
|
developer.log("fetchRecords: $userID records retrieved and cached", name: "services/tetrio_crud");
|
||||||
return map;
|
return map;
|
||||||
} 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);
|
||||||
|
@ -633,13 +703,18 @@ class TetrioService extends DB {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates an entry in local DB for [tetrioPlayer]. Throws an exception if that player already here.
|
||||||
Future<void> createPlayer(TetrioPlayer tetrioPlayer) async {
|
Future<void> createPlayer(TetrioPlayer tetrioPlayer) async {
|
||||||
await ensureDbIsOpen();
|
await ensureDbIsOpen();
|
||||||
final db = getDatabaseOrThrow();
|
final db = getDatabaseOrThrow();
|
||||||
|
|
||||||
|
// checking if its already here
|
||||||
final results = await db.query(tetrioUsersTable, limit: 1, where: '$idCol = ?', whereArgs: [tetrioPlayer.userId.toLowerCase()]);
|
final results = await db.query(tetrioUsersTable, limit: 1, where: '$idCol = ?', whereArgs: [tetrioPlayer.userId.toLowerCase()]);
|
||||||
if (results.isNotEmpty) {
|
if (results.isNotEmpty) {
|
||||||
throw TetrioPlayerAlreadyExist();
|
throw TetrioPlayerAlreadyExist();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// converting to json and store
|
||||||
final Map<String, dynamic> statesJson = {(tetrioPlayer.state.millisecondsSinceEpoch ~/ 1000).toString(): tetrioPlayer.toJson()};
|
final Map<String, dynamic> statesJson = {(tetrioPlayer.state.millisecondsSinceEpoch ~/ 1000).toString(): tetrioPlayer.toJson()};
|
||||||
db.insert(tetrioUsersTable, {idCol: tetrioPlayer.userId, nickCol: tetrioPlayer.username, statesCol: jsonEncode(statesJson)});
|
db.insert(tetrioUsersTable, {idCol: tetrioPlayer.userId, nickCol: tetrioPlayer.username, statesCol: jsonEncode(statesJson)});
|
||||||
_players.addEntries({
|
_players.addEntries({
|
||||||
|
@ -648,6 +723,7 @@ class TetrioService extends DB {
|
||||||
_tetrioStreamController.add(_players);
|
_tetrioStreamController.add(_players);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Adds user id of [tetrioPlayer] to the [tetrioUsersToTrackTable] of database.
|
||||||
Future<void> addPlayerToTrack(TetrioPlayer tetrioPlayer) async {
|
Future<void> addPlayerToTrack(TetrioPlayer tetrioPlayer) async {
|
||||||
await ensureDbIsOpen();
|
await ensureDbIsOpen();
|
||||||
final db = getDatabaseOrThrow();
|
final db = getDatabaseOrThrow();
|
||||||
|
@ -658,6 +734,7 @@ class TetrioService extends DB {
|
||||||
db.insert(tetrioUsersToTrackTable, {idCol: tetrioPlayer.userId});
|
db.insert(tetrioUsersToTrackTable, {idCol: tetrioPlayer.userId});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns bool, which tells whether is given [id] is in [tetrioUsersToTrackTable].
|
||||||
Future<bool> isPlayerTracking(String id) async {
|
Future<bool> isPlayerTracking(String id) async {
|
||||||
await ensureDbIsOpen();
|
await ensureDbIsOpen();
|
||||||
final db = getDatabaseOrThrow();
|
final db = getDatabaseOrThrow();
|
||||||
|
@ -665,6 +742,7 @@ class TetrioService extends DB {
|
||||||
return results.isNotEmpty;
|
return results.isNotEmpty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns Iterable with user ids of players who is tracked.
|
||||||
Future<Iterable<String>> getAllPlayerToTrack() async {
|
Future<Iterable<String>> getAllPlayerToTrack() async {
|
||||||
await ensureDbIsOpen();
|
await ensureDbIsOpen();
|
||||||
final db = getDatabaseOrThrow();
|
final db = getDatabaseOrThrow();
|
||||||
|
@ -673,6 +751,7 @@ class TetrioService extends DB {
|
||||||
return players.map((noteRow) => noteRow["id"].toString());
|
return players.map((noteRow) => noteRow["id"].toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Removes user with given [id] from the [tetrioUsersToTrackTable] of database.
|
||||||
Future<void> deletePlayerToTrack(String id) async {
|
Future<void> deletePlayerToTrack(String id) async {
|
||||||
await ensureDbIsOpen();
|
await ensureDbIsOpen();
|
||||||
final db = getDatabaseOrThrow();
|
final db = getDatabaseOrThrow();
|
||||||
|
@ -685,56 +764,71 @@ class TetrioService extends DB {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Saves state (which is [tetrioPlayer]) to the local database.
|
||||||
Future<void> storeState(TetrioPlayer tetrioPlayer) async {
|
Future<void> storeState(TetrioPlayer tetrioPlayer) async {
|
||||||
await ensureDbIsOpen();
|
await ensureDbIsOpen();
|
||||||
final db = getDatabaseOrThrow();
|
final db = getDatabaseOrThrow();
|
||||||
late List<TetrioPlayer> states;
|
late List<TetrioPlayer> states;
|
||||||
try {
|
try { // retrieveing previous states
|
||||||
states = _players[tetrioPlayer.userId]!;
|
states = _players[tetrioPlayer.userId]!;
|
||||||
} catch (e) {
|
} catch (e) { // nothing found - player not exist - create them
|
||||||
await createPlayer(tetrioPlayer);
|
await createPlayer(tetrioPlayer);
|
||||||
states = await getPlayer(tetrioPlayer.userId);
|
states = await getPlayer(tetrioPlayer.userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// we not going to add state, that is same, as the previous
|
||||||
bool test = _players[tetrioPlayer.userId]!.last.isSameState(tetrioPlayer);
|
bool test = _players[tetrioPlayer.userId]!.last.isSameState(tetrioPlayer);
|
||||||
if (test == false) states.add(tetrioPlayer);
|
if (test == false) states.add(tetrioPlayer);
|
||||||
|
|
||||||
|
// Making map of the states
|
||||||
final Map<String, dynamic> statesJson = {};
|
final Map<String, dynamic> statesJson = {};
|
||||||
for (var e in states) {
|
for (var e in states) {
|
||||||
|
// Saving in format: {"unix_seconds": json_of_state}
|
||||||
statesJson.addEntries({(e.state.millisecondsSinceEpoch ~/ 1000).toString(): e.toJson()}.entries);
|
statesJson.addEntries({(e.state.millisecondsSinceEpoch ~/ 1000).toString(): e.toJson()}.entries);
|
||||||
}
|
}
|
||||||
|
// Rewrite our database
|
||||||
await db.update(tetrioUsersTable, {idCol: tetrioPlayer.userId, nickCol: tetrioPlayer.username, statesCol: jsonEncode(statesJson)},
|
await db.update(tetrioUsersTable, {idCol: tetrioPlayer.userId, nickCol: tetrioPlayer.username, statesCol: jsonEncode(statesJson)},
|
||||||
where: '$idCol = ?', whereArgs: [tetrioPlayer.userId]);
|
where: '$idCol = ?', whereArgs: [tetrioPlayer.userId]);
|
||||||
_players[tetrioPlayer.userId]!.add(tetrioPlayer);
|
_players[tetrioPlayer.userId]!.add(tetrioPlayer);
|
||||||
_tetrioStreamController.add(_players);
|
_tetrioStreamController.add(_players);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Remove state (which is [tetrioPlayer]) from the local database
|
||||||
Future<void> deleteState(TetrioPlayer tetrioPlayer) async {
|
Future<void> deleteState(TetrioPlayer tetrioPlayer) async {
|
||||||
await ensureDbIsOpen();
|
await ensureDbIsOpen();
|
||||||
final db = getDatabaseOrThrow();
|
final db = getDatabaseOrThrow();
|
||||||
late List<TetrioPlayer> states;
|
late List<TetrioPlayer> states;
|
||||||
states = await getPlayer(tetrioPlayer.userId);
|
// removing state from map that contain every state of each user
|
||||||
_players[tetrioPlayer.userId]!.removeWhere((element) => element.state == tetrioPlayer.state);
|
_players[tetrioPlayer.userId]!.removeWhere((element) => element.state == tetrioPlayer.state);
|
||||||
states = _players[tetrioPlayer.userId]!;
|
states = _players[tetrioPlayer.userId]!;
|
||||||
|
|
||||||
|
// Making map of the states (without deleted one)
|
||||||
final Map<String, dynamic> statesJson = {};
|
final Map<String, dynamic> statesJson = {};
|
||||||
for (var e in states) {
|
for (var e in states) {
|
||||||
statesJson.addEntries({e.state.millisecondsSinceEpoch.toString(): e.toJson()}.entries);
|
statesJson.addEntries({(e.state.millisecondsSinceEpoch ~/ 1000).toString(): e.toJson()}.entries);
|
||||||
}
|
}
|
||||||
|
// Rewriting database entry with new json
|
||||||
await db.update(tetrioUsersTable, {idCol: tetrioPlayer.userId, nickCol: tetrioPlayer.username, statesCol: jsonEncode(statesJson)},
|
await db.update(tetrioUsersTable, {idCol: tetrioPlayer.userId, nickCol: tetrioPlayer.username, statesCol: jsonEncode(statesJson)},
|
||||||
where: '$idCol = ?', whereArgs: [tetrioPlayer.userId]);
|
where: '$idCol = ?', whereArgs: [tetrioPlayer.userId]);
|
||||||
_players[tetrioPlayer.userId]!.add(tetrioPlayer);
|
_players[tetrioPlayer.userId]!.add(tetrioPlayer);
|
||||||
_tetrioStreamController.add(_players);
|
_tetrioStreamController.add(_players);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns list of all states of player with given [id] from database. Can return empty list if player
|
||||||
|
/// was not found.
|
||||||
Future<List<TetrioPlayer>> getPlayer(String id) async {
|
Future<List<TetrioPlayer>> getPlayer(String id) async {
|
||||||
await ensureDbIsOpen();
|
await ensureDbIsOpen();
|
||||||
final db = getDatabaseOrThrow();
|
final db = getDatabaseOrThrow();
|
||||||
List<TetrioPlayer> states = [];
|
List<TetrioPlayer> states = [];
|
||||||
final results = await db.query(tetrioUsersTable, limit: 1, where: '$idCol = ?', whereArgs: [id.toLowerCase()]);
|
final results = await db.query(tetrioUsersTable, limit: 1, where: '$idCol = ?', whereArgs: [id.toLowerCase()]);
|
||||||
if (results.isEmpty) {
|
if (results.isEmpty) {
|
||||||
return states;
|
return states; // it empty
|
||||||
} else {
|
} else {
|
||||||
dynamic rawStates = results.first['jsonStates'] as String;
|
dynamic rawStates = results.first['jsonStates'] as String;
|
||||||
rawStates = json.decode(rawStates);
|
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)));
|
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.removeWhere((key, value) => key == id);
|
||||||
_players.addEntries({states.last.userId: states}.entries);
|
_players.addEntries({states.last.userId: states}.entries);
|
||||||
_tetrioStreamController.add(_players);
|
_tetrioStreamController.add(_players);
|
||||||
|
@ -742,6 +836,8 @@ class TetrioService extends DB {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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.
|
||||||
Future<TetrioPlayer> fetchPlayer(String user, {bool isItDiscordID = false}) async {
|
Future<TetrioPlayer> fetchPlayer(String user, {bool isItDiscordID = false}) async {
|
||||||
try{
|
try{
|
||||||
var cached = _playersCache.entries.firstWhere((element) => element.value.userId == user || element.value.username == user);
|
var cached = _playersCache.entries.firstWhere((element) => element.value.userId == user || element.value.username == user);
|
||||||
|
@ -757,6 +853,7 @@ class TetrioService extends DB {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isItDiscordID){
|
if (isItDiscordID){
|
||||||
|
// trying to find player with given discord id
|
||||||
Uri dUrl;
|
Uri dUrl;
|
||||||
if (kIsWeb) {
|
if (kIsWeb) {
|
||||||
dUrl = Uri.https('ts.dan63.by', 'oskware_bridge.php', {"endpoint": "tetrioUserByDiscordID", "user": user.toLowerCase().trim()});
|
dUrl = Uri.https('ts.dan63.by', 'oskware_bridge.php', {"endpoint": "tetrioUserByDiscordID", "user": user.toLowerCase().trim()});
|
||||||
|
@ -770,12 +867,14 @@ class TetrioService extends DB {
|
||||||
case 200:
|
case 200:
|
||||||
var json = jsonDecode(response.body);
|
var json = jsonDecode(response.body);
|
||||||
if (json['success'] && json['data'] != null) {
|
if (json['success'] && json['data'] != null) {
|
||||||
|
// success - rewrite user with tetrio user id and going to obtain data about him
|
||||||
user = json['data']['user']['_id'];
|
user = json['data']['user']['_id'];
|
||||||
} else {
|
} else { // fail - throw an exception
|
||||||
developer.log("fetchPlayer User dosen't exist", name: "services/tetrio_crud", error: response.body);
|
developer.log("fetchPlayer User dosen't exist", name: "services/tetrio_crud", error: response.body);
|
||||||
throw TetrioPlayerNotExist();
|
throw TetrioPlayerNotExist();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
// more exceptions to god of exceptions
|
||||||
case 403:
|
case 403:
|
||||||
throw TetrioForbidden();
|
throw TetrioForbidden();
|
||||||
case 429:
|
case 429:
|
||||||
|
@ -797,6 +896,7 @@ class TetrioService extends DB {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// finally going to obtain
|
||||||
Uri url;
|
Uri url;
|
||||||
if (kIsWeb) {
|
if (kIsWeb) {
|
||||||
url = Uri.https('ts.dan63.by', 'oskware_bridge.php', {"endpoint": "tetrioUser", "user": user.toLowerCase().trim()});
|
url = Uri.https('ts.dan63.by', 'oskware_bridge.php', {"endpoint": "tetrioUser", "user": user.toLowerCase().trim()});
|
||||||
|
@ -810,9 +910,10 @@ class TetrioService extends DB {
|
||||||
case 200:
|
case 200:
|
||||||
var json = jsonDecode(response.body);
|
var json = jsonDecode(response.body);
|
||||||
if (json['success']) {
|
if (json['success']) {
|
||||||
|
// 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']);
|
||||||
developer.log("fetchPlayer: $user retrieved and cached", name: "services/tetrio_crud");
|
|
||||||
_playersCache[jsonDecode(response.body)['cache']['cached_until'].toString()] = player;
|
_playersCache[jsonDecode(response.body)['cache']['cached_until'].toString()] = player;
|
||||||
|
developer.log("fetchPlayer: $user retrieved and cached", name: "services/tetrio_crud");
|
||||||
return player;
|
return player;
|
||||||
} else {
|
} else {
|
||||||
developer.log("fetchPlayer User dosen't exist", name: "services/tetrio_crud", error: response.body);
|
developer.log("fetchPlayer User dosen't exist", name: "services/tetrio_crud", error: response.body);
|
||||||
|
@ -839,6 +940,8 @@ class TetrioService extends DB {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Basucally, retrieves whole [tetrioUsersTable] and do stupud things idk
|
||||||
|
/// Returns god knows what. TODO: Rewrite this shit
|
||||||
Future<Iterable<Map<String, List<TetrioPlayer>>>> getAllPlayers() async {
|
Future<Iterable<Map<String, List<TetrioPlayer>>>> getAllPlayers() async {
|
||||||
await ensureDbIsOpen();
|
await ensureDbIsOpen();
|
||||||
final db = getDatabaseOrThrow();
|
final db = getDatabaseOrThrow();
|
||||||
|
|
|
@ -156,7 +156,7 @@ class _MainState extends State<MainView> with TickerProviderStateMixin {
|
||||||
late List<News> news;
|
late List<News> news;
|
||||||
late double? topTR;
|
late double? topTR;
|
||||||
requests = await Future.wait([ // all at once
|
requests = await Future.wait([ // all at once
|
||||||
teto.getTLStream(_searchFor),
|
teto.fetchTLStream(_searchFor),
|
||||||
teto.fetchRecords(_searchFor),
|
teto.fetchRecords(_searchFor),
|
||||||
teto.fetchNews(_searchFor),
|
teto.fetchNews(_searchFor),
|
||||||
if (me.tlSeason1.gamesPlayed > 9) teto.fetchTopTR(_searchFor) // can retrieve this only if player has TR
|
if (me.tlSeason1.gamesPlayed > 9) teto.fetchTopTR(_searchFor) // can retrieve this only if player has TR
|
||||||
|
@ -173,10 +173,13 @@ class _MainState extends State<MainView> with TickerProviderStateMixin {
|
||||||
TetraLeagueAlpha? compareWith;
|
TetraLeagueAlpha? compareWith;
|
||||||
Set<TetraLeagueAlpha> uniqueTL = {};
|
Set<TetraLeagueAlpha> uniqueTL = {};
|
||||||
tlMatches = tlStream.records;
|
tlMatches = tlStream.records;
|
||||||
|
var storedRecords = await teto.getTLMatchesbyPlayerID(me.userId); // get old matches
|
||||||
if (isTracking){ // if tracked - save data to local DB
|
if (isTracking){ // if tracked - save data to local DB
|
||||||
await teto.storeState(me);
|
await teto.storeState(me);
|
||||||
await teto.saveTLMatchesFromStream(tlStream);
|
await teto.saveTLMatchesFromStream(tlStream);
|
||||||
var storedRecords = await teto.getTLMatchesbyPlayerID(me.userId); // get old matches
|
}
|
||||||
|
|
||||||
|
// building list of TL matches
|
||||||
for (var match in storedRecords) {
|
for (var match in storedRecords) {
|
||||||
// add stored match to list only if it missing from retrived ones
|
// add stored match to list only if it missing from retrived ones
|
||||||
if (!tlMatches.contains(match)) tlMatches.add(match);
|
if (!tlMatches.contains(match)) tlMatches.add(match);
|
||||||
|
@ -186,8 +189,7 @@ class _MainState extends State<MainView> with TickerProviderStateMixin {
|
||||||
if(a.timestamp.isAtSameMomentAs(b.timestamp)) return 0;
|
if(a.timestamp.isAtSameMomentAs(b.timestamp)) return 0;
|
||||||
if(a.timestamp.isAfter(b.timestamp)) return -1;
|
if(a.timestamp.isAfter(b.timestamp)) return -1;
|
||||||
return 0;
|
return 0;
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
// Handling history
|
// Handling history
|
||||||
if(fetchHistory) await teto.fetchAndsaveTLHistory(_searchFor); // Retrieve if needed
|
if(fetchHistory) await teto.fetchAndsaveTLHistory(_searchFor); // Retrieve if needed
|
||||||
|
@ -309,8 +311,6 @@ class _MainState extends State<MainView> with TickerProviderStateMixin {
|
||||||
case ConnectionState.done:
|
case ConnectionState.done:
|
||||||
//bool bigScreen = MediaQuery.of(context).size.width > 1024;
|
//bool bigScreen = MediaQuery.of(context).size.width > 1024;
|
||||||
if (snapshot.hasData) {
|
if (snapshot.hasData) {
|
||||||
List<dynamic> sprintRuns = snapshot.data![1]['sprint'];
|
|
||||||
List<dynamic> blitzRuns = snapshot.data![1]['blitz'];
|
|
||||||
return RefreshIndicator(
|
return RefreshIndicator(
|
||||||
onRefresh: () {
|
onRefresh: () {
|
||||||
return Future(() => changePlayer(snapshot.data![0].userId));
|
return Future(() => changePlayer(snapshot.data![0].userId));
|
||||||
|
@ -356,8 +356,8 @@ class _MainState extends State<MainView> with TickerProviderStateMixin {
|
||||||
TLThingy(tl: snapshot.data![0].tlSeason1, userID: snapshot.data![0].userId, states: snapshot.data![2], topTR: snapshot.data![7], bot: snapshot.data![0].role == "bot", guest: snapshot.data![0].role == "anon"),
|
TLThingy(tl: snapshot.data![0].tlSeason1, userID: snapshot.data![0].userId, states: snapshot.data![2], topTR: snapshot.data![7], bot: snapshot.data![0].role == "bot", guest: snapshot.data![0].role == "anon"),
|
||||||
_TLRecords(userID: snapshot.data![0].userId, data: snapshot.data![3]),
|
_TLRecords(userID: snapshot.data![0].userId, data: snapshot.data![3]),
|
||||||
_History(states: snapshot.data![2], update: _justUpdate),
|
_History(states: snapshot.data![2], update: _justUpdate),
|
||||||
_RecordThingy(record: sprintRuns.elementAtOrNull(0)),
|
_RecordThingy(record: snapshot.data![1]['sprint']),
|
||||||
_RecordThingy(record: blitzRuns.elementAtOrNull(0)),
|
_RecordThingy(record: snapshot.data![1]['blitz']),
|
||||||
_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],)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
@ -392,7 +392,14 @@ class UserThingy extends StatelessWidget {
|
||||||
width: 32,
|
width: 32,
|
||||||
errorBuilder: (context, error, stackTrace) {
|
errorBuilder: (context, error, stackTrace) {
|
||||||
developer.log("Error with building $badge", name: "main_view", error: error, stackTrace: stackTrace);
|
developer.log("Error with building $badge", name: "main_view", error: error, stackTrace: stackTrace);
|
||||||
return Image.asset("res/icons/kagari.png", height: 32, width: 32);
|
return Image.network(
|
||||||
|
"https://tetr.io/res/badges/${badge.badgeId}.png",
|
||||||
|
height: 32,
|
||||||
|
width: 32,
|
||||||
|
errorBuilder:(context, error, stackTrace) {
|
||||||
|
return Image.asset("res/icons/kagari.png", height: 32, width: 32);
|
||||||
|
}
|
||||||
|
);
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
],
|
],
|
||||||
|
|
127
pubspec.yaml
|
@ -82,129 +82,10 @@ targets:
|
||||||
flutter:
|
flutter:
|
||||||
uses-material-design: true
|
uses-material-design: true
|
||||||
assets:
|
assets:
|
||||||
- res/avatars/tetrio_anon.png
|
- res/avatars/
|
||||||
- res/avatars/tetrio_banned.png
|
- res/icons/
|
||||||
- res/icons/kagari.png
|
- res/tetrio_tl_alpha_ranks/
|
||||||
- res/icons/osk.svg
|
- res/tetrio_badges/
|
||||||
- res/icons/tetrio-logo.svg
|
|
||||||
- res/icons/improvement-local.png
|
|
||||||
- res/icons/supporter-tag.png
|
|
||||||
- res/tetrio_tl_alpha_ranks/x.png
|
|
||||||
- res/tetrio_tl_alpha_ranks/u.png
|
|
||||||
- res/tetrio_tl_alpha_ranks/ss.png
|
|
||||||
- res/tetrio_tl_alpha_ranks/s+.png
|
|
||||||
- res/tetrio_tl_alpha_ranks/s.png
|
|
||||||
- res/tetrio_tl_alpha_ranks/s-.png
|
|
||||||
- res/tetrio_tl_alpha_ranks/a+.png
|
|
||||||
- res/tetrio_tl_alpha_ranks/a.png
|
|
||||||
- res/tetrio_tl_alpha_ranks/a-.png
|
|
||||||
- res/tetrio_tl_alpha_ranks/b+.png
|
|
||||||
- res/tetrio_tl_alpha_ranks/b.png
|
|
||||||
- res/tetrio_tl_alpha_ranks/b-.png
|
|
||||||
- res/tetrio_tl_alpha_ranks/c+.png
|
|
||||||
- res/tetrio_tl_alpha_ranks/c.png
|
|
||||||
- res/tetrio_tl_alpha_ranks/c-.png
|
|
||||||
- res/tetrio_tl_alpha_ranks/d+.png
|
|
||||||
- res/tetrio_tl_alpha_ranks/d.png
|
|
||||||
- res/tetrio_tl_alpha_ranks/z.png
|
|
||||||
|
|
||||||
- res/tetrio_badges/5mblast_1.png
|
|
||||||
- res/tetrio_badges/5mblast_10.png
|
|
||||||
- res/tetrio_badges/5mblast_100.png
|
|
||||||
- res/tetrio_badges/5mblast_1000.png
|
|
||||||
- res/tetrio_badges/20tsd.png
|
|
||||||
- res/tetrio_badges/100player.png
|
|
||||||
- res/tetrio_badges/allclear.png
|
|
||||||
- res/tetrio_badges/bugbounty.png
|
|
||||||
- res/tetrio_badges/cometopen_1.png
|
|
||||||
- res/tetrio_badges/cometopen_2.png
|
|
||||||
- res/tetrio_badges/cometopen_3.png
|
|
||||||
- res/tetrio_badges/early-supporter.png
|
|
||||||
- res/tetrio_badges/founder.png
|
|
||||||
- res/tetrio_badges/galactic2x2_1.png
|
|
||||||
- res/tetrio_badges/galactic2x2_2.png
|
|
||||||
- res/tetrio_badges/galactic2x2_3.png
|
|
||||||
- res/tetrio_badges/ggc_1.png
|
|
||||||
- res/tetrio_badges/ggc_2.png
|
|
||||||
- res/tetrio_badges/ggc_3.png
|
|
||||||
- res/tetrio_badges/hdoxii_1.png
|
|
||||||
- res/tetrio_badges/hdoxii_2.png
|
|
||||||
- res/tetrio_badges/hdoxii_3.png
|
|
||||||
- res/tetrio_badges/heart.png
|
|
||||||
- res/tetrio_badges/hnprism_1.png
|
|
||||||
- res/tetrio_badges/hnprism_2.png
|
|
||||||
- res/tetrio_badges/hnprism_3.png
|
|
||||||
- res/tetrio_badges/hnstratosphere50_1.png
|
|
||||||
- res/tetrio_badges/hnstratosphere50_2.png
|
|
||||||
- res/tetrio_badges/hnstratosphere50_3.png
|
|
||||||
- res/tetrio_badges/ift_1.png
|
|
||||||
- res/tetrio_badges/ift_2.png
|
|
||||||
- res/tetrio_badges/ift_3.png
|
|
||||||
- res/tetrio_badges/infdev.png
|
|
||||||
- res/tetrio_badges/kod_by_founder.png
|
|
||||||
- res/tetrio_badges/kod_founder.png
|
|
||||||
- res/tetrio_badges/leaderboard1.png
|
|
||||||
- res/tetrio_badges/mmc_tabi_1.png
|
|
||||||
- res/tetrio_badges/mmc_tabi_2.png
|
|
||||||
- res/tetrio_badges/mmc_tabi_3.png
|
|
||||||
- res/tetrio_badges/mmc_tabi_superlobby.png
|
|
||||||
- res/tetrio_badges/mmc_tabi_superlobby2.png
|
|
||||||
- res/tetrio_badges/mmc_tabi_superlobby3.png
|
|
||||||
- res/tetrio_badges/mmc_tabi_superlobby4.png
|
|
||||||
- res/tetrio_badges/redgevo_1.png
|
|
||||||
- res/tetrio_badges/redgevo_2.png
|
|
||||||
- res/tetrio_badges/redgevo_3.png
|
|
||||||
- res/tetrio_badges/rengervl_1.png
|
|
||||||
- res/tetrio_badges/rengervl_2.png
|
|
||||||
- res/tetrio_badges/rengervl_3.png
|
|
||||||
- res/tetrio_badges/sakurablend_1.png
|
|
||||||
- res/tetrio_badges/sakurablend_2.png
|
|
||||||
- res/tetrio_badges/sakurablend_3.png
|
|
||||||
- res/tetrio_badges/scuncapped_1.png
|
|
||||||
- res/tetrio_badges/scuncapped_2.png
|
|
||||||
- res/tetrio_badges/scuncapped_3.png
|
|
||||||
- res/tetrio_badges/secretgrade.png
|
|
||||||
- res/tetrio_badges/sfu_raccoon_1.png
|
|
||||||
- res/tetrio_badges/sfu_raccoon_2.png
|
|
||||||
- res/tetrio_badges/sfu_raccoon_3.png
|
|
||||||
- res/tetrio_badges/streamersuperlobby.png
|
|
||||||
- res/tetrio_badges/superlobby.png
|
|
||||||
- res/tetrio_badges/superlobby2.png
|
|
||||||
- res/tetrio_badges/taws_u50_1.png
|
|
||||||
- res/tetrio_badges/taws_u50_2.png
|
|
||||||
- res/tetrio_badges/taws_u50_3.png
|
|
||||||
- res/tetrio_badges/tawshdsl_capped.png
|
|
||||||
- res/tetrio_badges/tawshdsl_uncapped.png
|
|
||||||
- res/tetrio_badges/tawsignite_expert.png
|
|
||||||
- res/tetrio_badges/tawslg.png
|
|
||||||
- res/tetrio_badges/tetralympic_masters.png
|
|
||||||
- res/tetrio_badges/thaitour_1.png
|
|
||||||
- res/tetrio_badges/thaitour_2.png
|
|
||||||
- res/tetrio_badges/thaitour_3.png
|
|
||||||
- res/tetrio_badges/ttsdpf_1.png
|
|
||||||
- res/tetrio_badges/ttsdpf_2.png
|
|
||||||
- res/tetrio_badges/ttsdpf_3.png
|
|
||||||
- res/tetrio_badges/ttsdtc_1.png
|
|
||||||
- res/tetrio_badges/ttsdtc_2.png
|
|
||||||
- res/tetrio_badges/ttsdtc_3.png
|
|
||||||
- res/tetrio_badges/twc23_1.png
|
|
||||||
- res/tetrio_badges/twc23_2.png
|
|
||||||
- res/tetrio_badges/twc23_3.png
|
|
||||||
- res/tetrio_badges/twc23_4.png
|
|
||||||
- res/tetrio_badges/ubcea_1.png
|
|
||||||
- res/tetrio_badges/ubcea_2.png
|
|
||||||
- res/tetrio_badges/ubcea_3.png
|
|
||||||
- res/tetrio_badges/underdog_1.png
|
|
||||||
- res/tetrio_badges/underdog_2.png
|
|
||||||
- res/tetrio_badges/underdog_3.png
|
|
||||||
- res/tetrio_badges/underdog_predict.png
|
|
||||||
- res/tetrio_badges/wpl_1.png
|
|
||||||
- res/tetrio_badges/wpl_2.png
|
|
||||||
- res/tetrio_badges/wpl_3.png
|
|
||||||
- res/tetrio_badges/wplc_1.png
|
|
||||||
- res/tetrio_badges/wplc_2.png
|
|
||||||
- res/tetrio_badges/wplc_3.png
|
|
||||||
- res/tetrio_badges/wplc_participation.png
|
|
||||||
|
|
||||||
# An image asset can refer to one or more resolution-specific "variants", see
|
# An image asset can refer to one or more resolution-specific "variants", see
|
||||||
# https://flutter.dev/assets-and-images/#resolution-aware
|
# https://flutter.dev/assets-and-images/#resolution-aware
|
||||||
|
|
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 6.3 KiB |
After Width: | Height: | Size: 6.5 KiB |
After Width: | Height: | Size: 50 KiB |
After Width: | Height: | Size: 46 KiB |
After Width: | Height: | Size: 59 KiB |
After Width: | Height: | Size: 47 KiB |
After Width: | Height: | Size: 138 KiB |
After Width: | Height: | Size: 67 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 27 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 19 KiB |
|
@ -13,21 +13,27 @@ import 'package:tetra_stats/services/tetrio_crud.dart';
|
||||||
void main() {
|
void main() {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
DartPluginRegistrant.ensureInitialized();
|
DartPluginRegistrant.ensureInitialized();
|
||||||
if (kIsWeb) {
|
late TetrioService teto;
|
||||||
sqfliteFfiInit();
|
setUp(() {
|
||||||
databaseFactory = databaseFactoryFfiWeb;
|
if (kIsWeb) {
|
||||||
} else if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) {
|
sqfliteFfiInit();
|
||||||
sqfliteFfiInit();
|
databaseFactory = databaseFactoryFfiWeb;
|
||||||
databaseFactory = databaseFactoryFfi;
|
} else if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) {
|
||||||
}
|
sqfliteFfiInit();
|
||||||
|
databaseFactory = databaseFactoryFfi;
|
||||||
|
}
|
||||||
|
teto = TetrioService();
|
||||||
|
});
|
||||||
|
|
||||||
test("Initialize TetrioServise", () async {
|
test("Initialize TetrioServise", () async {
|
||||||
await TetrioService().open();
|
teto.open();
|
||||||
}, skip: true); // a fucking MissingPluginException how does that even happening?
|
}); // a fucking MissingPluginException how does that even happening?
|
||||||
// i guess i will be unable to test iteractions with DB
|
// i guess i will be unable to test iteractions with DB
|
||||||
|
|
||||||
group("Test fetchPlayer with different players", () {
|
group("Test fetchPlayer with different players", () {
|
||||||
|
// those tests exist in order to detect a tiny little change in Tetra Channel API in case of some update.
|
||||||
test("dan63047 (user who have activity in tetra league)", () async {
|
test("dan63047 (user who have activity in tetra league)", () async {
|
||||||
TetrioPlayer dan63047 = await TetrioService().fetchPlayer("6098518e3d5155e6ec429cdc");
|
TetrioPlayer dan63047 = await teto.fetchPlayer("6098518e3d5155e6ec429cdc");
|
||||||
expect(dan63047.userId, "6098518e3d5155e6ec429cdc");
|
expect(dan63047.userId, "6098518e3d5155e6ec429cdc");
|
||||||
expect(dan63047.registrationTime != null, true);
|
expect(dan63047.registrationTime != null, true);
|
||||||
expect(dan63047.avatarRevision != null, true);
|
expect(dan63047.avatarRevision != null, true);
|
||||||
|
@ -51,7 +57,7 @@ void main() {
|
||||||
expect(dan63047.tlSeason1.playstyle != null, true);
|
expect(dan63047.tlSeason1.playstyle != null, true);
|
||||||
});
|
});
|
||||||
test("osk (sysop who have activity in tetra league)", () async {
|
test("osk (sysop who have activity in tetra league)", () async {
|
||||||
TetrioPlayer osk = await TetrioService().fetchPlayer("5e32fc85ab319c2ab1beb07c");
|
TetrioPlayer osk = await teto.fetchPlayer("5e32fc85ab319c2ab1beb07c");
|
||||||
expect(osk.userId, "5e32fc85ab319c2ab1beb07c");
|
expect(osk.userId, "5e32fc85ab319c2ab1beb07c");
|
||||||
expect(osk.registrationTime, null);
|
expect(osk.registrationTime, null);
|
||||||
expect(osk.country, "XM");
|
expect(osk.country, "XM");
|
||||||
|
@ -79,7 +85,7 @@ void main() {
|
||||||
expect(osk.tlSeason1.playstyle != null, true);
|
expect(osk.tlSeason1.playstyle != null, true);
|
||||||
});
|
});
|
||||||
test("kagari (sysop who have zero activity)", () async {
|
test("kagari (sysop who have zero activity)", () async {
|
||||||
TetrioPlayer kagari = await TetrioService().fetchPlayer("5e331c3ce24a5a3e258f7a1b");
|
TetrioPlayer kagari = await teto.fetchPlayer("5e331c3ce24a5a3e258f7a1b");
|
||||||
expect(kagari.userId, "5e331c3ce24a5a3e258f7a1b");
|
expect(kagari.userId, "5e331c3ce24a5a3e258f7a1b");
|
||||||
expect(kagari.registrationTime, null);
|
expect(kagari.registrationTime, null);
|
||||||
expect(kagari.country, "XM");
|
expect(kagari.country, "XM");
|
||||||
|
@ -112,7 +118,7 @@ void main() {
|
||||||
expect(kagari.tlSeason1.playstyle, null);
|
expect(kagari.tlSeason1.playstyle, null);
|
||||||
});
|
});
|
||||||
test("furry (banned account)", () async {
|
test("furry (banned account)", () async {
|
||||||
TetrioPlayer furry = await TetrioService().fetchPlayer("5eea0ff69a1ba76c20347086");
|
TetrioPlayer furry = await teto.fetchPlayer("5eea0ff69a1ba76c20347086");
|
||||||
expect(furry.userId, "5eea0ff69a1ba76c20347086");
|
expect(furry.userId, "5eea0ff69a1ba76c20347086");
|
||||||
expect(furry.registrationTime, DateTime.parse("2020-06-17T12:43:34.790Z"));
|
expect(furry.registrationTime, DateTime.parse("2020-06-17T12:43:34.790Z"));
|
||||||
expect(furry.role, "banned");
|
expect(furry.role, "banned");
|
||||||
|
@ -143,7 +149,7 @@ void main() {
|
||||||
expect(furry.tlSeason1.playstyle, null);
|
expect(furry.tlSeason1.playstyle, null);
|
||||||
});
|
});
|
||||||
test("oskwarefan (anon account)", () async {
|
test("oskwarefan (anon account)", () async {
|
||||||
TetrioPlayer oskwarefan = await TetrioService().fetchPlayer("646cb8273e887a054d64febe");
|
TetrioPlayer oskwarefan = await teto.fetchPlayer("646cb8273e887a054d64febe");
|
||||||
expect(oskwarefan.userId, "646cb8273e887a054d64febe");
|
expect(oskwarefan.userId, "646cb8273e887a054d64febe");
|
||||||
expect(oskwarefan.registrationTime, DateTime.parse("2023-05-23T12:57:11.481Z"));
|
expect(oskwarefan.registrationTime, DateTime.parse("2023-05-23T12:57:11.481Z"));
|
||||||
expect(oskwarefan.role, "anon");
|
expect(oskwarefan.role, "anon");
|
||||||
|
@ -174,7 +180,7 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test("not existing account", () async {
|
test("not existing account", () async {
|
||||||
var future = TetrioService().fetchPlayer("hasdbashdbs");
|
var future = teto.fetchPlayer("hasdbashdbs");
|
||||||
await expectLater(future, throwsA(isA<TetrioPlayerNotExist>()));
|
await expectLater(future, throwsA(isA<TetrioPlayerNotExist>()));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|