Skip to content

Commit

Permalink
add matchinsights, fix defensive ranking
Browse files Browse the repository at this point in the history
  • Loading branch information
MaxAPCS committed Mar 28, 2024
1 parent 1b01cc8 commit 07670b9
Show file tree
Hide file tree
Showing 9 changed files with 204 additions and 174 deletions.
3 changes: 3 additions & 0 deletions lib/interfaces/bluealliance.dart
Original file line number Diff line number Diff line change
Expand Up @@ -313,3 +313,6 @@ const scoringpoints2024 = {
"endgame_spotlit": 4,
"comments_fouls": -2
};

const frcred = Color(0xffed1c24);
const frcblue = Color(0xff0066b3);
19 changes: 18 additions & 1 deletion lib/interfaces/supabase.dart
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ class SupabaseInterface {
: Future.value(_achievements);
static void clearAchievements() => _achievements = null;

static final aggregateStock = Stock<AggInfo, LinkedHashMap<dynamic, Map<String, num>>>(
static final matchAggregateStock = Stock<AggInfo, LinkedHashMap<dynamic, Map<String, num>>>(
sourceOfTruth: CachedSourceOfTruth(),
fetcher: Fetcher.ofFuture((key) async {
assert(key.event != null || key.team != null);
Expand Down Expand Up @@ -136,6 +136,23 @@ class SupabaseInterface {
}
throw UnimplementedError("No aggregate for that combination");
}));

static final eventAggregateStock = Stock<({int season, String event}), LinkedHashMap<String, double>>(sourceOfTruth: CachedSourceOfTruth(),fetcher: Fetcher.ofFuture((key) =>
Supabase.instance.client.functions
.invoke("event_aggregator?season=${key.season}&event=${key.event}")
.then((resp) => resp.status >= 400
? throw Exception("HTTP Error ${resp.status}")
: LinkedHashMap.fromEntries((Map<String, double?>.from(resp.data)
..removeWhere((key, value) => value == null))
.cast<String, double>()
.entries
.toList()
..sort((a, b) => a.value == b.value
? 0
: a.value > b.value
? -1
: 1)))
));

static final distinctStock = Stock<
AggInfo,
Expand Down
101 changes: 43 additions & 58 deletions lib/pages/admin/admin.dart
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import 'package:birdseye/pages/admin/nextmatch.dart';
import 'package:birdseye/pages/admin/matchinsight.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';

import '../../main.dart';
import '../../pages/metadata.dart';
import 'achievementqueue.dart';
import 'qualitativeanalysis.dart';
import 'statgraph.dart';

final _adminNavigatorKey = GlobalKey<NavigatorState>();
Expand Down Expand Up @@ -40,23 +39,16 @@ final adminGoRoute = GoRoute(
UserMetadata.instance.cachedPermissions.value.achievementApprover
? null
: state.namedLocation(RoutePaths.adminportal.name)),
GoRoute(
parentNavigatorKey: _adminNavigatorKey,
path: AdminRoutePaths.qualanaly.name,
name: AdminRoutePaths.qualanaly.name,
pageBuilder: (context, state) => const MaterialPage(
child: QualitativeAnalysisPage(), name: "Qualitative Analysis"),
redirect: (context, state) =>
UserMetadata.instance.cachedPermissions.value.qualitativeAnalyzer
? null
: state.namedLocation(RoutePaths.adminportal.name)),
GoRoute(
parentNavigatorKey: _adminNavigatorKey,
path: AdminRoutePaths.nextmatch.name,
name: AdminRoutePaths.nextmatch.name,
pageBuilder: (context, state) => const MaterialPage(
child: NextMatchPage(), name: "Next Match"),
redirect: (context, state) => null), // TODOX: add perm checking list prev routes
pageBuilder: (context, state) =>
MaterialPage(child: MatchInsightPage(), name: "Match Insight"),
redirect: (context, state) =>
UserMetadata.instance.cachedPermissions.value.graphViewer
? null
: state.namedLocation(RoutePaths.adminportal.name))
]),
GoRoute(
path: AdminRoutePaths.statgraphs.name,
Expand All @@ -72,48 +64,41 @@ class AdminScaffoldShell extends StatelessWidget {
final Widget child;
const AdminScaffoldShell(this.child, {super.key});

static Drawer drawer() => Drawer(
width: 250,
child: ListenableBuilder(
listenable: UserMetadata.instance.cachedPermissions,
builder: (context, _) => Column(children: [
ListTile(
leading: const Icon(Icons.chevron_left_rounded),
title: const Text("Back"),
onTap: () => GoRouter.of(context)
..pop()
..goNamed(RoutePaths.configuration.name)),
const Divider(),
ListTile(
leading: const Icon(Icons.auto_graph_rounded),
title: const Text("Stat Graphs"),
enabled: UserMetadata.instance.cachedPermissions.value.graphViewer,
onTap: () => GoRouter.of(context)
..pop()
..goNamed(AdminRoutePaths.statgraphs.name)),
ListTile(
leading: const Icon(Icons.queue_rounded),
title: const Text("Achievement Queue"),
enabled: UserMetadata.instance.cachedPermissions.value.achievementApprover,
onTap: () => GoRouter.of(context)
..pop()
..goNamed(AdminRoutePaths.achiqueue.name)),
ListTile(
leading: const Icon(Icons.visibility),
title: const Text("Match Insight"),
enabled: UserMetadata.instance.cachedPermissions.value.graphViewer,
onTap: () => GoRouter.of(context)
..pop()
..goNamed(AdminRoutePaths.nextmatch.name)),
])));

@override
Widget build(BuildContext context) => Scaffold(
drawer: Drawer(
width: 250,
child: ListenableBuilder(
listenable: UserMetadata.instance.cachedPermissions,
builder: (context, _) => Column(children: [
ListTile(
leading: const Icon(Icons.chevron_left_rounded),
title: const Text("Back"),
onTap: () => GoRouter.of(context)
..pop()
..goNamed(RoutePaths.configuration.name)),
const Divider(),
ListTile(
leading: const Icon(Icons.auto_graph_rounded),
title: const Text("Stat Graphs"),
enabled: UserMetadata.instance.cachedPermissions.value.graphViewer,
onTap: () => GoRouter.of(context)
..pop()
..goNamed(AdminRoutePaths.statgraphs.name)),
ListTile(
leading: const Icon(Icons.queue_rounded),
title: const Text("Achievement Queue"),
enabled: UserMetadata.instance.cachedPermissions.value.achievementApprover,
onTap: () => GoRouter.of(context)
..pop()
..goNamed(AdminRoutePaths.achiqueue.name)),
ListTile(
leading: const Icon(Icons.manage_search_rounded),
title: const Text("Qualitative Analysis"),
enabled: UserMetadata.instance.cachedPermissions.value.qualitativeAnalyzer,
onTap: () => GoRouter.of(context)
..pop()
..goNamed(AdminRoutePaths.qualanaly.name)),
ListTile(
leading: const Icon(Icons.visibility),
title: const Text("Next Match"),
// enabled: UserMetadata.instance.cachedPermissions.value.nextMatchViewer, // TODOX: add perm checking like prev routes
onTap: () => GoRouter.of(context)
..pop()
..goNamed(AdminRoutePaths.nextmatch.name)),
]))),
body: child);
Widget build(BuildContext context) => Scaffold(drawer: drawer(), body: child);
}
78 changes: 78 additions & 0 deletions lib/pages/admin/matchinsight.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import 'dart:collection';

import 'package:flutter/material.dart';

import '../../interfaces/bluealliance.dart';
import '../admin/statgraph.dart';
import '../configuration.dart';
import '../matchscout.dart';
import '../metadata.dart';

class MatchInsightPage extends StatelessWidget {
final ValueNotifier<String?> _selectedMatch = ValueNotifier(null);
MatchInsightPage({super.key});

@override
Widget build(BuildContext context) => FutureBuilder(
future: BlueAlliance.stock
.get((season: Configuration.instance.season, event: Configuration.event!, match: null))
.then((eventMatches) => Future.wait(eventMatches.keys.map((matchCode) => BlueAlliance.stock
.get((season: Configuration.instance.season, event: Configuration.event!, match: matchCode)).then(
(matchTeams) => matchTeams.keys.any(
(teamKey) => teamKey.startsWith(UserMetadata.instance.team!.toString()))
? MapEntry(matchCode, matchTeams)
: null))))
.then((teamMatches) =>
LinkedHashMap.fromEntries(teamMatches.whereType<MapEntry<String, Map<String, String>>>())),
builder: (context, snapshot) => !snapshot.hasData
? snapshot.hasError
? Column(mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [
Icon(Icons.warning_rounded, color: Colors.red[700], size: 50),
const SizedBox(height: 20),
Text(snapshot.error.toString())
])
: const Center(child: CircularProgressIndicator())
: ListenableBuilder(
listenable: _selectedMatch,
builder: (context, _) => CustomScrollView(slivers: [
SliverAppBar(title: const Text("Match Insight"), actions: [
DropdownButton(
items: snapshot.data!.keys
.map((matchCode) =>
DropdownMenuItem(value: matchCode, child: Text(matchCode)))
.toList(),
value: _selectedMatch.value,
onChanged: (matchCode) => _selectedMatch.value = matchCode)
]),
SliverSafeArea(
minimum: const EdgeInsets.only(bottom: 12, left: 12, right: 12),
sliver: SliverGrid.count(
crossAxisCount: 2,
childAspectRatio: MediaQuery.of(context).size.width > 600 ? 2 : 1 / 2,
crossAxisSpacing: 8,
children: [
if (_selectedMatch.value != null)
for (var team in snapshot.data![_selectedMatch.value]!.entries
.map((matchTeam) => MapEntry(matchTeam.key,
RobotPositionChip.robotPositionFromString(matchTeam.value)))
.toList(growable: false)
..sort((a, b) =>
(a.value.ordinal * 2 + (a.value.isRedAlliance ? 1 : 0)) -
(b.value.ordinal * 2 + (b.value.isRedAlliance ? 1 : 0))))
Column(crossAxisAlignment: CrossAxisAlignment.center, children: [
Text(
team.key,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.titleLarge!.copyWith(
color: team.value.isRedAlliance ? frcred : frcblue),
),
Expanded(
child: Transform.scale(
scale: 2 / 3,
child: TeamInSeasonGraph(
season: Configuration.instance.season,
team: team.key)))
])
]))
])));
}
8 changes: 0 additions & 8 deletions lib/pages/admin/nextmatch.dart

This file was deleted.

10 changes: 0 additions & 10 deletions lib/pages/admin/qualitativeanalysis.dart

This file was deleted.

Loading

0 comments on commit 07670b9

Please sign in to comment.