315 lines
12 KiB
Dart
315 lines
12 KiB
Dart
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<FetchResults> _data;
|
|
TetrioPlayersLeaderboard? _everyone;
|
|
|
|
Future<FetchResults> 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<dynamic> 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<TetraLeague> 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<MainView> createState() => _MainState();
|
|
}
|
|
|
|
enum Cards {overview, tetraLeague, quickPlay, sprint, blitz}
|
|
enum CardMod {info, records, ex, exRecords}
|
|
Map<Cards, String> 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<MainView> 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<double>(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<SearchDrawer> createState() => _SearchDrawerState();
|
|
}
|
|
|
|
class _SearchDrawerState extends State<SearchDrawer> {
|
|
@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<String, String>
|
|
: <String, String>{};
|
|
allPlayers.remove(prefs.getString("playerID") ?? "6098518e3d5155e6ec429cdc"); // player from the home button will be delisted
|
|
List<String> 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: ,
|
|
// );
|
|
// }
|
|
// } |