diff --git a/lib/data_objects/tetrio.dart b/lib/data_objects/tetrio.dart index 4d7970d..aad9899 100644 --- a/lib/data_objects/tetrio.dart +++ b/lib/data_objects/tetrio.dart @@ -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 json, DateTime stateTime, bool fetchRecords) { + TetrioPlayer.fromJson(Map 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 toJson() { diff --git a/lib/services/tetrio_crud.dart b/lib/services/tetrio_crud.dart index 48c07c6..3def9b3 100644 --- a/lib/services/tetrio_crud.dart +++ b/lib/services/tetrio_crud.dart @@ -30,6 +30,7 @@ const String createTetrioUsersToTrack = ''' class TetrioService extends DB { Map> _players = {}; final Map _playersCache = {}; + final Map> _recordsCache = {}; final Map _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> 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 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 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 fetchPlayer(String user, bool addToDB) async { + Future 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 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; }); diff --git a/lib/views/compare_view.dart b/lib/views/compare_view.dart index 312ceb5..40448fc 100644 --- a/lib/views/compare_view.dart +++ b/lib/views/compare_view.dart @@ -45,7 +45,7 @@ class CompareState extends State { void fetchRedSide(String user) async { try { - theRedSide = await teto.fetchPlayer(user, false); + theRedSide = await teto.fetchPlayer(user); late List states; try{ states = await teto.getPlayer(theRedSide!.userId); @@ -73,7 +73,7 @@ class CompareState extends State { void fetchGreenSide(String user) async { try { - theGreenSide = await teto.fetchPlayer(user, false); + theGreenSide = await teto.fetchPlayer(user); late List states; greenSideStates = null; try{ diff --git a/lib/views/main_view.dart b/lib/views/main_view.dart index 1444813..b87ed21 100644 --- a/lib/views/main_view.dart +++ b/lib/views/main_view.dart @@ -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 me; String _searchFor = "dan63047"; -Future? 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 createState() => _MainState(); @@ -38,6 +39,7 @@ class _MainState extends State with SingleTickerProviderStateMixin { final List 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 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 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 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 records = await teto.fetchRecords(me.userId); + return [me, records, isTracking]; + } + void _justUpdate() { setState(() {}); } @@ -173,7 +183,7 @@ class _MainState extends State with SingleTickerProviderStateMixin { ], ), body: SafeArea( - child: FutureBuilder( + child: FutureBuilder>( future: me, builder: (context, snapshot) { switch (snapshot.connectionState) { @@ -197,18 +207,13 @@ class _MainState extends State 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 with SingleTickerProviderStateMixin { controller: _tabController, isScrollable: true, tabs: myTabs, - onTap: (int tabId) { - setState(() {}); - }, ), ), ]; @@ -228,19 +230,20 @@ class _MainState extends State 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) ], ), );