Tetra League Leaderboard

This commit is contained in:
dan63047 2023-07-07 23:32:57 +03:00
parent eae9bbedee
commit 9fb74f051c
4 changed files with 196 additions and 6 deletions

View File

@ -845,10 +845,11 @@ class TetrioPlayersLeaderboard {
TetrioPlayersLeaderboard(this.type, this.leaderboard); TetrioPlayersLeaderboard(this.type, this.leaderboard);
TetrioPlayersLeaderboard.fromJson(Map<String, dynamic> json, String type, DateTime ts) { TetrioPlayersLeaderboard.fromJson(List<dynamic> json, String t, DateTime ts) {
type = type; type = t;
timestamp = ts; timestamp = ts;
for (Map<String, dynamic> entry in json['users']) { leaderboard = [];
for (Map<String, dynamic> entry in json) {
leaderboard.add(TetrioPlayerFromLeaderboard.fromJson(entry, ts)); leaderboard.add(TetrioPlayerFromLeaderboard.fromJson(entry, ts));
} }
} }
@ -862,9 +863,42 @@ class TetrioPlayerFromLeaderboard {
String? country; String? country;
late bool supporter; late bool supporter;
late bool verified; late bool verified;
late TetraLeagueAlpha league; late DateTime timestamp;
late int gamesPlayed;
late int gamesWon;
late double rating;
late double glicko;
late double rd;
late String rank;
late String bestRank;
late double apm;
late double pps;
late double vs;
late bool decaying;
TetrioPlayerFromLeaderboard(this.userId, this.username, this.role, this.xp, this.country, this.supporter, this.verified, this.league); TetrioPlayerFromLeaderboard(
this.userId,
this.username,
this.role,
this.xp,
this.country,
this.supporter,
this.verified,
this.timestamp,
this.gamesPlayed,
this.gamesWon,
this.rating,
this.glicko,
this.rd,
this.rank,
this.bestRank,
this.apm,
this.pps,
this.vs,
this.decaying);
get app => apm / (pps * 60);
get vsapm => vs / apm;
TetrioPlayerFromLeaderboard.fromJson(Map<String, dynamic> json, DateTime ts) { TetrioPlayerFromLeaderboard.fromJson(Map<String, dynamic> json, DateTime ts) {
userId = json['_id']; userId = json['_id'];
@ -874,6 +908,17 @@ class TetrioPlayerFromLeaderboard {
country = json['country ']; country = json['country '];
supporter = json['supporter']; supporter = json['supporter'];
verified = json['verified']; verified = json['verified'];
league = TetraLeagueAlpha.fromJson(json['league'], ts); timestamp = ts;
gamesPlayed = json['league']['gamesplayed'];
gamesWon = json['league']['gameswon'];
rating = json['league']['rating'].toDouble();
glicko = json['league']['glicko'].toDouble();
rd = json['league']['rd'].toDouble();
rank = json['league']['rank'];
bestRank = json['league']['bestrank'];
apm = json['league']['apm'].toDouble();
pps = json['league']['pps'].toDouble();
vs = json['league']['vs'].toDouble();
decaying = json['league']['decaying'];
} }
} }

View File

@ -1,7 +1,10 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:developer' as developer; import 'dart:developer' as developer;
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:path_provider/path_provider.dart';
import 'package:tetra_stats/services/crud_exceptions.dart'; import 'package:tetra_stats/services/crud_exceptions.dart';
import 'package:tetra_stats/services/sqlite_db_controller.dart'; import 'package:tetra_stats/services/sqlite_db_controller.dart';
import 'package:tetra_stats/data_objects/tetrio.dart'; import 'package:tetra_stats/data_objects/tetrio.dart';
@ -49,6 +52,7 @@ class TetrioService extends DB {
Map<String, List<TetrioPlayer>> _players = {}; Map<String, List<TetrioPlayer>> _players = {};
final Map<String, TetrioPlayer> _playersCache = {}; final Map<String, TetrioPlayer> _playersCache = {};
final Map<String, Map<String, dynamic>> _recordsCache = {}; final Map<String, Map<String, dynamic>> _recordsCache = {};
final Map<String, TetrioPlayersLeaderboard> _leaderboardsCache = {};
final Map<String, TetraLeagueAlphaStream> _tlStreamsCache = {}; // i'm trying to respect oskware api It should look something like {"cached_until": TetrioPlayer} final Map<String, TetraLeagueAlphaStream> _tlStreamsCache = {}; // i'm trying to respect oskware api It should look something like {"cached_until": TetrioPlayer}
static final TetrioService _shared = TetrioService._sharedInstance(); static final TetrioService _shared = TetrioService._sharedInstance();
factory TetrioService() => _shared; factory TetrioService() => _shared;
@ -91,6 +95,39 @@ class TetrioService extends DB {
return player.username; return player.username;
} }
Future<TetrioPlayersLeaderboard> fetchTLLeaderboard() async {
try{
var cached = _leaderboardsCache.entries.firstWhere((element) => element.value.type == "league");
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");
}
var url = Uri.https('ch.tetr.io', 'api/users/lists/league/all');
final response = await http.get(url);
if (response.statusCode == 200) {
var rawJson = jsonDecode(response.body);
if (rawJson['success']) {
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");
_leaderboardsCache[rawJson['cache']['cached_until'].toString()] = leaderboard;
return leaderboard;
} else {
developer.log("fetchTLLeaderboard: Bruh", name: "services/tetrio_crud", error: rawJson);
throw Exception("User doesn't exist");
}
} else {
developer.log("fetchTLLeaderboard: Failed to fetch leaderboard", name: "services/tetrio_crud", error: response.statusCode);
throw Exception('Failed to fetch player');
}
}
Future<TetraLeagueAlphaStream> getTLStream(String userID) async { Future<TetraLeagueAlphaStream> getTLStream(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);

View File

@ -8,6 +8,7 @@ import 'package:flutter/services.dart';
import 'package:tetra_stats/data_objects/tetrio.dart'; import 'package:tetra_stats/data_objects/tetrio.dart';
import 'package:tetra_stats/services/tetrio_crud.dart'; import 'package:tetra_stats/services/tetrio_crud.dart';
import 'package:tetra_stats/services/crud_exceptions.dart'; import 'package:tetra_stats/services/crud_exceptions.dart';
import 'package:tetra_stats/views/tl_leaderboard_view.dart' show TLLeaderboardView;
import 'package:tetra_stats/views/tl_match_view.dart' show TlMatchResultView; import 'package:tetra_stats/views/tl_match_view.dart' show TlMatchResultView;
import 'package:tetra_stats/widgets/stat_sell_num.dart'; import 'package:tetra_stats/widgets/stat_sell_num.dart';
import 'package:tetra_stats/widgets/tl_thingy.dart'; import 'package:tetra_stats/widgets/tl_thingy.dart';
@ -235,6 +236,8 @@ class _MainState extends State<MainView> with SingleTickerProviderStateMixin {
), ),
], ],
onSelected: (value) { onSelected: (value) {
if (value == "tll") {teto.fetchTLLeaderboard();
return;}
Navigator.pushNamed(context, value); Navigator.pushNamed(context, value);
}, },
), ),
@ -420,6 +423,20 @@ class _NavDrawerState extends State<NavDrawer> {
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
), ),
),
SliverToBoxAdapter(
child: ListTile(
leading: const Icon(Icons.leaderboard),
title: const Text("Tetra League leaderboard"),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const TLLeaderboardView(),
),
);
},
),
) )
]; ];
}, },

View File

@ -0,0 +1,91 @@
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:tetra_stats/data_objects/tetrio.dart';
import 'package:tetra_stats/services/tetrio_crud.dart';
import 'package:tetra_stats/views/states_view.dart';
final TetrioService teto = TetrioService();
class TLLeaderboardView extends StatefulWidget {
const TLLeaderboardView({Key? key}) : super(key: key);
@override
State<StatefulWidget> createState() => TLLeaderboardState();
}
final DateFormat dateFormat = DateFormat.yMMMd().add_Hms();
final NumberFormat f2 = NumberFormat.decimalPatternDigits(decimalDigits: 2);
class TLLeaderboardState extends State<TLLeaderboardView> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Tetra League Leaderboard"),
),
backgroundColor: Colors.black,
body: SafeArea(
child: FutureBuilder(
future: teto.fetchTLLeaderboard(),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.waiting:
case ConnectionState.active:
return const Center(child: Text('Fetching...'));
case ConnectionState.done:
final allPlayers = snapshot.data?.leaderboard;
return NestedScrollView(
headerSliverBuilder: (context, value) {
String howManyPlayers(int numberOfPlayers) => Intl.plural(
numberOfPlayers,
zero: 'Empty list. Press "Track" button in previous view to add current player here',
one: 'There is only one player',
other: 'There are $numberOfPlayers players',
name: 'howManyPeople',
args: [numberOfPlayers],
desc: 'Description of how many people are seen in a place.',
examples: const {'numberOfPeople': 3},
);
return [
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.only(left: 16),
child: Text(
howManyPlayers(allPlayers.length),
style: const TextStyle(color: Colors.white, fontSize: 25),
),
)),
const SliverToBoxAdapter(child: Divider())
];
},
body: ListView.builder(
itemCount: allPlayers!.length,
itemBuilder: (context, index) {
return ListTile(
leading: Text((index+1).toString(), style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 28)),
title: Text("${allPlayers[index].username}", style: const TextStyle(fontFamily: "Eurostile Round Extended")),
subtitle: Text(
"${f2.format(allPlayers[index].apm)} APM, ${f2.format(allPlayers[index].pps)} PPS, ${f2.format(allPlayers[index].vs)} VS, ${f2.format(allPlayers[index].app)} APP, ${f2.format(allPlayers[index].vsapm)} VS/APM"),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text("${f2.format(allPlayers[index].rating)} TR", style: const TextStyle(fontSize: 28)),
Image.asset("res/tetrio_tl_alpha_ranks/${allPlayers[index].rank}.png", height: 48),
],
),
onTap: () {
// Navigator.push(
// context,
// MaterialPageRoute(
// builder: (context) => StatesView(states: allPlayers!),
// ),
// );
},
);
}));
}
})),
);
}
}