From 8e7914f92f54d3967ca8dc91cacd21d9892d14ef Mon Sep 17 00:00:00 2001 From: Max L Date: Mon, 1 Apr 2024 00:02:56 -0700 Subject: [PATCH] retouch merge, compartmentalize classes --- lib/interfaces/bluealliance.dart | 85 ++++++++++++++++++------------- lib/interfaces/geoplugin.dart | 16 ++++++ lib/interfaces/localstore.dart | 18 +++---- lib/interfaces/supabase.dart | 41 +++++++-------- lib/pages/admin/matchinsight.dart | 19 ++++--- lib/pages/admin/statgraph.dart | 15 +++--- lib/pages/configuration.dart | 32 +++++++----- lib/pages/matchscout.dart | 71 +++++++++++--------------- lib/pages/pitscout.dart | 60 ++++++++++------------ lib/pages/savedresponses.dart | 19 +++---- 10 files changed, 198 insertions(+), 178 deletions(-) create mode 100644 lib/interfaces/geoplugin.dart diff --git a/lib/interfaces/bluealliance.dart b/lib/interfaces/bluealliance.dart index a686112..8ef7e7d 100644 --- a/lib/interfaces/bluealliance.dart +++ b/lib/interfaces/bluealliance.dart @@ -7,9 +7,6 @@ import 'package:stock/stock.dart'; import '../main.dart' show prefs; import 'localstore.dart'; -final qualificationMatchInfoPattern = RegExp(r'^(?qm)(?\d+)$'); -final finalsMatchInfoPattern = RegExp(r'^(?qf|sf|f)(?\d{1,2})m(?\d+)$'); - enum MatchLevel { qualification(compLevel: "qm"), quarterfinals(compLevel: "qf"), @@ -21,37 +18,54 @@ enum MatchLevel { static fromCompLevel(String s) => MatchLevel.values.firstWhere((type) => type.compLevel == s); } -typedef MatchInfo = ({MatchLevel level, int? finalnum, int index}); -MatchInfo? parseMatchInfo(String? s) { - if (s == null || s.isEmpty) return null; - RegExpMatch? match = - qualificationMatchInfoPattern.firstMatch(s) ?? finalsMatchInfoPattern.firstMatch(s); - if (match == null) return null; - return ( - level: MatchLevel.fromCompLevel(match.namedGroup("level")!), - finalnum: match.groupNames.contains("finalnum") - ? int.tryParse(match.namedGroup("finalnum") ?? "") - : null, - index: int.parse(match.namedGroup("index")!) - ); -} +class MatchInfo implements Comparable { + static final _qualificationPattern = RegExp(r'^(?qm)(?\d+)$'); + static final _finalsPattern = RegExp(r'^(?qf|sf|f)(?\d{1,2})m(?\d+)$'); + + final MatchLevel level; + final int? finalnum; + final int index; + MatchInfo({required this.level, this.finalnum, required this.index}); + factory MatchInfo.fromString(String s) { + RegExpMatch? match = _qualificationPattern.firstMatch(s) ?? _finalsPattern.firstMatch(s); + return MatchInfo( + level: MatchLevel.fromCompLevel(match!.namedGroup("level")!), + finalnum: match.groupNames.contains("finalnum") + ? int.tryParse(match.namedGroup("finalnum") ?? "") + : null, + index: int.parse(match.namedGroup("index")!)); + } -String stringifyMatchInfo(MatchInfo m) => - "${m.level.compLevel}${m.finalnum != null ? '${m.finalnum}m' : ''}${m.index}"; + @override + String toString() => "${level.compLevel}${finalnum != null ? '${finalnum}m' : ''}$index"; -int compareMatchInfo(MatchInfo a, MatchInfo b) => a.level != b.level - ? b.level.index - a.level.index - : a.finalnum != null && b.finalnum != null && a.finalnum != b.finalnum - ? b.finalnum! - a.finalnum! - : b.index - a.index; + @override + int compareTo(b) => level != b.level + ? b.level.index - level.index + : finalnum != null && b.finalnum != null && finalnum != b.finalnum + ? b.finalnum! - finalnum! + : b.index - index; -bool stringMatchIsLevel(String s, MatchLevel l) => s.startsWith(l.compLevel); + @override + int get hashCode => Object.hash(level, finalnum, index); -typedef TBAInfo = ({int season, String? event, String? match}); -String stringifyTBAInfo(TBAInfo t) => - t.season.toString() + - (t.event != null ? t.event! : "") + - (t.match != null ? "_${t.match!}" : ""); + @override + bool operator ==(Object other) => + other is MatchInfo && + other.level == level && + other.finalnum == finalnum && + other.index == index; +} + +class TBAInfo { + final int season; + final String? event, match; + TBAInfo({required this.season, this.event, this.match}); + + @override + String toString() => + season.toString() + (event != null ? event! : "") + (match != null ? "_${match!}" : ""); +} typedef OPRData = ({double? opr, double? dpr, double? ccwms}); @@ -101,7 +115,7 @@ class BlueAlliance { }).catchError((_) => false); } - static final stockSoT = LocalSourceOfTruth("tba"); + static final stockSoT = LocalSourceOfTruth("tba"); static final stock = Stock>( sourceOfTruth: stockSoT.mapTo>( (p) => p.map((k, v) => MapEntry(k, v.toString())), (p) => p), @@ -129,7 +143,8 @@ class BlueAlliance { for (MapEntry alliance in Map.from(data['alliances']).entries) { for (MapEntry team in List.from(alliance.value['team_keys']) - .followedBy(List.from(alliance.value['surrogate_team_keys'])) + .followedBy(List.from(alliance.value[ + 'surrogate_team_keys'])) // surrogate additions- i have no idea if they work this way .toList(growable: false) .asMap() .entries) { @@ -162,10 +177,10 @@ class BlueAlliance { } String matchkey = (matchdata['key'] as String).split("_").last; matches[matchkey] = matchdata['key']; - await stockSoT.write((season: season, event: event, match: matchkey), o); + await stockSoT.write(TBAInfo(season: season, event: event, match: matchkey), o); } - await stockSoT.write((season: season, event: event, match: null), matches); - await stockSoT.write((season: season, event: event, match: "*"), pitTeams); + await stockSoT.write(TBAInfo(season: season, event: event), matches); + await stockSoT.write(TBAInfo(season: season, event: event, match: "*"), pitTeams); } static final _oprStockSoT = diff --git a/lib/interfaces/geoplugin.dart b/lib/interfaces/geoplugin.dart new file mode 100644 index 0000000..4207cde --- /dev/null +++ b/lib/interfaces/geoplugin.dart @@ -0,0 +1,16 @@ +import 'dart:convert'; + +import 'package:http/http.dart' show Client; + +typedef Coordinates = ({double lat, double long}); + +class GeoPlugin { + static final _client = Client(); + static final _url = Uri.http('geoplugin.net', '/json.gp'); + + static Future get() => _client + .get(_url) + .then((resp) => Map.from(jsonDecode(resp.body))) + .then((data) => + (lat: data['geoplugin_latitude'] as double, long: data['geoplugin_longitude'] as double)); +} diff --git a/lib/interfaces/localstore.dart b/lib/interfaces/localstore.dart index 6ddb43f..d94b96b 100644 --- a/lib/interfaces/localstore.dart +++ b/lib/interfaces/localstore.dart @@ -4,7 +4,6 @@ import 'package:localstore/localstore.dart'; import 'package:stock/stock.dart'; import '../pages/matchscout.dart' show MatchScoutInfoSerialized; -import 'bluealliance.dart'; class LocalStoreInterface { static final _db = Localstore.instance; @@ -31,15 +30,16 @@ class LocalStoreInterface { _db.collection("scout").get().then((value) => value?.keys.toSet() ?? {}); } -class LocalSourceOfTruth implements SourceOfTruth> { +class LocalSourceOfTruth implements SourceOfTruth> { final CollectionRef collection; final StreamController<({String key, Map? value})> _stream = StreamController.broadcast(); - LocalSourceOfTruth(String key) : collection = LocalStoreInterface._db.collection(key); + LocalSourceOfTruth(String collection) + : collection = LocalStoreInterface._db.collection(collection); @override - Future delete(TBAInfo key) { - String s = stringifyTBAInfo(key); + Future delete(K key) { + String s = key.toString(); _stream.add((key: s, value: null)); return collection.doc(s).delete(); } @@ -53,15 +53,15 @@ class LocalSourceOfTruth implements SourceOfTruth> }).then((_) => collection.delete()); @override - Stream?> reader(TBAInfo key) async* { - String s = stringifyTBAInfo(key); + Stream?> reader(K key) async* { + String s = key.toString(); yield* collection.doc(s).get().asStream(); yield* _stream.stream.where((e) => e.key == s).map((e) => e.value); } @override - Future write(TBAInfo key, Map? value) { - String s = stringifyTBAInfo(key); + Future write(K key, Map? value) { + String s = key.toString(); _stream.add((key: s, value: value)); return collection.doc(s).set(value ?? {}); } diff --git a/lib/interfaces/supabase.dart b/lib/interfaces/supabase.dart index e33851a..8cbb052 100644 --- a/lib/interfaces/supabase.dart +++ b/lib/interfaces/supabase.dart @@ -5,7 +5,7 @@ import 'package:supabase_flutter/supabase_flutter.dart'; import '../pages/configuration.dart'; import '../pages/matchscout.dart' hide MatchScoutPage; -import 'bluealliance.dart' show MatchInfo, parseMatchInfo; +import 'bluealliance.dart' show MatchInfo; typedef AggInfo = ({int season, String? event, String? team}); @@ -123,36 +123,37 @@ class SupabaseInterface { .map((k, v) => MapEntry(k, Map.from(v))); if (key.event != null && key.team != null) { return LinkedHashMap>.of( - response.map((k, v) => MapEntry(parseMatchInfo(k)!, Map.from(v[key.team])))); + response.map((k, v) => MapEntry(MatchInfo.fromString(k), Map.from(v[key.team])))); // match: {scoretype: aggregated_count} } if (key.team != null) { return LinkedHashMap.fromEntries(response.entries .map((evententry) => Map.from(evententry.value).map( (matchstring, matchscores) => MapEntry( - (event: evententry.key, match: parseMatchInfo(matchstring)!), + (event: evententry.key, match: MatchInfo.fromString(matchstring)), Map.from(matchscores)))) .expand((e) => e.entries)); } throw UnimplementedError("No aggregate for that combination"); })); - - static final eventAggregateStock = Stock<({int season, String event}), LinkedHashMap>(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.from(resp.data) - ..removeWhere((key, value) => value == null)) - .cast() - .entries - .toList() - ..sort((a, b) => a.value == b.value - ? 0 - : a.value > b.value - ? -1 - : 1))) - )); + + static final eventAggregateStock = + Stock<({int season, String event}), LinkedHashMap>( + 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.from(resp.data) + ..removeWhere((key, value) => value == null)) + .cast() + .entries + .toList() + ..sort((a, b) => a.value == b.value + ? 0 + : a.value > b.value + ? -1 + : 1))))); static final distinctStock = Stock< AggInfo, diff --git a/lib/pages/admin/matchinsight.dart b/lib/pages/admin/matchinsight.dart index c479468..cb95196 100644 --- a/lib/pages/admin/matchinsight.dart +++ b/lib/pages/admin/matchinsight.dart @@ -15,15 +15,18 @@ class MatchInsightPage extends StatelessWidget { @override Widget build(BuildContext context) => FutureBuilder( future: BlueAlliance.stock - .get((season: Configuration.instance.season, event: Configuration.event!, match: null)) + .get(TBAInfo(season: Configuration.instance.season, event: Configuration.event!)) .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>>())), + .get(TBAInfo( + 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>>())), builder: (context, snapshot) => !snapshot.hasData ? snapshot.hasError ? Column(mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ diff --git a/lib/pages/admin/statgraph.dart b/lib/pages/admin/statgraph.dart index b3d0ca9..c85efe7 100644 --- a/lib/pages/admin/statgraph.dart +++ b/lib/pages/admin/statgraph.dart @@ -370,15 +370,15 @@ class TeamAtEventGraph extends StatelessWidget { future: Future.wait([ SupabaseInterface.matchAggregateStock.get((season: season, event: event, team: team)), BlueAlliance.stock - .get((season: season, event: event, match: null)) + .get(TBAInfo(season: season, event: event)) .then((eventMatches) => Future.wait(eventMatches.keys.map((match) => BlueAlliance.stock - .get((season: season, event: event, match: match)).then( - (teams) => teams.keys.contains(team) ? match : null)))) + .get(TBAInfo(season: season, event: event, match: match)) + .then((teams) => teams.keys.contains(team) ? match : null)))) .then((unparsedEventMatches) => unparsedEventMatches - .where((match) => match != null) - .map((match) => parseMatchInfo(match)!) + .whereType() + .map((match) => MatchInfo.fromString(match)) .toList() - ..sort((a, b) => compareMatchInfo(b, a))) + ..sort((b, a) => a.compareTo(b))) ]).then((results) => ( data: results[0] as LinkedHashMap>, ordinalMatches: results[1] as List @@ -412,8 +412,7 @@ class TeamAtEventGraph extends StatelessWidget { } return SideTitleWidget( axisSide: AxisSide.bottom, - child: - Text(stringifyMatchInfo(snapshot.data!.ordinalMatches[n]))); + child: Text(snapshot.data!.ordinalMatches[n].toString())); })), leftTitles: const AxisTitles( axisNameWidget: Text("Score"), diff --git a/lib/pages/configuration.dart b/lib/pages/configuration.dart index 99ad46c..68966c3 100644 --- a/lib/pages/configuration.dart +++ b/lib/pages/configuration.dart @@ -1,4 +1,3 @@ -import 'package:birdseye/utils.dart'; import 'package:carousel_slider/carousel_slider.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -6,6 +5,7 @@ import 'package:flutter/material.dart'; import '../interfaces/bluealliance.dart'; import '../interfaces/supabase.dart'; import '../main.dart' show prefs; +import '../utils.dart'; class ConfigurationPage extends StatelessWidget { final _eventCarouselController = CarouselController(); @@ -32,10 +32,12 @@ class ConfigurationPage extends StatelessWidget { ]) : const LinearProgressIndicator(); } - int index = snapshot.data! - .indexWhere((element) => element == Configuration.instance.season); - if (Configuration.instance.season < 0 || index < 0) { - index = snapshot.data!.length - 1; + int maybeSeason = Configuration.instance.season < 0 + ? DateTime.now().year + : Configuration.instance.season; + int index = snapshot.data!.indexWhere((element) => element == maybeSeason); + if (index < 0) index = snapshot.data!.length - 1; + if (snapshot.data![index] != Configuration.instance.season) { WidgetsBinding.instance.addPostFrameCallback( (_) => Configuration.instance.season = snapshot.data![index]); } @@ -64,8 +66,8 @@ class ConfigurationPage extends StatelessWidget { builder: (context, child) => Configuration.instance.season < 0 ? const Center(child: CircularProgressIndicator()) : FutureBuilder( - future: BlueAlliance.stock.get( - (season: Configuration.instance.season, event: null, match: null)), + future: BlueAlliance.stock + .get(TBAInfo(season: Configuration.instance.season)), builder: (context, snapshot) { if (!snapshot.hasData) { return snapshot.hasError @@ -93,12 +95,13 @@ class ConfigurationPage extends StatelessWidget { const Text("No Events Found") ]); } - int index = 0; - if (Configuration.event == null) { + String maybeEvent = Configuration.event ?? + entries[entries.length ~/ 2].key; // TODO autofill event goes here + int index = + entries.indexWhere((element) => element.key == maybeEvent); + if (index < 0) index = snapshot.data!.length ~/ 2; + if (entries[index].key != Configuration.event) { Configuration.event = entries[index].key; - } else { - index = snapshot.data!.keys.toList().indexOf(Configuration.event!); - if (index < 0) index = (entries.length / 2).round(); } return ListenableBuilder( listenable: _carouselProgress, @@ -179,6 +182,7 @@ class Configuration extends ChangeNotifier { Future get isValid async => season >= 0 && event != null && - await BlueAlliance.stock.get((season: season, event: null, match: null)).then( - (value) => value.containsKey(event)); + await BlueAlliance.stock + .get(TBAInfo(season: season)) + .then((value) => value.containsKey(event)); } diff --git a/lib/pages/matchscout.dart b/lib/pages/matchscout.dart index 42571c5..d4fc75c 100644 --- a/lib/pages/matchscout.dart +++ b/lib/pages/matchscout.dart @@ -4,11 +4,11 @@ import 'dart:collection'; import 'package:flutter/material.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; -import '../utils.dart'; -import './configuration.dart'; import '../interfaces/bluealliance.dart'; import '../interfaces/localstore.dart'; import '../interfaces/supabase.dart'; +import '../utils.dart'; +import './configuration.dart'; typedef MatchScoutQuestionSchema = LinkedHashMap>; @@ -206,7 +206,7 @@ class _MatchScoutPageState extends State with WidgetsBindingObse submitInfo(( season: Configuration.instance.season, event: Configuration.event!, - match: stringifyMatchInfo(currmatch), + match: currmatch.toString(), team: info.team! ), fields: _fields) .then((_) async { @@ -214,11 +214,9 @@ class _MatchScoutPageState extends State with WidgetsBindingObse if (currmatch.level == MatchLevel.qualification && currmatch.index < info.highestQual) { info.team = null; - info.match = ( - level: MatchLevel.qualification, - finalnum: null, - index: currmatch.index + 1 - ); + info.match = MatchInfo( + level: MatchLevel.qualification, + index: currmatch.index + 1); } else { info.resetInfo(); } @@ -277,13 +275,13 @@ class MatchScoutInfo { : matchController = TextEditingController(), teamController = ValueNotifier(null) { refreshMatches(BlueAlliance.stock - .get((season: Configuration.instance.season, event: Configuration.event, match: null))); + .get(TBAInfo(season: Configuration.instance.season, event: Configuration.event))); } Future refreshMatches(Future> resp) => resp.then((matchesdata) => matches = LinkedHashMap.fromEntries( - matchesdata.keys.map((k) => MapEntry(k, parseMatchInfo(k)!)).toList() - ..sort((a, b) => compareMatchInfo(a.value, b.value)))); + matchesdata.keys.map((k) => MapEntry(k, MatchInfo.fromString(k))).toList() + ..sort((a, b) => a.value.compareTo(b.value)))); int highestQual = -1; LinkedHashMap? _matches; @@ -309,20 +307,22 @@ class MatchScoutInfo { return _match = teams = null; } if (matches == null) return; - if (m == match || !matches!.containsValue(m)) return; - String mstr = stringifyMatchInfo(m); + if (m == match) return; + if (!matches!.containsValue(m)) { + return _match = teams = null; + } + String mstr = m.toString(); matchController.text = mstr; _match = m; teams = null; Future> tbaDataFuture = m.level != MatchLevel.qualification && BlueAlliance.dirtyConnected - ? BlueAlliance.stock - .fresh((season: Configuration.instance.season, event: Configuration.event, match: mstr)) - : BlueAlliance.stock.get(( + ? BlueAlliance.stock.fresh( + TBAInfo(season: Configuration.instance.season, event: Configuration.event, match: mstr)) + : BlueAlliance.stock.get(TBAInfo( season: Configuration.instance.season, event: Configuration.event, - match: mstr - )); // keep fetching latest info for finals + match: mstr)); // keep fetching latest info for finals SupabaseInterface.canConnect .then((conn) => conn ? SupabaseInterface.getSessions(match: mstr) : Future.value({})) @@ -337,14 +337,9 @@ class MatchScoutInfo { }).catchError((e) => teams = null); } - String? getMatchStr() => match == null ? null : stringifyMatchInfo(match!); - void setMatchStr(String? m) { - if (parseMatchInfo(m) != null) { - match = parseMatchInfo(m); // invoke the other setter - } else { - teams = null; - } - } + String? getMatchStr() => match?.toString(); + void setMatchStr(String? m) => + match = m == null ? null : MatchInfo.fromString(m); // invoke the other setter NotifiableChangeNotifier teamsController = NotifiableChangeNotifier(); LinkedHashMap? _teams; @@ -426,12 +421,10 @@ class MatchScoutInfoFields extends StatelessWidget { IconButton( onPressed: () { if (info.highestQual < 1) return; - info.match = ( - level: MatchLevel.qualification, - finalnum: null, - index: (info.match == null ? 1 : info.match!.index + 1) - .clamp(1, info.highestQual) - ); + info.match = MatchInfo( + level: MatchLevel.qualification, + index: (info.match == null ? 1 : info.match!.index + 1) + .clamp(1, info.highestQual)); }, constraints: const BoxConstraints(maxHeight: 22), iconSize: 28, @@ -441,14 +434,12 @@ class MatchScoutInfoFields extends StatelessWidget { IconButton( onPressed: () { if (info.highestQual < 1) return; - info.match = ( - level: MatchLevel.qualification, - finalnum: null, - index: (info.match == null - ? info.highestQual - : info.match!.index - 1) - .clamp(1, info.highestQual) - ); + info.match = MatchInfo( + level: MatchLevel.qualification, + index: (info.match == null + ? info.highestQual + : info.match!.index - 1) + .clamp(1, info.highestQual)); }, constraints: const BoxConstraints(maxHeight: 22), iconSize: 28, diff --git a/lib/pages/pitscout.dart b/lib/pages/pitscout.dart index f39196d..7151937 100644 --- a/lib/pages/pitscout.dart +++ b/lib/pages/pitscout.dart @@ -2,12 +2,12 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; -import '../utils.dart'; -import './configuration.dart'; -import './metadata.dart'; import '../interfaces/bluealliance.dart'; import '../interfaces/localstore.dart'; import '../interfaces/supabase.dart'; +import '../utils.dart'; +import './configuration.dart'; +import './metadata.dart'; Future?> _getPrevious(int team) => Supabase.instance.client .from("pit_data_${Configuration.instance.season}") @@ -45,24 +45,22 @@ class _PitScoutPageState extends State { Future> _getUnfilled() { if (unfilled != null) return Future.value(unfilled); return BlueAlliance.stock - .get((season: Configuration.instance.season, event: Configuration.event, match: "*")) + .get(TBAInfo(season: Configuration.instance.season, event: Configuration.event, match: "*")) .then((data) => Set.of(data.keys.map(int.parse))) .then((teams) async { - Set filledteams = await Supabase.instance.client - .from("pit_data_${Configuration.instance.season}") - .select("team") - .eq("event", Configuration.event!) - .withConverter((value) => value.map((e) => e['team']).toSet()) - .catchError((_) => {}); - if (UserMetadata.instance.team != null) { - filledteams.add(UserMetadata.instance.team!); - } - return teams.difference(filledteams).toList()..sort(); - }) - .then((teams) { - return unfilled = teams; - }) - .catchError((e) => []); + Set filledteams = await Supabase.instance.client + .from("pit_data_${Configuration.instance.season}") + .select("team") + .eq("event", Configuration.event!) + .withConverter((value) => value.map((e) => e['team']).toSet()) + .catchError((_) => {}); + if (UserMetadata.instance.team != null) { + filledteams.add(UserMetadata.instance.team!); + } + return teams.difference(filledteams).toList()..sort(); + }).then((teams) { + return unfilled = teams; + }).catchError((e) => []); } @override @@ -140,23 +138,19 @@ class _PitScoutPageState extends State { onChanged: (value) { _team.value = null; BlueAlliance.stock - .get(( - season: Configuration.instance.season, - event: Configuration.event, - match: "*" - )) + .get(TBAInfo( + season: Configuration.instance.season, + event: Configuration.event, + match: "*")) .then((data) => Set.of(data.keys) .contains(value)) .then((isValid) { - _teamFieldError = - isValid ? "" : "Invalid"; - _teamFieldKey.currentState!.validate(); - }) - .catchError((e) { - ScaffoldMessenger.of(context) - .showSnackBar(SnackBar( - content: Text(e.toString()))); - }); + _teamFieldError = isValid ? "" : "Invalid"; + _teamFieldKey.currentState!.validate(); + }).catchError((e) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(e.toString()))); + }); }, onFieldSubmitted: (String? value) async { if (value == null) return; diff --git a/lib/pages/savedresponses.dart b/lib/pages/savedresponses.dart index 3067051..9a50f4b 100644 --- a/lib/pages/savedresponses.dart +++ b/lib/pages/savedresponses.dart @@ -1,13 +1,13 @@ -import 'package:birdseye/interfaces/supabase.dart'; import 'package:flutter/material.dart'; import 'package:flutter_swipe_action_cell/core/cell.dart'; -import '../utils.dart'; -import './matchscout.dart' as matchscout; -import './pitscout.dart' as pitscout; import '../interfaces/bluealliance.dart'; import '../interfaces/localstore.dart'; +import '../interfaces/supabase.dart'; import '../pages/configuration.dart'; +import '../utils.dart'; +import './matchscout.dart' as matchscout; +import './pitscout.dart' as pitscout; class SavedResponsesPage extends StatelessWidget { final _list = _WrappedList([]); @@ -169,13 +169,10 @@ class CacheAddWidget extends StatelessWidget { listenable: _cacheStatus, builder: (context, _) => Text(_cacheStatus.value)), const SizedBox(height: 8), FutureBuilder( - future: BlueAlliance.stock.get(( - season: Configuration.instance.season, - event: null, - match: null - )).then((events) => events.keys - .map((eventcode) => DropdownMenuItem(value: eventcode, child: Text(eventcode))) - .toList(growable: false)), + future: BlueAlliance.stock.get(TBAInfo(season: Configuration.instance.season)).then( + (events) => events.keys + .map((eventcode) => DropdownMenuItem(value: eventcode, child: Text(eventcode))) + .toList(growable: false)), builder: (context, snapshot) => ListenableBuilder( listenable: _dropdownValue, builder: (context, _) => Row(children: [