leaderboards done (kinda)
This commit is contained in:
parent
c3214f5ba9
commit
dbdfe29dc0
|
@ -3,9 +3,11 @@
|
|||
import 'package:tetra_stats/data_objects/aggregate_stats.dart';
|
||||
import 'package:tetra_stats/data_objects/record_extras.dart';
|
||||
import 'package:tetra_stats/data_objects/results_stats.dart';
|
||||
import 'package:tetra_stats/data_objects/tetrio_prisecter.dart';
|
||||
|
||||
class RecordSingle {
|
||||
late String? userId;
|
||||
late String username;
|
||||
late String replayId;
|
||||
late String ownId;
|
||||
late String gamemode;
|
||||
|
@ -15,8 +17,9 @@ class RecordSingle {
|
|||
late int countryRank;
|
||||
late AggregateStats aggregateStats;
|
||||
late RecordExtras extras;
|
||||
late Prisecter prisecter;
|
||||
|
||||
RecordSingle({required this.userId, required this.replayId, required this.ownId, required this.timestamp, required this.stats, required this.rank, required this.countryRank, required this.aggregateStats});
|
||||
RecordSingle({required this.replayId, required this.ownId, required this.timestamp, required this.stats, required this.rank, required this.countryRank, required this.aggregateStats});
|
||||
|
||||
RecordSingle.fromJson(Map<String, dynamic> json, int ran, int cran) {
|
||||
ownId = json['_id'];
|
||||
|
@ -24,10 +27,14 @@ class RecordSingle {
|
|||
stats = ResultsStats.fromJson(json['results']['stats']);
|
||||
replayId = json['replayid'];
|
||||
timestamp = DateTime.parse(json['ts']);
|
||||
if (json['user'] != null) userId = json['user']['id'];
|
||||
if (json['user'] != null) {
|
||||
userId = json['user']['id'];
|
||||
username = json['user']['username'];
|
||||
}
|
||||
rank = ran;
|
||||
countryRank = cran;
|
||||
aggregateStats = AggregateStats.fromJson(json['results']['aggregatestats']);
|
||||
prisecter = Prisecter.fromJson(json['p']);
|
||||
var ex = json['extras'] as Map<String, dynamic>;
|
||||
switch (ex.keys.firstOrNull){
|
||||
case "zenith":
|
||||
|
|
|
@ -6,6 +6,7 @@ import 'package:tetra_stats/data_objects/est_tr.dart';
|
|||
import 'package:tetra_stats/data_objects/nerd_stats.dart';
|
||||
import 'package:tetra_stats/data_objects/playstyle.dart';
|
||||
import 'package:tetra_stats/data_objects/tetrio_constants.dart';
|
||||
import 'package:tetra_stats/data_objects/tetrio_prisecter.dart';
|
||||
|
||||
class TetrioPlayerFromLeaderboard {
|
||||
late String userId;
|
||||
|
@ -33,6 +34,8 @@ class TetrioPlayerFromLeaderboard {
|
|||
late int gamesWonTotal;
|
||||
late Duration playtime;
|
||||
late int ar;
|
||||
late Map<String, int> ar_counts;
|
||||
late Prisecter prisecter;
|
||||
|
||||
TetrioPlayerFromLeaderboard(
|
||||
this.userId,
|
||||
|
@ -91,6 +94,11 @@ class TetrioPlayerFromLeaderboard {
|
|||
gamesWonTotal = json['gameswon'] as int;
|
||||
playtime = Duration(microseconds: (json['gametime'].toDouble() * 1000000).floor());
|
||||
ar = json['ar'];
|
||||
ar_counts = {};
|
||||
for (var entry in json['ar_counts'].keys){
|
||||
ar_counts[entry.toString()] = json['ar_counts'][entry];
|
||||
}
|
||||
prisecter = Prisecter.fromJson(json['p']);
|
||||
nerdStats = NerdStats(apm, pps, vs);
|
||||
estTr = EstTr(apm, pps, vs, nerdStats.app, nerdStats.dss, nerdStats.dsp, nerdStats.gbe);
|
||||
playstyle = Playstyle(apm, pps, nerdStats.app, nerdStats.vsapm, nerdStats.dsp, nerdStats.gbe, estTr.srarea, estTr.statrank);
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
class Prisecter {
|
||||
late final num pri;
|
||||
late final num sec;
|
||||
late final num ter;
|
||||
|
||||
Prisecter(this.pri, this.sec, this.ter);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return "${pri}:${sec}:${ter}";
|
||||
}
|
||||
|
||||
Prisecter.fromJson(Map<String, dynamic> json){
|
||||
pri = json['pri'];
|
||||
sec = json['sec'];
|
||||
ter = json['ter'];
|
||||
}
|
||||
}
|
|
@ -798,8 +798,7 @@ class TetrioService extends DB {
|
|||
}
|
||||
}
|
||||
|
||||
Future<List<TetrioPlayerFromLeaderboard>> fetchTetrioLeaderboard({double? after, String? lb}) async {
|
||||
const int lbLength = 100;
|
||||
Future<List<TetrioPlayerFromLeaderboard>> fetchTetrioLeaderboard({String? prisecter, String? lb}) async {
|
||||
// TetrioPlayersLeaderboard? cached = _cache.get("league", TetrioPlayersLeaderboard);
|
||||
// if (cached != null) return cached;
|
||||
|
||||
|
@ -809,7 +808,7 @@ class TetrioService extends DB {
|
|||
} else {
|
||||
url = Uri.https('ch.tetr.io', 'api/users/by/${lb??"league"}', {
|
||||
"limit": "100",
|
||||
if (after != null && after != -1) "after": "$after:0:0"
|
||||
if (prisecter != null) "after": prisecter
|
||||
});
|
||||
}
|
||||
try{
|
||||
|
@ -853,6 +852,57 @@ class TetrioService extends DB {
|
|||
}
|
||||
}
|
||||
|
||||
Future<List<RecordSingle>> fetchTetrioRecordsLeaderboard({String? prisecter, String? lb}) async{
|
||||
Uri url;
|
||||
if (kIsWeb) {
|
||||
url = Uri.https('ts.dan63.by', 'oskware_bridge.php', {"endpoint": "TLLeaderboard"});
|
||||
} else {
|
||||
url = Uri.https('ch.tetr.io', 'api/records/${lb??"40l_global"}', {
|
||||
"limit": "100",
|
||||
if (prisecter != null) "after": prisecter
|
||||
});
|
||||
}
|
||||
try{
|
||||
final response = await client.get(url);
|
||||
|
||||
switch (response.statusCode) {
|
||||
case 200:
|
||||
_lbPositions.clear();
|
||||
var rawJson = jsonDecode(response.body);
|
||||
if (rawJson['success']) { // if api confirmed that everything ok
|
||||
List<RecordSingle> leaderboard = [];
|
||||
for (Map<String, dynamic> entry in rawJson['data']['entries']) {
|
||||
leaderboard.add(RecordSingle.fromJson(entry, -1, -1));
|
||||
}
|
||||
developer.log("fetchTLLeaderboard: Leaderboard retrieved and cached", name: "services/tetrio_crud");
|
||||
//_leaderboardsCache[rawJson['cache']['cached_until'].toString()] = leaderboard;
|
||||
//_cache.store(leaderboard, rawJson['cache']['cached_until']);
|
||||
return leaderboard;
|
||||
} else { // idk how to hit that one
|
||||
developer.log("fetchTLLeaderboard: Bruh", name: "services/tetrio_crud", error: rawJson);
|
||||
throw Exception("Failed to get leaderboard (problems on the tetr.io side)"); // will it be on tetr.io side?
|
||||
}
|
||||
case 403:
|
||||
throw TetrioForbidden();
|
||||
case 429:
|
||||
throw TetrioTooManyRequests();
|
||||
case 418:
|
||||
throw TetrioOskwareBridgeProblem();
|
||||
case 500:
|
||||
case 502:
|
||||
case 503:
|
||||
case 504:
|
||||
throw TetrioInternalProblem();
|
||||
default:
|
||||
developer.log("fetchTLLeaderboard: Failed to fetch leaderboard", name: "services/tetrio_crud", error: response.statusCode);
|
||||
throw ConnectionIssue(response.statusCode, response.reasonPhrase??"No reason");
|
||||
}
|
||||
} on http.ClientException catch (e, s) {
|
||||
developer.log("$e, $s");
|
||||
throw http.ClientException(e.message, e.uri);
|
||||
}
|
||||
}
|
||||
|
||||
TetrioPlayersLeaderboard? getCachedLeaderboard(){
|
||||
return _cache.get("league", TetrioPlayersLeaderboard);
|
||||
}
|
||||
|
|
|
@ -200,6 +200,7 @@ class _MainState extends State<MainView> with TickerProviderStateMixin {
|
|||
0 => DestinationHome(searchFor: _searchFor, constraints: constraints),
|
||||
1 => DestinationGraphs(searchFor: _searchFor, constraints: constraints),
|
||||
2 => DestinationLeaderboards(constraints: constraints),
|
||||
3 => DestinationCutoffs(constraints: constraints),
|
||||
_ => Text("Unknown destination $destination")
|
||||
},
|
||||
)
|
||||
|
@ -215,14 +216,17 @@ class DestinationCutoffs extends StatefulWidget{
|
|||
const DestinationCutoffs({super.key, required this.constraints});
|
||||
|
||||
@override
|
||||
State<DestinationLeaderboards> createState() => _DestinationCutoffsState();
|
||||
State<DestinationCutoffs> createState() => _DestinationCutoffsState();
|
||||
}
|
||||
|
||||
class _DestinationCutoffsState extends State<DestinationLeaderboards> {
|
||||
class _DestinationCutoffsState extends State<DestinationCutoffs> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// TODO: implement build
|
||||
throw UnimplementedError();
|
||||
return Column(
|
||||
children: [
|
||||
Card(),
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -238,19 +242,31 @@ class DestinationLeaderboards extends StatefulWidget{
|
|||
enum Leaderboards{
|
||||
tl,
|
||||
xp,
|
||||
ar
|
||||
ar,
|
||||
sprint,
|
||||
blitz,
|
||||
zenith,
|
||||
zenithex,
|
||||
}
|
||||
|
||||
class _DestinationLeaderboardsState extends State<DestinationLeaderboards> {
|
||||
//Duration postSeasonLeft = seasonStart.difference(DateTime.now());
|
||||
final Map<Leaderboards, String> leaderboards = {Leaderboards.tl: "Tetra League", Leaderboards.xp: "XP", Leaderboards.ar: "Acievement Points"};
|
||||
final Map<Leaderboards, String> leaderboards = {
|
||||
Leaderboards.tl: "Tetra League",
|
||||
Leaderboards.xp: "XP",
|
||||
Leaderboards.ar: "Acievement Points",
|
||||
Leaderboards.sprint: "40 Lines",
|
||||
Leaderboards.blitz: "Blitz",
|
||||
Leaderboards.zenith: "Quick Play",
|
||||
Leaderboards.zenithex: "Quick Play Expert",
|
||||
};
|
||||
Leaderboards _currentLb = Leaderboards.tl;
|
||||
final StreamController<List<TetrioPlayerFromLeaderboard>> _dataStreamController = StreamController<List<TetrioPlayerFromLeaderboard>>();
|
||||
final StreamController<List<dynamic>> _dataStreamController = StreamController<List<dynamic>>();
|
||||
late final ScrollController _scrollController;
|
||||
Stream<List<TetrioPlayerFromLeaderboard>> get dataStream => _dataStreamController.stream;
|
||||
List<TetrioPlayerFromLeaderboard> list = [];
|
||||
Stream<List<dynamic>> get dataStream => _dataStreamController.stream;
|
||||
List<dynamic> list = [];
|
||||
bool _isFetchingData = false;
|
||||
double after = 25000.00;
|
||||
String? prisecter;
|
||||
|
||||
Future<void> _fetchData() async {
|
||||
if (_isFetchingData) {
|
||||
|
@ -262,19 +278,19 @@ class _DestinationLeaderboardsState extends State<DestinationLeaderboards> {
|
|||
setState(() {});
|
||||
|
||||
final items = switch(_currentLb){
|
||||
Leaderboards.tl => await teto.fetchTetrioLeaderboard(after: after),
|
||||
Leaderboards.xp => await teto.fetchTetrioLeaderboard(after: after, lb: "xp"),
|
||||
Leaderboards.ar => await teto.fetchTetrioLeaderboard(after: after, lb: "ar"),
|
||||
Leaderboards.tl => await teto.fetchTetrioLeaderboard(prisecter: prisecter),
|
||||
Leaderboards.xp => await teto.fetchTetrioLeaderboard(prisecter: prisecter, lb: "xp"),
|
||||
Leaderboards.ar => await teto.fetchTetrioLeaderboard(prisecter: prisecter, lb: "ar"),
|
||||
Leaderboards.sprint => await teto.fetchTetrioRecordsLeaderboard(prisecter: prisecter),
|
||||
Leaderboards.blitz => await teto.fetchTetrioRecordsLeaderboard(prisecter: prisecter, lb: "blitz_global"),
|
||||
Leaderboards.zenith => await teto.fetchTetrioRecordsLeaderboard(prisecter: prisecter, lb: "zenith_global"),
|
||||
Leaderboards.zenithex => await teto.fetchTetrioRecordsLeaderboard(prisecter: prisecter, lb: "zenithex_global"),
|
||||
};
|
||||
|
||||
list.addAll(items);
|
||||
|
||||
_dataStreamController.add(list);
|
||||
after = switch (_currentLb){
|
||||
Leaderboards.tl => list.last.tr,
|
||||
Leaderboards.xp => list.last.xp,
|
||||
Leaderboards.ar => list.last.ar.toDouble(),
|
||||
};
|
||||
prisecter = list.last.prisecter.toString();
|
||||
} catch (e) {
|
||||
_dataStreamController.addError(e);
|
||||
} finally {
|
||||
|
@ -332,11 +348,7 @@ class _DestinationLeaderboardsState extends State<DestinationLeaderboards> {
|
|||
onTap: () {
|
||||
_currentLb = leaderboards.keys.elementAt(index);
|
||||
list.clear();
|
||||
after = switch (_currentLb){
|
||||
Leaderboards.tl => 25000.00,
|
||||
Leaderboards.xp => -1.00,
|
||||
Leaderboards.ar => -1.00,
|
||||
};
|
||||
prisecter = null;
|
||||
_fetchData();
|
||||
},
|
||||
),
|
||||
|
@ -350,7 +362,7 @@ class _DestinationLeaderboardsState extends State<DestinationLeaderboards> {
|
|||
SizedBox(
|
||||
width: widget.constraints.maxWidth - 350 - 88,
|
||||
child: Card(
|
||||
child: StreamBuilder<List<TetrioPlayerFromLeaderboard>>(
|
||||
child: StreamBuilder<List<dynamic>>(
|
||||
stream: dataStream,
|
||||
builder:(context, snapshot) {
|
||||
switch (snapshot.connectionState){
|
||||
|
@ -371,12 +383,25 @@ class _DestinationLeaderboardsState extends State<DestinationLeaderboards> {
|
|||
itemBuilder: (BuildContext context, int index){
|
||||
return ListTile(
|
||||
leading: Text(intf.format(index+1)),
|
||||
title: Text(snapshot.data![index].username),
|
||||
title: Text(snapshot.data![index].username, style: TextStyle(fontSize: 22)),
|
||||
trailing: Text(switch (_currentLb){
|
||||
Leaderboards.tl => f2.format(snapshot.data![index].tr),
|
||||
Leaderboards.xp => f2.format(snapshot.data![index].level),
|
||||
Leaderboards.ar => intf.format(snapshot.data![index].ar),
|
||||
}),
|
||||
Leaderboards.tl => "${f2.format(snapshot.data![index].tr)} TR",
|
||||
Leaderboards.xp => "LVL ${f2.format(snapshot.data![index].level)}",
|
||||
Leaderboards.ar => "${intf.format(snapshot.data![index].ar)} AR",
|
||||
Leaderboards.sprint => get40lTime(snapshot.data![index].stats.finalTime.inMicroseconds),
|
||||
Leaderboards.blitz => intf.format(snapshot.data![index].stats.score),
|
||||
Leaderboards.zenith => "${f2.format(snapshot.data![index].stats.zenith!.altitude)} m",
|
||||
Leaderboards.zenithex => "${f2.format(snapshot.data![index].stats.zenith!.altitude)} m"
|
||||
}, style: TextStyle(fontSize: 28)),
|
||||
subtitle: Text(switch (_currentLb){
|
||||
Leaderboards.tl => "${f2.format(snapshot.data![index].apm)} APM, ${f2.format(snapshot.data![index].pps)} PPS, ${f2.format(snapshot.data![index].vs)} VS, ${f2.format(snapshot.data![index].nerdStats.app)} APP, ${f2.format(snapshot.data![index].nerdStats.vsapm)} VS/APM",
|
||||
Leaderboards.xp => "${f2.format(snapshot.data![index].xp)} XP${snapshot.data![index].playtime.isNegative ? "" : ", ${playtime(snapshot.data![index].playtime)} of gametime"}",
|
||||
Leaderboards.ar => "${snapshot.data![index].ar_counts}",
|
||||
Leaderboards.sprint => "${intf.format(snapshot.data![index].stats.finesse.faults)} FF, ${f2.format(snapshot.data![index].stats.kpp)} KPP, ${f2.format(snapshot.data![index].stats.pps)} PPS, ${intf.format(snapshot.data![index].stats.piecesPlaced)} P",
|
||||
Leaderboards.blitz => "lvl ${snapshot.data![index].stats.level}, ${f2.format(snapshot.data![index].stats.pps)} PPS, ${f2.format(snapshot.data![index].stats.spp)} SPP",
|
||||
Leaderboards.zenith => "${f2.format(snapshot.data![index].aggregateStats.apm)} APM, ${f2.format(snapshot.data![index].aggregateStats.pps)} PPS, ${intf.format(snapshot.data![index].stats.kills)} KO's, ${f2.format(snapshot.data![index].stats.cps)} climb speed (${f2.format(snapshot.data![index].stats.zenith!.peakrank)} peak), ${intf.format(snapshot.data![index].stats.topBtB)} B2B",
|
||||
Leaderboards.zenithex => "${f2.format(snapshot.data![index].aggregateStats.apm)} APM, ${f2.format(snapshot.data![index].aggregateStats.pps)} PPS, ${intf.format(snapshot.data![index].stats.kills)} KO's, ${f2.format(snapshot.data![index].stats.cps)} climb speed (${f2.format(snapshot.data![index].stats.zenith!.peakrank)} peak), ${intf.format(snapshot.data![index].stats.topBtB)} B2B"
|
||||
}, style: TextStyle(color: Colors.grey, fontSize: 12)),
|
||||
);
|
||||
}
|
||||
),
|
||||
|
|
Loading…
Reference in New Issue