import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:tetra_stats/data_objects/cutoff_tetrio.dart'; import 'package:tetra_stats/data_objects/news.dart'; import 'package:tetra_stats/data_objects/p1nkl0bst3r.dart'; import 'package:tetra_stats/data_objects/player_leaderboard_position.dart'; import 'package:tetra_stats/data_objects/summaries.dart'; import 'package:tetra_stats/data_objects/tetra_league.dart'; import 'package:tetra_stats/data_objects/tetrio_constants.dart'; import 'package:tetra_stats/data_objects/tetrio_player.dart'; import 'package:tetra_stats/data_objects/tetrio_players_leaderboard.dart'; import 'package:tetra_stats/gen/strings.g.dart'; import 'package:tetra_stats/services/crud_exceptions.dart'; import 'package:tetra_stats/views/destination_calculator.dart'; import 'package:tetra_stats/views/destination_cutoffs.dart'; import 'package:tetra_stats/views/destination_graphs.dart'; import 'package:tetra_stats/views/destination_home.dart'; import 'package:tetra_stats/views/destination_info.dart'; import 'package:tetra_stats/views/destination_leaderboards.dart'; import 'package:tetra_stats/views/destination_saved_data.dart'; import 'package:tetra_stats/views/destination_settings.dart'; import 'package:tetra_stats/main.dart'; late Future _data; TetrioPlayersLeaderboard? _everyone; Future getData(String searchFor) async { TetrioPlayer player; try{ if (searchFor.startsWith("ds:")){ player = await teto.fetchPlayer(searchFor.substring(3), isItDiscordID: true); // we trying to get him with that }else{ player = await teto.fetchPlayer(searchFor); // Otherwise it's probably a user id or username } }on TetrioPlayerNotExist{ return FetchResults(false, null, [], null, null, null, null, null, false, TetrioPlayerNotExist()); } late Summaries summaries; late News? news; late Cutoffs? cutoffs; late CutoffsTetrio? averages; try { List requests = await Future.wait([ teto.fetchSummaries(player.userId), teto.fetchNews(player.userId), teto.fetchCutoffsBeanserver(), if (prefs.getBool("showAverages") == true) teto.fetchCutoffsTetrio() ]); summaries = requests[0]; news = requests[1]; cutoffs = requests.elementAtOrNull(2); averages = requests.elementAtOrNull(3); } on Exception catch (e) { return FetchResults(false, null, [], null, null, null, null, null, false, e); } PlayerLeaderboardPosition? _meAmongEveryone; if (prefs.getBool("showPositions") == true){ // Get tetra League leaderboard _everyone = teto.getCachedLeaderboard(); _everyone ??= await teto.fetchTLLeaderboard(); if (_everyone!.leaderboard.isNotEmpty){ _meAmongEveryone = await compute(_everyone!.getLeaderboardPosition, {player.userId: summaries.league}); if (_meAmongEveryone != null) teto.cacheLeaderboardPositions(player.userId, _meAmongEveryone); } } List states = await teto.getStates(player.userId, season: currentSeason); bool isTracking = await teto.isPlayerTracking(player.userId); if (isTracking){ // if tracked - save data to local DB await teto.storeState(summaries.league); } return FetchResults(true, player, states, summaries, news, cutoffs, averages, _meAmongEveryone, isTracking, null); } class MainView extends StatefulWidget { final String? player; /// The very first view, that user see when he launch this programm. /// By default it loads my or defined in preferences user stats, but /// if [player] username or id provided, it loads his stats. Also it hides menu drawer and three dots menu. const MainView({super.key, this.player}); @override State createState() => _MainState(); } enum Cards {overview, tetraLeague, quickPlay, sprint, blitz} enum CardMod {info, records, ex, exRecords} Map cardsTitles = { Cards.overview: "Overview", Cards.tetraLeague: t.tetraLeague, Cards.quickPlay: t.quickPlay, //Cards.quickPlayExpert: "${t.quickPlay} ${t.expert}", Cards.sprint: t.sprint, Cards.blitz: t.blitz, //Cards.other: t.other }; late ScrollController controller; class _MainState extends State with TickerProviderStateMixin { int destination = 0; String _searchFor = "6098518e3d5155e6ec429cdc"; final TextEditingController _searchController = TextEditingController(); @override void initState() { teto.open(); controller = ScrollController(); changePlayer(_searchFor); super.initState(); } void changePlayer(String player) { setState(() { _searchFor = player; _data = getData(_searchFor); }); } @override void dispose() { controller.dispose(); _searchController.dispose(); super.dispose(); } NavigationRailDestination getDestinationButton(IconData icon, String title){ return NavigationRailDestination( icon: Tooltip( message: title, child: Icon(icon) ), selectedIcon: Icon(icon), label: Text(title), ); } @override Widget build(BuildContext context) { return Scaffold( drawer: SearchDrawer(changePlayer: changePlayer, controller: _searchController), body: LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ TweenAnimationBuilder( child: NavigationRail( leading: FloatingActionButton( elevation: 0, onPressed: () { Scaffold.of(context).openDrawer(); }, child: const Icon(Icons.search), ), trailing: IconButton( onPressed: () { // Add your onPressed code here! }, icon: const Icon(Icons.refresh), ), destinations: [ getDestinationButton(Icons.home, "Home"), getDestinationButton(Icons.data_thresholding_outlined, "Graphs"), getDestinationButton(Icons.leaderboard, "Leaderboards"), getDestinationButton(Icons.compress, "Cutoffs"), getDestinationButton(Icons.calculate, "Calc"), getDestinationButton(Icons.info_outline, "Information"), getDestinationButton(Icons.storage, "Saved Data"), getDestinationButton(Icons.settings, "Settings"), ], selectedIndex: destination, onDestinationSelected: (value) { setState(() { destination = value; }); }, ), duration: Durations.long4, tween: Tween(begin: 0, end: 1), curve: Easing.standard, builder: (context, value, child) { return Container( transform: Matrix4.translationValues(-80+value*80, 0, 0), child: Opacity(opacity: value, child: child), ); }, ), Expanded( child: switch (destination){ 0 => DestinationHome(searchFor: _searchFor, constraints: constraints, dataFuture: _data), 1 => DestinationGraphs(searchFor: _searchFor, constraints: constraints), 2 => DestinationLeaderboards(constraints: constraints), 3 => DestinationCutoffs(constraints: constraints), 4 => DestinationCalculator(constraints: constraints), 5 => DestinationInfo(constraints: constraints), 6 => DestinationSavedData(constraints: constraints), 7 => DestinationSettings(constraints: constraints), _ => Text("Unknown destination $destination") }, ) ]); }, )); } } class SearchDrawer extends StatefulWidget{ final Function changePlayer; final TextEditingController controller; const SearchDrawer({super.key, required this.changePlayer, required this.controller}); @override State createState() => _SearchDrawerState(); } class _SearchDrawerState extends State { @override Widget build(BuildContext context) { return Drawer( child: StreamBuilder( stream: teto.allPlayers, builder: (context, snapshot) { switch (snapshot.connectionState) { case ConnectionState.none: case ConnectionState.waiting: case ConnectionState.done: case ConnectionState.active: final allPlayers = (snapshot.data != null) ? snapshot.data as Map : {}; allPlayers.remove(prefs.getString("playerID") ?? "6098518e3d5155e6ec429cdc"); // player from the home button will be delisted List keys = allPlayers.keys.toList(); return NestedScrollView( headerSliverBuilder: (BuildContext context, bool value){ return [ SliverToBoxAdapter( child: SearchBar( controller: widget.controller, hintText: "Enter the username", hintStyle: const WidgetStatePropertyAll(TextStyle(color: Colors.grey)), trailing: [ IconButton(onPressed: (){setState(() { widget.changePlayer(widget.controller.value.text); Navigator.of(context).pop(); });}, icon: const Icon(Icons.search)) ], onSubmitted: (value) { setState(() { widget.changePlayer(value); Navigator.of(context).pop(); }); }, ), ), SliverToBoxAdapter( child: ListTile( leading: Icon(Icons.home), title: Text(prefs.getString("player") ?? "dan63"), onTap: () { widget.changePlayer(prefs.getString("playerID") ?? "6098518e3d5155e6ec429cdc"); Navigator.of(context).pop(); }, ), ), SliverToBoxAdapter( child: Divider(), ), SliverToBoxAdapter( child: Padding( padding: const EdgeInsets.only(left: 10.0), child: Text("Tracked Players", style: Theme.of(context).textTheme.headlineLarge), ), ) ]; }, body: ListView.builder( // Builds list of tracked players. itemCount: allPlayers.length, itemBuilder: (context, index) { var i = allPlayers.length-1-index; // Last players in this map are most recent ones, they are gonna be shown at the top. return ListTile( title: Text(allPlayers[keys[i]]??keys[i]), // Takes last known username from list of states trailing: IconButton(onPressed: (){ teto.deletePlayerToTrack(keys[i]); }, icon: Icon(Icons.delete, color: Colors.grey)), onTap: () { widget.changePlayer(keys[i]); // changes to chosen player Navigator.of(context).pop(); // and closes itself. }, ); }) ); } } ) ); } } // class EstTrThingy extends StatelessWidget{ // final EstTr estTr; // const EstTrThingy({super.key, required this.estTr}); // @override // Widget build(BuildContext context) { // return const Card( // //child: , // ); // } // }