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/aggregate_stats.dart';
|
||||||
import 'package:tetra_stats/data_objects/record_extras.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/results_stats.dart';
|
||||||
|
import 'package:tetra_stats/data_objects/tetrio_prisecter.dart';
|
||||||
|
|
||||||
class RecordSingle {
|
class RecordSingle {
|
||||||
late String? userId;
|
late String? userId;
|
||||||
|
late String username;
|
||||||
late String replayId;
|
late String replayId;
|
||||||
late String ownId;
|
late String ownId;
|
||||||
late String gamemode;
|
late String gamemode;
|
||||||
|
@ -15,8 +17,9 @@ class RecordSingle {
|
||||||
late int countryRank;
|
late int countryRank;
|
||||||
late AggregateStats aggregateStats;
|
late AggregateStats aggregateStats;
|
||||||
late RecordExtras extras;
|
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) {
|
RecordSingle.fromJson(Map<String, dynamic> json, int ran, int cran) {
|
||||||
ownId = json['_id'];
|
ownId = json['_id'];
|
||||||
|
@ -24,10 +27,14 @@ class RecordSingle {
|
||||||
stats = ResultsStats.fromJson(json['results']['stats']);
|
stats = ResultsStats.fromJson(json['results']['stats']);
|
||||||
replayId = json['replayid'];
|
replayId = json['replayid'];
|
||||||
timestamp = DateTime.parse(json['ts']);
|
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;
|
rank = ran;
|
||||||
countryRank = cran;
|
countryRank = cran;
|
||||||
aggregateStats = AggregateStats.fromJson(json['results']['aggregatestats']);
|
aggregateStats = AggregateStats.fromJson(json['results']['aggregatestats']);
|
||||||
|
prisecter = Prisecter.fromJson(json['p']);
|
||||||
var ex = json['extras'] as Map<String, dynamic>;
|
var ex = json['extras'] as Map<String, dynamic>;
|
||||||
switch (ex.keys.firstOrNull){
|
switch (ex.keys.firstOrNull){
|
||||||
case "zenith":
|
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/nerd_stats.dart';
|
||||||
import 'package:tetra_stats/data_objects/playstyle.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_constants.dart';
|
||||||
|
import 'package:tetra_stats/data_objects/tetrio_prisecter.dart';
|
||||||
|
|
||||||
class TetrioPlayerFromLeaderboard {
|
class TetrioPlayerFromLeaderboard {
|
||||||
late String userId;
|
late String userId;
|
||||||
|
@ -33,6 +34,8 @@ class TetrioPlayerFromLeaderboard {
|
||||||
late int gamesWonTotal;
|
late int gamesWonTotal;
|
||||||
late Duration playtime;
|
late Duration playtime;
|
||||||
late int ar;
|
late int ar;
|
||||||
|
late Map<String, int> ar_counts;
|
||||||
|
late Prisecter prisecter;
|
||||||
|
|
||||||
TetrioPlayerFromLeaderboard(
|
TetrioPlayerFromLeaderboard(
|
||||||
this.userId,
|
this.userId,
|
||||||
|
@ -91,6 +94,11 @@ class TetrioPlayerFromLeaderboard {
|
||||||
gamesWonTotal = json['gameswon'] as int;
|
gamesWonTotal = json['gameswon'] as int;
|
||||||
playtime = Duration(microseconds: (json['gametime'].toDouble() * 1000000).floor());
|
playtime = Duration(microseconds: (json['gametime'].toDouble() * 1000000).floor());
|
||||||
ar = json['ar'];
|
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);
|
nerdStats = NerdStats(apm, pps, vs);
|
||||||
estTr = EstTr(apm, pps, vs, nerdStats.app, nerdStats.dss, nerdStats.dsp, nerdStats.gbe);
|
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);
|
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 {
|
Future<List<TetrioPlayerFromLeaderboard>> fetchTetrioLeaderboard({String? prisecter, String? lb}) async {
|
||||||
const int lbLength = 100;
|
|
||||||
// TetrioPlayersLeaderboard? cached = _cache.get("league", TetrioPlayersLeaderboard);
|
// TetrioPlayersLeaderboard? cached = _cache.get("league", TetrioPlayersLeaderboard);
|
||||||
// if (cached != null) return cached;
|
// if (cached != null) return cached;
|
||||||
|
|
||||||
|
@ -809,7 +808,7 @@ class TetrioService extends DB {
|
||||||
} else {
|
} else {
|
||||||
url = Uri.https('ch.tetr.io', 'api/users/by/${lb??"league"}', {
|
url = Uri.https('ch.tetr.io', 'api/users/by/${lb??"league"}', {
|
||||||
"limit": "100",
|
"limit": "100",
|
||||||
if (after != null && after != -1) "after": "$after:0:0"
|
if (prisecter != null) "after": prisecter
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
try{
|
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(){
|
TetrioPlayersLeaderboard? getCachedLeaderboard(){
|
||||||
return _cache.get("league", TetrioPlayersLeaderboard);
|
return _cache.get("league", TetrioPlayersLeaderboard);
|
||||||
}
|
}
|
||||||
|
|
|
@ -200,6 +200,7 @@ class _MainState extends State<MainView> with TickerProviderStateMixin {
|
||||||
0 => DestinationHome(searchFor: _searchFor, constraints: constraints),
|
0 => DestinationHome(searchFor: _searchFor, constraints: constraints),
|
||||||
1 => DestinationGraphs(searchFor: _searchFor, constraints: constraints),
|
1 => DestinationGraphs(searchFor: _searchFor, constraints: constraints),
|
||||||
2 => DestinationLeaderboards(constraints: constraints),
|
2 => DestinationLeaderboards(constraints: constraints),
|
||||||
|
3 => DestinationCutoffs(constraints: constraints),
|
||||||
_ => Text("Unknown destination $destination")
|
_ => Text("Unknown destination $destination")
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -215,14 +216,17 @@ class DestinationCutoffs extends StatefulWidget{
|
||||||
const DestinationCutoffs({super.key, required this.constraints});
|
const DestinationCutoffs({super.key, required this.constraints});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<DestinationLeaderboards> createState() => _DestinationCutoffsState();
|
State<DestinationCutoffs> createState() => _DestinationCutoffsState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _DestinationCutoffsState extends State<DestinationLeaderboards> {
|
class _DestinationCutoffsState extends State<DestinationCutoffs> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
// TODO: implement build
|
return Column(
|
||||||
throw UnimplementedError();
|
children: [
|
||||||
|
Card(),
|
||||||
|
]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -238,19 +242,31 @@ class DestinationLeaderboards extends StatefulWidget{
|
||||||
enum Leaderboards{
|
enum Leaderboards{
|
||||||
tl,
|
tl,
|
||||||
xp,
|
xp,
|
||||||
ar
|
ar,
|
||||||
|
sprint,
|
||||||
|
blitz,
|
||||||
|
zenith,
|
||||||
|
zenithex,
|
||||||
}
|
}
|
||||||
|
|
||||||
class _DestinationLeaderboardsState extends State<DestinationLeaderboards> {
|
class _DestinationLeaderboardsState extends State<DestinationLeaderboards> {
|
||||||
//Duration postSeasonLeft = seasonStart.difference(DateTime.now());
|
//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;
|
Leaderboards _currentLb = Leaderboards.tl;
|
||||||
final StreamController<List<TetrioPlayerFromLeaderboard>> _dataStreamController = StreamController<List<TetrioPlayerFromLeaderboard>>();
|
final StreamController<List<dynamic>> _dataStreamController = StreamController<List<dynamic>>();
|
||||||
late final ScrollController _scrollController;
|
late final ScrollController _scrollController;
|
||||||
Stream<List<TetrioPlayerFromLeaderboard>> get dataStream => _dataStreamController.stream;
|
Stream<List<dynamic>> get dataStream => _dataStreamController.stream;
|
||||||
List<TetrioPlayerFromLeaderboard> list = [];
|
List<dynamic> list = [];
|
||||||
bool _isFetchingData = false;
|
bool _isFetchingData = false;
|
||||||
double after = 25000.00;
|
String? prisecter;
|
||||||
|
|
||||||
Future<void> _fetchData() async {
|
Future<void> _fetchData() async {
|
||||||
if (_isFetchingData) {
|
if (_isFetchingData) {
|
||||||
|
@ -262,19 +278,19 @@ class _DestinationLeaderboardsState extends State<DestinationLeaderboards> {
|
||||||
setState(() {});
|
setState(() {});
|
||||||
|
|
||||||
final items = switch(_currentLb){
|
final items = switch(_currentLb){
|
||||||
Leaderboards.tl => await teto.fetchTetrioLeaderboard(after: after),
|
Leaderboards.tl => await teto.fetchTetrioLeaderboard(prisecter: prisecter),
|
||||||
Leaderboards.xp => await teto.fetchTetrioLeaderboard(after: after, lb: "xp"),
|
Leaderboards.xp => await teto.fetchTetrioLeaderboard(prisecter: prisecter, lb: "xp"),
|
||||||
Leaderboards.ar => await teto.fetchTetrioLeaderboard(after: after, lb: "ar"),
|
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);
|
list.addAll(items);
|
||||||
|
|
||||||
_dataStreamController.add(list);
|
_dataStreamController.add(list);
|
||||||
after = switch (_currentLb){
|
prisecter = list.last.prisecter.toString();
|
||||||
Leaderboards.tl => list.last.tr,
|
|
||||||
Leaderboards.xp => list.last.xp,
|
|
||||||
Leaderboards.ar => list.last.ar.toDouble(),
|
|
||||||
};
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_dataStreamController.addError(e);
|
_dataStreamController.addError(e);
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -332,11 +348,7 @@ class _DestinationLeaderboardsState extends State<DestinationLeaderboards> {
|
||||||
onTap: () {
|
onTap: () {
|
||||||
_currentLb = leaderboards.keys.elementAt(index);
|
_currentLb = leaderboards.keys.elementAt(index);
|
||||||
list.clear();
|
list.clear();
|
||||||
after = switch (_currentLb){
|
prisecter = null;
|
||||||
Leaderboards.tl => 25000.00,
|
|
||||||
Leaderboards.xp => -1.00,
|
|
||||||
Leaderboards.ar => -1.00,
|
|
||||||
};
|
|
||||||
_fetchData();
|
_fetchData();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -350,7 +362,7 @@ class _DestinationLeaderboardsState extends State<DestinationLeaderboards> {
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: widget.constraints.maxWidth - 350 - 88,
|
width: widget.constraints.maxWidth - 350 - 88,
|
||||||
child: Card(
|
child: Card(
|
||||||
child: StreamBuilder<List<TetrioPlayerFromLeaderboard>>(
|
child: StreamBuilder<List<dynamic>>(
|
||||||
stream: dataStream,
|
stream: dataStream,
|
||||||
builder:(context, snapshot) {
|
builder:(context, snapshot) {
|
||||||
switch (snapshot.connectionState){
|
switch (snapshot.connectionState){
|
||||||
|
@ -371,12 +383,25 @@ class _DestinationLeaderboardsState extends State<DestinationLeaderboards> {
|
||||||
itemBuilder: (BuildContext context, int index){
|
itemBuilder: (BuildContext context, int index){
|
||||||
return ListTile(
|
return ListTile(
|
||||||
leading: Text(intf.format(index+1)),
|
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){
|
trailing: Text(switch (_currentLb){
|
||||||
Leaderboards.tl => f2.format(snapshot.data![index].tr),
|
Leaderboards.tl => "${f2.format(snapshot.data![index].tr)} TR",
|
||||||
Leaderboards.xp => f2.format(snapshot.data![index].level),
|
Leaderboards.xp => "LVL ${f2.format(snapshot.data![index].level)}",
|
||||||
Leaderboards.ar => intf.format(snapshot.data![index].ar),
|
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