Last active
February 21, 2020 03:37
-
-
Save Levi-Lesches/1e17ea7d48ee17982520328e1b39bc5e to your computer and use it in GitHub Desktop.
Ram Life Sports Page
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import "package:flutter/material.dart"; | |
@immutable | |
class Time { | |
/// The hour in 24-hour format. | |
final int hour; | |
/// The minutes. | |
final int minutes; | |
/// A const constructor. | |
const Time (this.hour, this.minutes); | |
/// Simplifies a [DateTime] object to a [Time]. | |
Time.fromDateTime (DateTime date) : | |
hour = date.hour, | |
minutes = date.minute; | |
/// Returns a new [Time] object from JSON data. | |
/// | |
/// The json must have `hour` and `minutes` fields that map to integers. | |
Time.fromJson(Map<String, dynamic> json) : | |
hour = json ["hour"], | |
minutes = json ["minutes"]; | |
/// Returns this obect in JSON form | |
Map<String, dynamic> toJson() => { | |
"hour": hour, | |
"minutes": minutes, | |
}; | |
@override | |
int get hashCode => toString().hashCode; | |
@override | |
bool operator == (dynamic other) => other.runtimeType == Time && | |
other.hour == hour && | |
other.minutes == minutes; | |
/// Returns whether this [Time] is before another [Time]. | |
bool operator < (Time other) => hour < other.hour || | |
(hour == other.hour && minutes < other.minutes); | |
/// Returns whether this [Time] is at or before another [Time]. | |
bool operator <= (Time other) => this < other || this == other; | |
/// Returns whether this [Time] is after another [Time]. | |
bool operator > (Time other) => hour > other.hour || | |
(hour == other.hour && minutes > other.minutes); | |
/// Returns whether this [Time] is at or after another [Time]. | |
bool operator >= (Time other) => this > other || this == other; | |
@override | |
String toString() => | |
"${hour > 12 ? hour - 12 : hour}:${minutes.toString().padLeft(2, '0')}"; | |
} | |
/// A range of times. | |
@immutable | |
class Range { | |
/// When this range starts. | |
final Time start; | |
/// When this range ends. | |
final Time end; | |
/// Provides a const constructor. | |
const Range (this.start, this.end); | |
/// Convenience method for manually creating a range by hand. | |
Range.nums ( | |
int startHour, | |
int startMinute, | |
int endHour, | |
int endMinute | |
) : | |
start = Time (startHour, startMinute), | |
end = Time (endHour, endMinute); | |
/// Returns a new [Range] from JSON data | |
/// | |
/// The json must have `start` and `end` fields | |
/// that map to [Time] JSON objects. | |
/// See [Time.fromJson] for more details. | |
Range.fromJson(Map<String, dynamic> json) : | |
start = Time.fromJson(Map<String, dynamic>.from(json ["start"])), | |
end = Time.fromJson(Map<String, dynamic>.from(json ["end"])); | |
/// Returns a JSON representation of this range. | |
Map<String, dynamic> toJson() => { | |
"start": start.toJson(), | |
"end": end.toJson(), | |
}; | |
/// Returns whether [other] is in this range. | |
bool contains (Time other) => start <= other && other <= end; | |
@override String toString() => "$start-$end"; | |
/// Returns whether this range is before another range. | |
bool operator < (Time other) => end.hour < other.hour || | |
( | |
end.hour == other.hour && | |
end.minutes < other.minutes | |
); | |
/// Returns whether this range is after another range. | |
bool operator > (Time other) => start.hour > other.hour || | |
( | |
start.hour == other.hour && | |
start.minutes > other.minutes | |
); | |
} | |
enum Sport {baseball, basketball, hockey, tennis, volleyball, soccer} | |
const Map<String, Sport> stringToSports = { | |
"baseball": Sport.baseball, | |
"basketball": Sport.basketball, | |
"hockey": Sport.hockey, | |
"tennis": Sport.tennis, | |
"volleyball": Sport.volleyball, | |
"soccer": Sport.soccer, | |
}; | |
@immutable | |
class SportsGame { | |
static List<SportsGame> fromList(List<Map<String, dynamic>> listJson) => [ | |
for (final Map<String, dynamic> json in listJson) | |
SportsGame.fromJson(json) | |
]; | |
final Sport sport; | |
final DateTime date; | |
final Range times; | |
final String team, opponent; | |
final bool home; | |
final Scores scores; | |
const SportsGame({ | |
@required this.sport, | |
@required this.date, | |
@required this.times, | |
@required this.team, | |
@required this.opponent, | |
@required this.home, | |
this.scores, | |
}); | |
SportsGame.fromJson(Map<String, dynamic> json) : | |
sport = stringToSports [json ["sport"]], | |
date = DateTime.parse(json ["date"]), | |
times = Range.fromJson(json ["times"]), | |
team = json ["team"], | |
home = json ["home"], | |
opponent = json ["opponent"], | |
scores = Scores.fromJson( | |
Map<String, dynamic>.from(json ["scores"]) | |
); | |
String get homeTeam => home ? "Ramaz" : opponent; | |
String get awayTeam => home ? opponent : "Ramaz"; | |
String get description => "$awayTeam @ $homeTeam"; | |
} | |
class SportsStats extends StatelessWidget { | |
final String team, dateTime; | |
final int score; | |
const SportsStats({ | |
@required this.team, | |
@required this.dateTime, | |
@required this.score, | |
}); | |
@override | |
Widget build(BuildContext context) => Row( | |
mainAxisSize: MainAxisSize.min, | |
mainAxisAlignment: MainAxisAlignment.spaceBetween, | |
children: [ | |
Text(team), | |
Spacer(flex: 2), | |
Text(score?.toString() ?? ""), | |
Spacer(flex: 3), | |
Text(dateTime), | |
Spacer(), | |
] | |
); | |
} | |
@immutable | |
class Scores { | |
final int ramazScore, otherScore; | |
final bool isHome; | |
const Scores(this.ramazScore, this.otherScore, {@required this.isHome}); | |
Scores.fromJson(Map<String, dynamic> json) : | |
isHome = json ["isHome"], | |
ramazScore = json ["ramaz"], | |
otherScore = json ["other"]; | |
@override | |
String toString() => "Ramaz: $ramazScore, Other: $otherScore"; | |
bool get didDraw => ramazScore == otherScore; | |
bool get didWin => ramazScore > otherScore; | |
int getScore({bool home}) => home == isHome | |
? ramazScore : otherScore; | |
} | |
class SportsScoreUpdater extends StatefulWidget { | |
static Future<Scores> updateGame(BuildContext context, SportsGame game) => showDialog<Scores>( | |
context: context, | |
builder: (_) => SportsScoreUpdater(game), | |
); | |
final SportsGame game; | |
const SportsScoreUpdater(this.game); | |
@override | |
ScoreUpdaterState createState() => ScoreUpdaterState(); | |
} | |
class ScoreUpdaterState extends State<SportsScoreUpdater> { | |
TextEditingController ramazController, otherController; | |
bool get ready => ramazController.text.isNotEmpty && otherController.text.isNotEmpty; | |
Scores get scores => Scores( | |
int.parse(ramazController.text), | |
int.parse(otherController.text), | |
isHome: widget.game.home, | |
); | |
@override | |
void initState() { | |
super.initState(); | |
ramazController = TextEditingController(); | |
otherController = TextEditingController(); | |
} | |
@override | |
Widget build(_) => AlertDialog( | |
title: Text("Update Scores"), | |
content: Column( | |
mainAxisSize: MainAxisSize.min, | |
children: [ | |
Row( | |
mainAxisAlignment: MainAxisAlignment.center, | |
children: [ | |
Text("Ramaz"), | |
SizedBox(width: 50), | |
Text(widget.game.opponent), | |
] | |
), | |
SizedBox(height: 20), | |
Row( | |
mainAxisAlignment: MainAxisAlignment.center, | |
children: [ | |
SizedBox( | |
width: 20, | |
child: TextField( | |
controller: ramazController, | |
onChanged: (_) => setState(() {}), | |
) | |
), | |
SizedBox(width: 50), | |
SizedBox( | |
width: 20, | |
child: TextField( | |
controller: otherController, | |
onChanged: (_) => setState(() {}), | |
) | |
), | |
] | |
) | |
], | |
), | |
actions: [ | |
FlatButton( | |
child: Text("Cancel"), | |
onPressed: () => Navigator.of(_).pop(), | |
), | |
RaisedButton( | |
child: Text("Save"), | |
onPressed: !ready ? null : () => Navigator.of(_).pop(scores), | |
) | |
] | |
); | |
} | |
class SportsTile extends StatelessWidget { | |
final SportsGame game; | |
final void Function(int, int) updateScores; | |
const SportsTile(this.game, {this.updateScores}); | |
int get padLength => game.opponent.length > "Ramaz".length | |
? game.opponent.length : "Ramaz".length; | |
String getLetter(Sport sport) { | |
switch(sport) { | |
case Sport.baseball: return "B"; | |
case Sport.basketball: return "B"; | |
case Sport.volleyball: return "V"; | |
case Sport.tennis: return "T"; | |
case Sport.hockey: return "H"; | |
case Sport.soccer: return "S"; | |
default: return "X"; | |
} | |
} | |
String formatDate(DateTime date) => | |
"${date.month}-${date.day}-${date.year}"; | |
@override | |
Widget build(BuildContext context) => SizedBox( | |
height: 170, | |
child: Card( | |
color: game.scores != null | |
? (game.scores.didWin ? Colors.lightGreen : Colors.red [400]) | |
: null, | |
child: InkWell( | |
child: Padding( | |
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 20), | |
child: Column( | |
children: [ | |
ListTile( | |
leading: CircleAvatar(child: Text(getLetter(game?.sport))), | |
title: Text(game?.team ?? ""), | |
subtitle: Text( | |
game.home ? "${game.opponent} @ Ramaz" : "Ramaz @ ${game.opponent}"), | |
), | |
SizedBox(height: 20), | |
SportsStats( | |
team: game.awayTeam.padRight(padLength), | |
score: game?.scores?.getScore(home: false), | |
dateTime: formatDate(game.date), | |
), | |
SizedBox(height: 10), | |
SportsStats( | |
team: game.homeTeam.padRight(padLength), | |
score: game?.scores?.getScore(home: true), | |
dateTime: game.times.toString(), | |
), | |
] | |
) | |
), | |
onTap: updateScores == null ? null : () async => print( | |
await SportsScoreUpdater.updateGame(context, game) | |
) | |
) | |
) | |
); | |
} | |
enum SortingOption {chronological, sport} | |
class SportsPage extends StatelessWidget { | |
static const List<Tab> tabs = [ | |
Tab(text: "Upcoming"), | |
Tab(text: "Recents"), | |
]; | |
final List<SportsGame> recents, upcoming; | |
SportsPage(List<SportsGame> games) : | |
recents = games.where((SportsGame game) => game.scores != null).toList(), | |
upcoming = games.where((SportsGame game) => game.scores == null).toList(); | |
@override | |
Widget build(BuildContext context) => DefaultTabController( | |
length: 2, | |
child: Scaffold( | |
drawer: Drawer(), | |
appBar: AppBar( | |
title: Text("Sports games"), | |
bottom: const TabBar(tabs: tabs), | |
actions: [ | |
PopupMenuButton( | |
icon: Icon(Icons.sort), | |
itemBuilder: (_) => [ | |
PopupMenuItem( | |
value: SortingOption.chronological, | |
child: Text("By date"), | |
), | |
PopupMenuItem( | |
value: SortingOption.sport, | |
child: Text("By sport"), | |
) | |
] | |
) | |
] | |
), | |
body: TabBarView( | |
children: [ | |
for(List<SportsGame> gameList in [upcoming, recents]) | |
ListView( | |
padding: EdgeInsets.symmetric(horizontal: 5), | |
children: [ | |
for (final SportsGame game in gameList) | |
SportsTile(game, updateScores: (int a, int b) => print("$a, $b")), | |
] | |
), | |
] | |
), | |
floatingActionButton: FloatingActionButton.extended( | |
label: Text("Watch livestream"), | |
icon: Icon(Icons.open_in_new), | |
onPressed: () {} | |
), | |
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, | |
) | |
); | |
} | |
final List<SportsGame> testGames = [ | |
SportsGame( | |
date: DateTime(2020, 2, 19), | |
home: false, | |
times: Range.nums(20, 00, 21, 00), | |
team: "Junior Varsity Boys Basketball", | |
opponent: "SAR ", | |
scores: Scores(5, 32, isHome: false), | |
sport: Sport.basketball, | |
), | |
SportsGame( | |
date: DateTime(2020, 5, 22), | |
home: false, | |
times: Range.nums(20, 00, 21, 00), | |
team: "Girls Volleyball", | |
opponent: "Frisch", | |
sport: Sport.volleyball, | |
), | |
SportsGame( | |
date: DateTime(2020, 1, 13), | |
home: true, | |
times: Range.nums(20, 00, 21, 00), | |
team: "Boys Tennis", | |
opponent: "Heschel", | |
sport: Sport.tennis, | |
scores: Scores(20, 15, isHome: true), | |
) | |
]; | |
void main() => runApp( | |
MaterialApp( | |
debugShowCheckedModeBanner: false, | |
theme: ThemeData( | |
primaryColor: Color(0xff004b8d), | |
accentColor: Color(0xfff9ca15), | |
buttonTheme: ButtonThemeData( | |
colorScheme: ColorScheme.light( | |
primary: Color(0xff004b8d), | |
secondary: Colors.white, | |
) | |
), | |
), | |
home: SportsPage(testGames) | |
) | |
); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment