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 'dart:math';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:vector_math/vector_math.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 noTrRd = 60.9;
const double apmWeight = 1; 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; 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"); //developer.log("TetrioPlayer.fromJson $stateTime: $json", name: "data_objects/tetrio");
userId = json['_id']; userId = json['_id'];
username = json['username']; username = json['username'];
@ -112,25 +109,6 @@ class TetrioPlayer {
friendCount = json['friend_count'] ?? 0; friendCount = json['friend_count'] ?? 0;
badstanding = json['badstanding']; badstanding = json['badstanding'];
botmaster = json['botmaster']; 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() { Map<String, dynamic> toJson() {

View File

@ -30,6 +30,7 @@ const String createTetrioUsersToTrack = '''
class TetrioService extends DB { 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, 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;
@ -94,10 +95,6 @@ class TetrioService extends DB {
if (jsonDecode(response.body)['success']) { if (jsonDecode(response.body)['success']) {
TetraLeagueAlphaStream stream = TetraLeagueAlphaStream.fromJson( TetraLeagueAlphaStream stream = TetraLeagueAlphaStream.fromJson(
jsonDecode(response.body)['data']['records'], userID); jsonDecode(response.body)['data']['records'], userID);
// if (addToDB) {
// await ensureDbIsOpen();
// storeState(player);
// }
developer.log("getTLStream: $userID stream retrieved and cached", name: "services/tetrio_crud"); developer.log("getTLStream: $userID stream retrieved and cached", name: "services/tetrio_crud");
_tlStreamsCache[jsonDecode(response.body)['cache']['cached_until'].toString()] = stream; _tlStreamsCache[jsonDecode(response.body)['cache']['cached_until'].toString()] = stream;
return 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 { Future<void> createPlayer(TetrioPlayer tetrioPlayer) async {
ensureDbIsOpen(); ensureDbIsOpen();
final db = getDatabaseOrThrow(); final db = getDatabaseOrThrow();
@ -216,7 +254,7 @@ class TetrioService extends DB {
} else { } else {
dynamic rawStates = results.first['jsonStates'] as String; dynamic rawStates = results.first['jsonStates'] as String;
rawStates = json.decode(rawStates); 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.removeWhere((key, value) => key == id);
_players.addEntries({states.last.userId: states}.entries); _players.addEntries({states.last.userId: states}.entries);
_tetrioStreamController.add(_players); _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{ try{
var cached = _playersCache.entries.firstWhere((element) => element.value.userId == user || element.value.username == user); 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())){ 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 (response.statusCode == 200) {
if (jsonDecode(response.body)['success']) { if (jsonDecode(response.body)['success']) {
TetrioPlayer player = TetrioPlayer.fromJson( TetrioPlayer player = TetrioPlayer.fromJson(
jsonDecode(response.body)['data']['user'], DateTime.fromMillisecondsSinceEpoch(jsonDecode(response.body)['cache']['cached_at'], isUtc: true), true); jsonDecode(response.body)['data']['user'], DateTime.fromMillisecondsSinceEpoch(jsonDecode(response.body)['cache']['cached_at'], isUtc: true));
if (addToDB) {
await ensureDbIsOpen();
storeState(player);
}
developer.log("fetchPlayer: $user retrieved and cached", name: "services/tetrio_crud"); developer.log("fetchPlayer: $user retrieved and cached", name: "services/tetrio_crud");
_playersCache[jsonDecode(response.body)['cache']['cached_until'].toString()] = player; _playersCache[jsonDecode(response.body)['cache']['cached_until'].toString()] = player;
return player; return player;
@ -272,7 +306,7 @@ class TetrioService extends DB {
// what the fuck am i doing here? // what the fuck am i doing here?
var test = json.decode(row['jsonStates'] as String); var test = json.decode(row['jsonStates'] as String);
List<TetrioPlayer> states = []; 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); data.addEntries({states.last.userId: states}.entries);
return data; return data;
}); });

View File

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