Tetra League Leaderboard
This commit is contained in:
parent
eae9bbedee
commit
9fb74f051c
|
@ -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'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
)
|
)
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
|
|
|
@ -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!),
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue