Skip to content

Instantly share code, notes, and snippets.

@rodydavis
Created May 2, 2019 13:50
Show Gist options
  • Save rodydavis/d11bd2082aed3afeb227fb4847d6766d to your computer and use it in GitHub Desktop.
Save rodydavis/d11bd2082aed3afeb227fb4847d6766d to your computer and use it in GitHub Desktop.
Working version of a desktop calendar built with Flutter.
import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import '../../constants.dart';
import '../../utils/index.dart';
import 'index.dart';
class FullCalendar extends StatefulWidget {
const FullCalendar({
this.events,
this.onNextWeek,
this.onPreviousWeek,
});
final List<CalendarEvent> events;
final ValueChanged<DateTime> onNextWeek, onPreviousWeek;
@override
_FullCalendarState createState() => _FullCalendarState();
}
class _FullCalendarState extends State<FullCalendar> {
DateTime _week;
DateTime _currentTime = DateTime.now();
ScrollController _controller;
Timer _timer;
List<CalendarEvent> _events;
@override
void initState() {
startTimer();
changeDate(DateTime.now());
loadEvents();
super.initState();
}
void loadEvents() {
if (mounted)
setState(() {
_events = widget?.events ?? [];
});
}
void nextWeek() {
if (mounted)
setState(() {
_week = _week.add(Duration(days: 7));
});
if (widget?.onNextWeek != null) {
widget.onNextWeek(_week);
}
}
void previousWeek() {
if (mounted)
setState(() {
_week = _week.subtract(Duration(days: 7));
});
if (widget?.onPreviousWeek != null) {
widget.onPreviousWeek(_week);
}
}
void changeDate(DateTime value) {
final _now = value;
if (mounted)
setState(() {
_week = _now.subtract(Duration(days: _now.weekday));
final _offset = _getHourOffset(_now);
_controller = ScrollController(
initialScrollOffset:
(_offset - 200).isNegative ? _offset : (_offset - 200));
});
}
double _getHourOffset(DateTime _now) {
final _duration = Duration(hours: _now.hour, minutes: _now.minute);
return _duration.inMinutes * (_cellHeight / 60);
}
double _getEndOffset(CalendarEvent event) {
var _start = event.startDate;
_start = _start.add(Duration(minutes: 30));
final _end = event?.endDate ?? _start;
final _offset = _getHourOffset(_end);
final _hours = TimeOfDay.hoursPerDay * (60 * (_cellHeight / 60));
return _hours - _offset;
}
bool _isSameDay(int day, DateTime value) {
final _now = value;
final _current = _week.add(Duration(days: day));
if (_current.year == _now.year) {
if (_current.month == _now.month) {
if (_current.day == _now.day) {
return true;
}
}
}
return false;
}
void startTimer() {
_timer = new Timer.periodic(const Duration(seconds: 5), (timer) {
if (mounted)
setState(() {
_currentTime = DateTime.now();
});
});
}
@override
void dispose() {
_timer.cancel();
super.dispose();
}
@override
void didUpdateWidget(FullCalendar oldWidget) {
if (oldWidget.events != widget.events) {
loadEvents();
}
super.didUpdateWidget(oldWidget);
}
final _cellHeight = 100.0;
double _getWidth(BuildContext context) {
final _size = MediaQuery.of(context).size;
final _width = _size.width / 12.84;
return _width;
}
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 20.0, vertical: 8.0),
child: Column(
children: <Widget>[
_buildHeader(),
_buildDateRange(context),
Expanded(
child: Container(
padding: EdgeInsets.only(top: 20.0),
child: SingleChildScrollView(
controller: _controller,
child: Stack(
children: <Widget>[
_buildHours(context),
Positioned(
top: 0,
left: 0,
bottom: 0,
right: 0,
child: _buildEvents(),
),
_buildCurrentTime(context),
],
)),
),
),
],
),
);
}
Container _buildDateRange(BuildContext context) {
final _width = _getWidth(context);
return Container(
child: Stack(
children: <Widget>[
Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Container(
width: 100,
child: IconButton(
icon: Icon(Icons.arrow_back),
onPressed: previousWeek,
),
),
for (var i = 0; i < DateTime.daysPerWeek; i++) ...[
Container(
width: _width,
child: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
padding: EdgeInsets.all(8.0),
decoration: !_isSameDay(i, DateTime.now())
? null
: BoxDecoration(
color: Colors.redAccent,
borderRadius: BorderRadius.circular(12.0),
),
child: Column(
children: <Widget>[
Text(
formatDateCustom(_week.add(Duration(days: i)),
format: 'EEE'),
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w300,
color: textColor(context,
invert: _isSameDay(i, DateTime.now())),
),
),
Text(
_week.add(Duration(days: i)).day.toString(),
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.w500,
color: textColor(context,
invert: _isSameDay(i, DateTime.now())),
),
),
],
),
)
],
),
),
)
],
],
),
),
Positioned(
right: 0,
top: 0,
bottom: 0,
child: Container(
width: _width / 2,
child: IconButton(
icon: Icon(Icons.arrow_forward),
onPressed: nextWeek,
),
),
)
],
),
);
}
Container _buildHeader() {
return Container(
padding: EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
RaisedButton.icon(
icon: Icon(Icons.event),
label: Text('Today'),
onPressed: () {
changeDate(DateTime.now());
},
),
Text(
formatDateCustom(_week, format: 'MMMM'),
style: TextStyle(
fontSize: 30,
fontWeight: FontWeight.w700,
),
),
RaisedButton.icon(
icon: Icon(Icons.refresh),
label: Text('Refresh'),
onPressed: loadEvents,
),
],
),
);
}
Container _buildEvents() {
final _width = _getWidth(context);
return Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Container(width: 100, height: _cellHeight),
for (var i = 0; i < DateTime.daysPerWeek; i++) ...[
Container(
width: _width,
child: Stack(
children: <Widget>[
if (_events != null)
for (var event in _events) ...[
if (_isSameDay(i, event.startDate)) ...[
Positioned(
top: _getHourOffset(event.startDate),
bottom: _getEndOffset(event),
child: Container(
width: _width,
child: _buildEvent(
event,
_getHourOffset(event.startDate),
_getEndOffset(event)),
),
)
]
]
],
),
),
],
],
),
);
}
Container _buildEvent(CalendarEvent event, double start, double end) {
final _width = _getWidth(context);
final _color = event?.color ?? Colors.lightBlueAccent;
return Container(
child: InkWell(
onTap: () {
showDialog(
context: context,
builder: (context) => PopUpSurface(
title: 'Event Info',
),
);
},
child: Container(
width: _width,
child: Container(
decoration: BoxDecoration(
color: _color.withAlpha(50),
border: Border(
left: BorderSide(
color: _color,
width: 3.0,
),
)),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
padding: EdgeInsets.only(left: 4.0, top: 4.0),
child: Text(
TimeOfDay.fromDateTime(event.startDate).format(context),
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
color: _color,
),
),
),
Container(
padding: EdgeInsets.only(left: 4.0),
child: Text(
event.title,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
),
if (event?.description != null) ...[
Expanded(
child: Container(
padding: EdgeInsets.all(4.0),
child: Text(
event.description,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
),
)
]
],
),
),
),
),
);
}
Column _buildHours(BuildContext context) {
final _width = _getWidth(context);
return Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
for (var i = 0; i < TimeOfDay.hoursPerDay; i++) ...[
Container(
width: 100,
height: _cellHeight,
child: Container(
padding: EdgeInsets.all(8.0),
child: Text(
TimeOfDay(hour: i, minute: 0).format(context),
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
),
),
),
),
...[Divider(height: 0.0)],
],
],
);
}
AnimatedContainer _buildCurrentTime(BuildContext context) {
return AnimatedContainer(
padding: EdgeInsets.only(top: _getHourOffset(_currentTime)),
duration: Duration(seconds: 1),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Material(
borderRadius: BorderRadius.circular(4.0),
elevation: 8.0,
child: Container(
padding: EdgeInsets.all(10.0),
child: Text(
TimeOfDay.fromDateTime(_currentTime).format(context),
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w800,
color: Colors.blueAccent),
),
),
),
Expanded(
child: DashedLine(
color: Colors.blueAccent,
height: 1,
))
],
),
);
}
}
class CalendarEvent {
const CalendarEvent({
@required this.title,
@required this.startDate,
this.allDay = false,
this.endDate,
this.description,
this.onTap,
this.color,
});
final DateTime startDate, endDate;
final bool allDay;
final String title;
final String description;
final VoidCallback onTap;
final Color color;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment