Skip to content

Instantly share code, notes, and snippets.

@Levi-Lesches
Last active February 21, 2020 03:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Levi-Lesches/1e17ea7d48ee17982520328e1b39bc5e to your computer and use it in GitHub Desktop.
Save Levi-Lesches/1e17ea7d48ee17982520328e1b39bc5e to your computer and use it in GitHub Desktop.
Ram Life Sports Page
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