Flutter - Birthday Reminder App

In order to help solidify my knowlege of Flutter my current work in progress is a little birthday reminder app. It uses a number of prebuilt widgets as well as some custom ones. I'm terrible at remembering birthdays and I found the in build calendars never alerted me in time or I'd miss the notification. So for my app you can specify what you need to send, and it will alert you a few days before to give you time to purchase things. E.g. 7 days for a card, 14 days for a gift.

I'm using a number of pre-built packages, including the provider package to help manage state, and the shared_preferences for saving and loading data. However there was a small graphical I wanted to achieve that wasn't available out of the box and that led me to build a custom painter.


Custom Painter

birthday clock birthday clock

Flutter allows you to extend the CustomPainter class and draw your own widget ready to compose. In my case I wanted a little countdown with a more obvious dot visual. This then forms part of a visual stack and I can add a normal text or icon widget for a day countdown...and cake on the day.

Full code sample is as follows.

 
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'dart:math' as Math;

class ProgressPainter extends CustomPainter {
  final Color progressColor;
  final double progress;
  final double _progressRadius = 24;
  final double _ringStrokeWidth = 2;
  final double _dotRadius = 4;
  final bool countdown;

  ProgressPainter({this.progress, this.progressColor, this.countdown});

  double getProgress() {
    if (countdown) {
      return progress;
    }
    return 1 - progress;
  }

  Offset getArcOffset(double radius, double progress) {
    double angle = progress * Math.pi * 2;
    double x = radius * Math.sin(angle);
    double y = radius * -Math.cos(angle);
    print(x + radius);
    return Offset(x, y);
  }

  @override
  void paint(Canvas canvas, Size size) {
    final center = Offset(size.width / 2, size.height / 2);
    final paint = Paint()..color = progressColor;

    // main style for our countdown
    final arcPaint = Paint()
      ..style = PaintingStyle.stroke
      ..strokeCap = StrokeCap.round
      ..strokeWidth = _ringStrokeWidth
      ..color = progressColor;
    // background which will be the full ring
    final arcPaintBackground = Paint()
      ..style = PaintingStyle.stroke
      ..strokeCap = StrokeCap.butt
      ..strokeWidth = _ringStrokeWidth
      ..color = progressColor.withOpacity(0.25);

    // draw feint circle
    canvas.drawArc(Rect.fromCircle(center: center, radius: _progressRadius), 0,
        2 * Math.pi, false, arcPaintBackground);
    // draw highlighted countdown section - need to minus Pi/2 to it starts at 12 not 3 on the clock
    // and we use a -ve value to get a countdown look
    canvas.drawArc(
        Rect.fromCircle(center: center, radius: _progressRadius),
        0 - Math.pi / 2,
        (countdown ? -1 : 1) * getProgress() * Math.pi * 2,
        false,
        arcPaint);
    // draw circle to indicate current countdown progress
    canvas.drawCircle(
        getArcOffset(_progressRadius, (countdown ? -1 : 1) * getProgress()) +
            center,
        _dotRadius,
        paint);
  }

  @override
  bool shouldRepaint(ProgressPainter oldDelegate) {
    // check for any change and return accordingly
    return oldDelegate.progress != this.progress ||
        oldDelegate.progressColor != this.progressColor ||
        oldDelegate.countdown != this.countdown;
  }
}