Flutterで集中タイマーを作る

[琉大 Advent Calendar 2019](https://adventar.org/calendars/4060)の19日目の担当です。
Flutterで簡単なタイマーを作ってみたーというお話をします。 が、ソース等は2,3時間で作ったコピペ継ぎ接ぎコードですし、解説もかなりサボってます。お許しください。

## Flutter は Google 製のUIビルドツール
Flutterを初めて聞いたという人もいると思いますが、FlutterはGoogle製のUIビルドツールです。Android/iOSのモバイルアプリケーションやWebアプリケーション、さらにはデスクトップアプリケーションを同一のコードからビルドできることから、開発効率のUPが見込めます。当然この話を聞いて「あれ？それってReact？」と思う方もいると思いますが、まあそんな感じです。競合フレームワークが多い中、今後どうなっていくのかは定かではありませんが、Google製というネムバリューは簡単に無視できるものではないと個人的には思っています。注目はされているみたいで[わざわざ学ばなくてもいいプログラミング言語](https://qiita.com/rana_kualu/items/5be7a827ed370c3cc67f)では、2018年1位だったGoogle製のDart言語(Flutter開発言語)が2019年には14位まで下がっており、これはFlutterの登場(結構最近)によるものが大きいのは間違いなさそうです。

ちなみに、昨年のアドベントカレンダーでは[Swiftの記事](https://qiita.com/dobusky/items/d948b9ea509f23b10bf2)を書いていた訳ですが、結局Swiftは諦めてDartやっているに至ります。今回はFlutterで雑にタイマーを作ります。

## 雑にタイマー作る
そもそもの動機ですが、私の友人がマイニーというちょっと変わったタイマーを所持していまして、以前使わせてもらった感じ結構いい感触でした。
マイニーに関しては[この辺](https://sakidori.co/article/414595)を見ていただければわかると思います。簡単にいうと「残り時間が数字ではなく、領域で表示されるタイマー」といったところでしょうか。
使い勝手もよく、ポチろうとも思ったのですが、タイマーを買うと考えるとちょっとお高い部分もあり泣く泣く見送り。
ならいっそ同じようなものを作ってやろうというモチベーションになり今に至ります。

### 環境の構築
[とってもわかりやすいです](https://flutter.dev/docs/get-started/install)

### main.dart を作る
main.dartはこんな感じです。
Dart言語に関しては私もよくわかっていません。気になった方は以下を見てみてください。
- [Dart](https://dart.dev)
- [Language samples](https://dart.dev/samples)
- [A tour of the Dart language](https://dart.dev/guides/language/language-tour)

```dart
import 'package:flutter/material.dart';
import 'package:timer/clock.dart'; // <-①

void main() => runApp(ConcentrateTimer());

class ConcentrateTimer extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Timer',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Home(),
    );
  }
}
```

①は分割したファイルです。
clock.dartは以下のようになります。

```dart
import 'package:flutter/material.dart';

import 'dart:async';
import 'dart:math';
import 'dart:ui';

class Home extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
            elevation: 0.0,
            title: Text(
              "Concentrate timer",
              style: TextStyle(
                  color: Colors.white,
                  fontFamily: 'Nunito',
                  letterSpacing: 1.0),
            ),
            backgroundColor: Colors.redAccent, // Color(0xFF2979FF),
            centerTitle: true),
        body: CTimer());
  }
}

class CTimer extends StatefulWidget {
  @override
  _CTimerState createState() => _CTimerState();
}

class _CTimerState extends State<CTimer> with TickerProviderStateMixin {
  double percentage = 0.0;
  double newPercentage = 0.0;
  AnimationController percentageAnimationController;

  Timer _timer;
  int _start = 60;
  double _interval = 0.0;

  @override
  void initState() {
    super.initState();
    setState(() {
      percentage = 0.0;
      _interval = 100 / _start;
    });
    percentageAnimationController = AnimationController(
        vsync: this, duration: new Duration(milliseconds: 1000))
      ..addListener(() {
        setState(() {
          percentage = lerpDouble(
              percentage, newPercentage, percentageAnimationController.value);
        });
      });
  }

  void startTimer() {
    const oneSec = const Duration(seconds: 1);
    _timer = new Timer.periodic(
      oneSec,
      (Timer timer) => setState(
        () {
          if (_start < 1) {
            timer.cancel();
            // _start = 10;
          } else {
            _start = _start - 1;
            percentage = newPercentage;
            newPercentage += _interval;
            if (newPercentage > 100.0) {
              percentage = 0.0;
              newPercentage = 0.0;
            }
            percentageAnimationController.forward(from: 0.0);
          }
        },
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        height: 500.0,
        width: 500.0,
        child: CustomPaint(
          foregroundPainter: MyPainter(
              lineColor: Colors.redAccent,
              completeColor: Colors.grey,
              completePercent: percentage,
              width: 120.0),
          child: Padding(
            padding: const EdgeInsets.all(8.0),
            child: RaisedButton(
              color: Colors.white70,
              splashColor: Colors.blueAccent,
              shape: CircleBorder(),
              child: Text(
                "$_start",
                style: TextStyle(
                  fontSize: 60.0,
                  fontFamily: 'IBMPlexMono',
                ),
              ),
              onPressed: () => startTimer(),
            ),
          ),
        ),
      ),
    );
  }
}

class MyPainter extends CustomPainter {
  Color lineColor;
  Color completeColor;
  double completePercent;
  double width;

  MyPainter(
      {this.lineColor, this.completeColor, this.completePercent, this.width});

  @override
  void paint(Canvas canvas, Size size) {
    Paint line = Paint()
      ..color = lineColor
      ..strokeCap = StrokeCap.butt
      // ..style = PaintingStyle.stroke
      ..style = PaintingStyle.stroke
      ..strokeWidth = width;
    Paint complete = Paint()
      ..color = completeColor
      ..strokeCap = StrokeCap.butt
      ..style = PaintingStyle.stroke
      ..strokeWidth = width;
    Offset center = Offset(size.width / 2, size.height / 2);
    double radius = min(size.width / 2, size.height / 2);
    canvas.drawCircle(center, radius, line);
    double arcAngle = 2 * pi * (completePercent / 100);
    canvas.drawArc(Rect.fromCircle(center: center, radius: radius), -pi / 2,
        arcAngle, false, complete);
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return true;
  }
}
```

ビルド画面は以下のような形です。

<img width="732" alt="Screen Shot 2019-12-19 at 16.16.52.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/296924/2a1eb485-0537-dfbb-8455-34e57145f0ae.png">

とりあえず、雛形はツクレマシタ。

### 課題だらけ
バッテン、課題だらけです。
- Reset機能がない
- タイマーの時間変更を現状プログラムレベルでやらないといけない
- 状態管理してない
- タイマーの時間を60分にするとエミュレータが吠える！！

実用化はまだまだ先だな...。

## まとめ
と、いうことで、Flutterで雑にタイマーのプロトタイプを作ってみました。ちゃんとした設計もせずにコピペし始めたため、課題だらけのコードになりました...。解説もなくてすいやせん。

Flutterの個人的な長所としては、Flutterに怒られないようにプログラムを組みと結果綺麗なUIができる点だと思います。これからモバイルアプリ作るぞ！と思った方は是非、検討の候補の1つにFlutterも入れていただけると嬉しいです。

さて、明日はマエケン先輩です。よろしくお願いいたします！！