diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 854f2c8..0e24c20 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -17,11 +17,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.0.1 + 1.1.0 CFBundleSignature ???? CFBundleVersion - 2 + 1 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/lib/views/app/TournamentList.dart b/lib/views/app/TournamentList.dart index fd20b5f..2809146 100644 --- a/lib/views/app/TournamentList.dart +++ b/lib/views/app/TournamentList.dart @@ -14,7 +14,7 @@ import "package:ezra_companion/views/tournament/TournamentView.dart"; /// Make a request to Ezra and get a list of tournaments /// TODO: Store the results of this locally, check for new tournaments every so often (every week?) Future> fetchTournaments(http.Client client) async { - final response = await client.get('https://www.ezratech.us/api/tournaments/search?appOptIn=true'); + final response = await client.get('https://www.ezratech.us/api/tournaments/search'); if (response.statusCode == 200) { // Use the compute function to run parseTournaments diff --git a/lib/views/tournament/TournamentView.dart b/lib/views/tournament/TournamentView.dart index 74edfe3..464ddb2 100644 --- a/lib/views/tournament/TournamentView.dart +++ b/lib/views/tournament/TournamentView.dart @@ -115,6 +115,7 @@ class _TournamentViewState extends State { updateSubscriptionStatus: _updatePublicSubscriptionStatus ); _tournamentResults = new TournamentResults( + fileStorage: _fileStorage, tournamentInfo: widget.tournamentInfo ); _tournamentSchedule = new TournamentSchedule( @@ -179,8 +180,19 @@ class _TournamentViewState extends State { // Build the widget @override Widget build(BuildContext context) { - final double padding = _selectedIndex != 1 ? 15.0 : 0.0; - final TextTheme textTheme = Theme.of(context).textTheme; + // Ignore the maps and results page for padding + double padding; + switch(_selectedIndex) { + case 1: + padding = 0.0; + break; + case 4: + padding = 5.0; + break; + default: + padding = 15.0; + } + final TextTheme textTheme = Theme.of(context).textTheme; return FutureBuilder( future: initializeTournament(), diff --git a/lib/views/tournament/tabs/TournamentMap.dart b/lib/views/tournament/tabs/TournamentMap.dart index 8fabaa3..ef14217 100644 --- a/lib/views/tournament/tabs/TournamentMap.dart +++ b/lib/views/tournament/tabs/TournamentMap.dart @@ -41,11 +41,26 @@ class TournamentMap extends StatelessWidget { } } else { - return Column( - children: [ - Text('No map!'), - Text('View more ${tournamentInfo.name}'), - ], + TextStyle pageTextStyle = new TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.w600, + color: Colors.black87, + ); + return Padding( + padding: new EdgeInsets.all(25.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + new Icon(Icons.map), + SizedBox(height: 30), + new Text( + "No map has been provided for this tournament; ask your organizers to upload one, then you'll see it here!", + style: pageTextStyle, + textAlign: TextAlign.center + ), + ] + ) ); } } diff --git a/lib/views/tournament/tabs/TournamentResults.dart b/lib/views/tournament/tabs/TournamentResults.dart index 67c575e..f8579d4 100644 --- a/lib/views/tournament/tabs/TournamentResults.dart +++ b/lib/views/tournament/tabs/TournamentResults.dart @@ -1,41 +1,296 @@ +import 'package:async/async.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'dart:convert'; + +import 'package:http/http.dart' as http; // Consume the Ezra results API, allow people to view results on teams and events from here // Class imports import "package:ezra_companion/classes/TournamentListItem.dart"; -class TournamentResults extends StatelessWidget { +class TournamentResults extends StatefulWidget { + final fileStorage; final TournamentListItem tournamentInfo; const TournamentResults({ Key key, + this.fileStorage, this.tournamentInfo, }) : super(key: key); @override - Widget build(BuildContext context) { - TextStyle textStyle = new TextStyle( - fontSize: 16.0, - fontWeight: FontWeight.w600, - color: Colors.black87, - ); - return Padding( - padding: new EdgeInsets.all(25.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - new Icon(Icons.stars), - SizedBox(height: 30), - new Text( - "Results from this tournament will be available after the conclusion of the tournament. Check back here to see them when they've been released!", - style: textStyle, - textAlign: TextAlign.center + _TournamentResultsState createState() => _TournamentResultsState(); +} + +// https://stackoverflow.com/questions/51998995/invalid-arguments-illegal-argument-in-isolate-message-object-is-a-closure +/// A function that converts a response body into a List +List parseApiResponse(String responseBody) { + final decodedBody = json.decode(responseBody); + if (decodedBody is List && decodedBody.length > 0) { + return decodedBody; + } + else { + return []; + } +} + +class _TournamentResultsState extends State { + final AsyncMemoizer _memoizer = AsyncMemoizer(); + + int _dropdownIndex; + + List _eventNames; + + TextStyle _rowTextStyle = new TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.w600, + color: Colors.black87, + ); + + void initState() { + _dropdownIndex = 0; + _eventNames = []; + } + + List createTableRowsFromIndex(List data, int eventIndex) { + // Event data is not normally sorted from the API, this sorts it by place within the event + if (eventIndex < 998) { + data.sort((teamA, teamB) { + return teamA["name"].compareTo(teamB["name"]); + }); + } + else { + data.sort((teamA, teamB) { + int teamAScore = teamA["finalScore"]; + int teamBScore = teamB["finalScore"]; + return teamAScore - teamBScore; + }); + } + + // Create a list of rows based on each team's performance in this event (or a final aspect) + List tableRows = List.from(data.map((team) { + String teamName = team["name"]; + String teamPlace; + + List teamEvents = team["events"]; + + // Sort each event by name (this is already done by Ezra, but sometimes it isn't perfect for some reason) + teamEvents.sort((eventA, eventB) { + return eventA["eventName"].compareTo(eventB["eventName"]); + }); + + // Determine where to look for information that will populate the current row + if (eventIndex == 998) { + // Final scores + teamPlace = "${team["finalScore"] != null ? team["finalScore"] : "N/A"}"; + } + else if (eventIndex == 999) { + // Final places + teamPlace = "${team["finalPlace"] != null ? team["finalPlace"] : "N/A"}"; + } + else { + // Regular event + Map currentEvent = teamEvents[eventIndex]; + teamPlace = "${currentEvent["place"]}"; + } + + // Create a TableRow + return TableRow( + children: [ + TableCell( + child: Padding( + padding: const EdgeInsets.all(9.0), + child: Center( + child: Text( + teamName, + style: _rowTextStyle, + textAlign: TextAlign.center, + ) + ) + ), + verticalAlignment: TableCellVerticalAlignment.middle, + ), + TableCell( + child: Padding( + padding: const EdgeInsets.all(9.0), + child: Center( + child: Text( + teamPlace, + style: _rowTextStyle, + textAlign: TextAlign.center, + ) + ) + ), + verticalAlignment: TableCellVerticalAlignment.middle, + ), + ], + ); + })); + + // Insert header rows for the table + tableRows.insert( + 0, + TableRow( + decoration: BoxDecoration( + color: Colors.red + ), + children: [ + TableCell( + child: Padding( + padding: const EdgeInsets.all(9.0), + child: Center( + child: Text( + "Team Name", + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.w600, + color: Colors.white, + ), + textAlign: TextAlign.center, + ) + ) + ), + verticalAlignment: TableCellVerticalAlignment.middle, + ), + TableCell( + child: Padding( + padding: const EdgeInsets.all(9.0), + child: Center( + child: Text( + "Score", + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.w600, + color: Colors.white, + ), + textAlign: TextAlign.center, + ) + ) + ), + verticalAlignment: TableCellVerticalAlignment.middle, ), ] ) ); + return tableRows; + } + + // Get event names + List getDropdownChoices(List data) { + int eventIndex = 0; + List dropdownChoices = List.from( + data[0]["events"].map((currentEvent) { + DropdownMenuItem item = DropdownMenuItem( + child: Text(currentEvent["eventName"]), + value: eventIndex, + ); + eventIndex++; + return item; + }) + ); + dropdownChoices.addAll([ + DropdownMenuItem( + child: Text("Final Scores"), + value: 998 + ), + DropdownMenuItem( + child: Text("Final Places"), + value: 999 + ) + ]); + return dropdownChoices; + } + + Future get tournamentResults async { + List parsedTournamentResults = await this._memoizer.runOnce(() async { + // See if results have already been saved for this tournament + // If they have, pull them up + // If not, make a request for them + // If they are there, save them locally + // If they are not there, save nothing + http.Client client = http.Client(); + http.Response resultsApiResponse = await client.get("https://www.ezratech.us/competition/${widget.tournamentInfo.key}/api/results"); + if (resultsApiResponse.statusCode == 200) { + // Parse each event + final parsedResults = await compute(parseApiResponse, resultsApiResponse.body); + + return parsedResults; + } + else { + print(resultsApiResponse.statusCode); + print(resultsApiResponse.toString()); + } + }); + return parsedTournamentResults; + } + + @override + Widget build(BuildContext context) { + return FutureBuilder( + future: tournamentResults, + builder: (context, snapshot) { + if (snapshot.hasError) { + print(snapshot.error); + } + if (!snapshot.hasData) { + return Center(child: CircularProgressIndicator()); + } + else { + if (snapshot.data is List && snapshot.data.length > 0) { + return SingleChildScrollView( + child: Column( + children: [ + DropdownButton( + items: getDropdownChoices(snapshot.data), + onChanged: (dropdownChoice) { + if (dropdownChoice is int) { + setState(() { + _dropdownIndex = dropdownChoice; + }); + } + }, + value: _dropdownIndex, + ), + Table( + border: TableBorder.all( + color: Colors.black, + style: BorderStyle.solid, + width: 1.0 + ), + children: createTableRowsFromIndex(snapshot.data, _dropdownIndex) + ) + ], + ) + ); + } + else { + TextStyle _rowTextStyle = new TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.w600, + color: Colors.black87, + ); + // no data + return Padding( + padding: new EdgeInsets.all(25.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + new Icon(Icons.stars), + SizedBox(height: 30), + new Text( + "Results from this tournament will be available after the conclusion of the tournament. Check back here to see them when they've been released!", + style: _rowTextStyle, + textAlign: TextAlign.center + ), + ] + ) + ); + } + } + } + ); } } diff --git a/pubspec.yaml b/pubspec.yaml index fbc59db..500597a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -7,7 +7,7 @@ description: Companion to Ezra Tech. # Both the version and the builder number may be overridden in flutter # build by specifying --build-name and --build-number, respectively. # Read more about versioning at semver.org. -version: 1.0.1+2 +version: 1.1.0+3 environment: sdk: ">=2.0.0-dev.68.0 <3.0.0"