Main view now finally have a proper fetch function

Also prepared for making TL history function
This commit is contained in:
dan63047 2023-06-22 01:05:14 +03:00
parent ffbe76e5cc
commit 974294f167
4 changed files with 75 additions and 60 deletions

View File

@ -1,9 +1,6 @@
import 'dart:math';
import 'package:flutter/foundation.dart';
import 'package:vector_math/vector_math.dart';
import 'dart:developer' as developer;
import 'package:http/http.dart' as http;
import 'dart:convert';
const double noTrRd = 60.9;
const double apmWeight = 1;
@ -84,7 +81,7 @@ class TetrioPlayer {
double get level => pow((xp / 500), 0.6) + (xp / (5000 + (max(0, xp - 4 * pow(10, 6)) / 5000))) + 1;
TetrioPlayer.fromJson(Map<String, dynamic> json, DateTime stateTime, bool fetchRecords) {
TetrioPlayer.fromJson(Map<String, dynamic> json, DateTime stateTime) {
//developer.log("TetrioPlayer.fromJson $stateTime: $json", name: "data_objects/tetrio");
userId = json['_id'];
username = json['username'];
@ -112,25 +109,6 @@ class TetrioPlayer {
friendCount = json['friend_count'] ?? 0;
badstanding = json['badstanding'];
botmaster = json['botmaster'];
if (fetchRecords) {
var url = Uri.https('ch.tetr.io', 'api/users/$userId/records');
Future response = http.get(url);
response.then((value) {
if (value.statusCode == 200) {
Map jsonRecords = jsonDecode(value.body);
sprint = jsonRecords['data']['records']['40l']['record'] != null
? [RecordSingle.fromJson(jsonRecords['data']['records']['40l']['record'], jsonRecords['data']['records']['40l']['rank'])]
: [];
blitz = jsonRecords['data']['records']['blitz']['record'] != null
? [RecordSingle.fromJson(jsonRecords['data']['records']['blitz']['record'], jsonRecords['data']['records']['blitz']['rank'])]
: [];
zen = TetrioZen.fromJson(jsonRecords['data']['zen']);
} else {
developer.log("TetrioPlayer.fromJson exception", name: "data_objects/tetrio", error: value.statusCode);
throw Exception('Failed to fetch player');
}
});
}
}
Map<String, dynamic> toJson() {

View File

@ -30,6 +30,7 @@ const String createTetrioUsersToTrack = '''
class TetrioService extends DB {
Map<String, List<TetrioPlayer>> _players = {};
final Map<String, TetrioPlayer> _playersCache = {};
final Map<String, Map<String, dynamic>> _recordsCache = {};
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();
factory TetrioService() => _shared;
@ -94,10 +95,6 @@ class TetrioService extends DB {
if (jsonDecode(response.body)['success']) {
TetraLeagueAlphaStream stream = TetraLeagueAlphaStream.fromJson(
jsonDecode(response.body)['data']['records'], userID);
// if (addToDB) {
// await ensureDbIsOpen();
// storeState(player);
// }
developer.log("getTLStream: $userID stream retrieved and cached", name: "services/tetrio_crud");
_tlStreamsCache[jsonDecode(response.body)['cache']['cached_until'].toString()] = stream;
return stream;
@ -111,6 +108,47 @@ class TetrioService extends DB {
}
}
Future<Map<String, dynamic>> fetchRecords(String userID) async {
try{
var cached = _recordsCache.entries.firstWhere((element) => element.value['user'] == userID);
if (DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true).isAfter(DateTime.now())){
developer.log("fetchRecords: $userID records retrieved from cache, that expires ${DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true)}", name: "services/tetrio_crud");
return cached.value;
}else{
_recordsCache.remove(cached.key);
developer.log("fetchRecords: $userID records expired (${DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true)})", name: "services/tetrio_crud");
}
}catch(e){
developer.log("fetchRecords: Trying to retrieve $userID records", name: "services/tetrio_crud");
}
var url = Uri.https('ch.tetr.io', 'api/users/${userID.toLowerCase().trim()}/records');
final response = await http.get(url);
if (response.statusCode == 200) {
if (jsonDecode(response.body)['success']) {
Map jsonRecords = jsonDecode(response.body);
var sprint = jsonRecords['data']['records']['40l']['record'] != null
? [RecordSingle.fromJson(jsonRecords['data']['records']['40l']['record'], jsonRecords['data']['records']['40l']['rank'])]
: [];
var blitz = jsonRecords['data']['records']['blitz']['record'] != null
? [RecordSingle.fromJson(jsonRecords['data']['records']['blitz']['record'], jsonRecords['data']['records']['blitz']['rank'])]
: [];
var zen = TetrioZen.fromJson(jsonRecords['data']['zen']);
Map<String, dynamic> map = {"user": userID.toLowerCase().trim(), "sprint": sprint, "blitz": blitz, "zen": zen};
developer.log("fetchRecords: $userID stream retrieved and cached", name: "services/tetrio_crud");
_recordsCache[jsonDecode(response.body)['cache']['cached_until'].toString()] = map;
return map;
} else {
developer.log("fetchRecords User dosen't exist", name: "services/tetrio_crud", error: response.body);
throw Exception("User doesn't exist");
}
} else {
developer.log("fetchRecords Failed to fetch records", name: "services/tetrio_crud", error: response.statusCode);
throw Exception('Failed to fetch player');
}
}
Future<void> createPlayer(TetrioPlayer tetrioPlayer) async {
ensureDbIsOpen();
final db = getDatabaseOrThrow();
@ -216,7 +254,7 @@ class TetrioService extends DB {
} else {
dynamic rawStates = results.first['jsonStates'] as String;
rawStates = json.decode(rawStates);
rawStates.forEach((k, v) => states.add(TetrioPlayer.fromJson(v, DateTime.fromMillisecondsSinceEpoch(int.parse(k)), false)));
rawStates.forEach((k, v) => states.add(TetrioPlayer.fromJson(v, DateTime.fromMillisecondsSinceEpoch(int.parse(k)))));
_players.removeWhere((key, value) => key == id);
_players.addEntries({states.last.userId: states}.entries);
_tetrioStreamController.add(_players);
@ -224,7 +262,7 @@ class TetrioService extends DB {
}
}
Future<TetrioPlayer> fetchPlayer(String user, bool addToDB) async {
Future<TetrioPlayer> fetchPlayer(String user) async {
try{
var cached = _playersCache.entries.firstWhere((element) => element.value.userId == user || element.value.username == user);
if (DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true).isAfter(DateTime.now())){
@ -244,11 +282,7 @@ class TetrioService extends DB {
if (response.statusCode == 200) {
if (jsonDecode(response.body)['success']) {
TetrioPlayer player = TetrioPlayer.fromJson(
jsonDecode(response.body)['data']['user'], DateTime.fromMillisecondsSinceEpoch(jsonDecode(response.body)['cache']['cached_at'], isUtc: true), true);
if (addToDB) {
await ensureDbIsOpen();
storeState(player);
}
jsonDecode(response.body)['data']['user'], DateTime.fromMillisecondsSinceEpoch(jsonDecode(response.body)['cache']['cached_at'], isUtc: true));
developer.log("fetchPlayer: $user retrieved and cached", name: "services/tetrio_crud");
_playersCache[jsonDecode(response.body)['cache']['cached_until'].toString()] = player;
return player;
@ -272,7 +306,7 @@ class TetrioService extends DB {
// what the fuck am i doing here?
var test = json.decode(row['jsonStates'] as String);
List<TetrioPlayer> states = [];
test.forEach((k, v) => states.add(TetrioPlayer.fromJson(v, DateTime.fromMillisecondsSinceEpoch(int.parse(k)), false)));
test.forEach((k, v) => states.add(TetrioPlayer.fromJson(v, DateTime.fromMillisecondsSinceEpoch(int.parse(k)))));
data.addEntries({states.last.userId: states}.entries);
return data;
});

View File

@ -45,7 +45,7 @@ class CompareState extends State<CompareView> {
void fetchRedSide(String user) async {
try {
theRedSide = await teto.fetchPlayer(user, false);
theRedSide = await teto.fetchPlayer(user);
late List<TetrioPlayer> states;
try{
states = await teto.getPlayer(theRedSide!.userId);
@ -73,7 +73,7 @@ class CompareState extends State<CompareView> {
void fetchGreenSide(String user) async {
try {
theGreenSide = await teto.fetchPlayer(user, false);
theGreenSide = await teto.fetchPlayer(user);
late List<TetrioPlayer> states;
greenSideStates = null;
try{

View File

@ -10,8 +10,9 @@ import 'package:tetra_stats/widgets/stat_sell_num.dart';
import 'package:tetra_stats/widgets/tl_thingy.dart';
import 'package:tetra_stats/widgets/user_thingy.dart';
late Future<List> me;
String _searchFor = "dan63047";
Future<TetrioPlayer>? me;
String _titleNickname = "dan63047";
final TetrioService teto = TetrioService();
late SharedPreferences prefs;
const allowedHeightForPlayerIdInPixels = 40.0;
@ -23,7 +24,7 @@ final NumberFormat f2 = NumberFormat.decimalPatternDigits(decimalDigits: 2);
class MainView extends StatefulWidget {
const MainView({Key? key}) : super(key: key);
String get title => "Tetra Stats: $_searchFor";
String get title => "Tetra Stats: $_titleNickname";
@override
State<MainView> createState() => _MainState();
@ -38,6 +39,7 @@ class _MainState extends State<MainView> with SingleTickerProviderStateMixin {
final List<Widget> myTabs = [
const Tab(text: "Tetra League"),
const Tab(text: "TL Records"),
const Tab(text: "TL History"),
const Tab(text: "40 Lines"),
const Tab(text: "Blitz"),
const Tab(text: "Other"),
@ -77,7 +79,7 @@ class _MainState extends State<MainView> with SingleTickerProviderStateMixin {
void initState() {
teto.open();
_scrollController = ScrollController();
_tabController = TabController(length: 5, vsync: this);
_tabController = TabController(length: 6, vsync: this);
_getPreferences()
.then((value) => changePlayer(prefs.getString("player") ?? "dan63047"));
super.initState();
@ -96,12 +98,20 @@ class _MainState extends State<MainView> with SingleTickerProviderStateMixin {
void changePlayer(String player) {
setState(() {
_tabController.animateTo(0, duration: const Duration(milliseconds: 300));
_searchFor = player;
me = teto.fetchPlayer(player, false);
me = fetch(_searchFor);
});
}
Future<List> fetch(String nickOrID) async {
TetrioPlayer me = await teto.fetchPlayer(nickOrID);
setState((){_titleNickname = me.username;});
bool isTracking = await teto.isPlayerTracking(nickOrID);
if (isTracking) teto.storeState(me);
Map<String, dynamic> records = await teto.fetchRecords(me.userId);
return [me, records, isTracking];
}
void _justUpdate() {
setState(() {});
}
@ -173,7 +183,7 @@ class _MainState extends State<MainView> with SingleTickerProviderStateMixin {
],
),
body: SafeArea(
child: FutureBuilder<TetrioPlayer>(
child: FutureBuilder<List<dynamic>>(
future: me,
builder: (context, snapshot) {
switch (snapshot.connectionState) {
@ -197,18 +207,13 @@ class _MainState extends State<MainView> with SingleTickerProviderStateMixin {
case ConnectionState.done:
//bool bigScreen = MediaQuery.of(context).size.width > 1024;
if (snapshot.hasData) {
if (_searchFor.length > 16)
_searchFor = snapshot.data!.username;
teto.isPlayerTracking(snapshot.data!.userId).then((value) {
if (value) teto.storeState(snapshot.data!);
});
return NestedScrollView(
controller: _scrollController,
headerSliverBuilder: (context, value) {
return [
SliverToBoxAdapter(
child: UserThingy(
player: snapshot.data!,
player: snapshot.data![0],
showStateTimestamp: false,
setState: _justUpdate,
)),
@ -217,9 +222,6 @@ class _MainState extends State<MainView> with SingleTickerProviderStateMixin {
controller: _tabController,
isScrollable: true,
tabs: myTabs,
onTap: (int tabId) {
setState(() {});
},
),
),
];
@ -228,19 +230,20 @@ class _MainState extends State<MainView> with SingleTickerProviderStateMixin {
controller: _tabController,
children: [
TLThingy(
tl: snapshot.data!.tlSeason1,
userID: snapshot.data!.userId),
_TLRecords(userID: snapshot.data!.userId),
tl: snapshot.data![0].tlSeason1,
userID: snapshot.data![0].userId),
_TLRecords(userID: snapshot.data![0].userId),
Text("kekwa"),
_RecordThingy(
record: (snapshot.data!.sprint.isNotEmpty)
? snapshot.data!.sprint[0]
record: (snapshot.data![1]['sprint'].isNotEmpty)
? snapshot.data![1]['sprint'][0]
: null),
_RecordThingy(
record: (snapshot.data!.blitz.isNotEmpty)
? snapshot.data!.blitz[0]
record: (snapshot.data![1]['blitz'].isNotEmpty)
? snapshot.data![1]['blitz'][0]
: null),
_OtherThingy(
zen: snapshot.data!.zen, bio: snapshot.data!.bio)
zen: snapshot.data![1]['zen'], bio: snapshot.data![0].bio)
],
),
);