Flutterで湯婆婆を実装してみる

# はじめに
* @Nemesis さんの[Javaで湯婆婆を実装してみる](https://qiita.com/Nemesis/items/c7192a7c510788d2cba2)を見て、Flutterでやってみようと思い立っただけです。ネタです。
* Flutterは勉強中なので突っ込みどころ満載だとは思いますが優しくご指摘いただければと思います。
* (2020/11/16 追記) 続編→[Flutterの湯婆婆にサロゲートペア文字を理解させたら可愛くなった](https://qiita.com/simontanz/items/690fb1b22ac75410d87e)

# 実装
シンプルにStatefulWidgetでゴリゴリ書いてます。
1時間くらいで書けました。

ざつーにレスポンシブ対応版に差し替えました。(2020/11/12 追記)

``` dart
import 'dart:math';

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

void main() {
  runApp(MyApp());
}

enum ScreenType { lg, sm }

ScreenType screenType(BuildContext context) {
  var width = MediaQuery.of(context).size.width;
  if (width > 768) {
    return ScreenType.lg;
  } else {
    return ScreenType.sm;
  }
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Fu婆婆',
      theme: ThemeData(
        primarySwatch: Colors.brown,
      ),
      home: MyHomePage(title: 'Fu婆婆'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  String _yubabaText = "契約書だよ。\nそこに名前を書きな。";

  var _nameController = TextEditingController();

  String _zeitaku(String name) {
    if (name == null && name.isEmpty) {
      return "";
    }

    final rand = Random();
    int index = rand.nextInt(name.length);
    return name.substring(index, index + 1);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Stack(
        children: <Widget>[
          Container(
            decoration: BoxDecoration(
              image: DecorationImage(
                image: AssetImage('assets/chihiro016.jpg'),
                fit: BoxFit.cover,
              ),
            ),
          ),
          SingleChildScrollView(
            child: screenType(context) == ScreenType.sm
                ? Center(
                    child: Column(
                      children: [
                        _buildYubabaTextBox(context),
                        _buildContract(context),
                      ],
                    ),
                  )
                : Center(
                    child: Stack(
                      children: <Widget>[
                        Align(
                          alignment: Alignment.topLeft,
                          child: _buildYubabaTextBox(context),
                        ),
                        Align(
                          alignment: Alignment.bottomRight,
                          child: _buildContract(context),
                        ),
                      ],
                    ),
                  ),
          ),
        ],
      ),
    );
  }

  Widget _buildYubabaTextBox(BuildContext context) {
    return Container(
      margin: EdgeInsets.all(30),
      padding: EdgeInsets.all(30),
      decoration: new BoxDecoration(
        color: Colors.white.withAlpha(200),
        borderRadius: BorderRadius.all(Radius.circular(10)),
        border: Border.all(color: Colors.black.withAlpha(200), width: 5),
      ),
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          Text(
            _yubabaText,
            softWrap: true,
            style: TextStyle(fontSize: 30),
          ),
        ],
      ),
    );
  }

  Widget _buildContract(BuildContext context) {
    return Container(
      width: 500,
      height: 400,
      margin: EdgeInsets.all(20),
      color: Color(0xffe3ab7a).withAlpha(220),
      child: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.start,
          children: [
            Padding(
                padding: EdgeInsets.all(50),
                child: Text(
                  '契約書',
                  style: TextStyle(fontSize: 50),
                )),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                SizedBox(
                    width: 50,
                    child: Text(
                      '甲',
                      style: TextStyle(fontSize: 30),
                    )),
                SizedBox(
                  width: 300,
                  child: Text(
                    '油屋当主 湯婆婆',
                    style: TextStyle(fontSize: 30),
                  ),
                ),
              ],
            ),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                SizedBox(
                  width: 50,
                  child: Text(
                    '乙',
                    style: TextStyle(fontSize: 30),
                  ),
                ),
                SizedBox(
                    width: 300,
                    child: TextField(
                      controller: _nameController,
                      cursorColor: Colors.brown,
                      style: TextStyle(fontSize: 30),
                      maxLength: 30,
                      decoration: InputDecoration(
                        hintText: '名前を入力',
                      ),
                    )),
              ],
            ),
            Padding(
              padding: EdgeInsets.all(30),
              child: RaisedButton(
                child: Padding(
                  padding: EdgeInsets.all(5),
                  child: Text(
                    '契約',
                    style: TextStyle(fontSize: 30, color: Colors.red),
                  ),
                ),
                color: Color(0xffe3ab7a),
                shape: const OutlineInputBorder(
                  borderRadius: BorderRadius.all(Radius.circular(10)),
                  borderSide: BorderSide(color: Colors.red),
                ),
                onPressed: () {
                  var name = _nameController.text;
                  if (name == null || name.isEmpty) {
                    return;
                  }
                  var newName = _zeitaku(name);

                  setState(
                    () {
                      _yubabaText =
                          'フン。\n$nameというのかい。\n贅沢な名だねぇ。\n\今からお前の名前は$newNameだ。\nいいかい、$newNameだよ。\n分かったら返事をするんだ、$newName!!\n';
                    },
                  );
                },
              ),
            ),
          ],
        ),
      ),
    );
  }
}

```

全コードはここ→[GitHub](https://github.com/kohta1992/flutter-baba)

# Hosting
今回は手軽に触ることができるようにWebアプリとしてビルドしFirebaseでHostingしました。
↓からどうぞ。
https://flutter-baba.web.app/#/

# 画面
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/249961/b7c27340-382d-f239-8275-fd05fa118cb6.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/249961/d0b2edeb-6851-428f-1706-7bab0b539178.png)
※ 湯婆婆の画像は[ジブリ公式](http://www.ghibli.jp/works/chihiro/)からお借りしています。(常識の範囲内だと思ってます・・・)

# おわりに
今回、突発的にFlutterで実装してみました。
改めてですが、Flutterは素早くアプリが作れて良き良きの良きですね。
それと、私は普段Android系の開発をしているのですが、Flutter+Firebaseを活用すれば手軽にWebアプリを開発できるってのもありがたいです。
モバイルアプリだとこういうネタアプリのためだと配布がめんどいので。
Flutterやったことないって人はぜひ一度触ってみてください。

# 追記
* 2020/11/11
  * @torifukukaiouさんが`"Hello, 湯婆婆"`なアプリをランキング形式にしてくれているようです。現在56位・・・。  
  [【毎日自動更新】湯婆婆 LGTMランキング！](https://qiita.com/torifukukaiou/items/c8361231cdc56e493245)
  * 名前から1文字抽出するときなぜか`rand.nextInt`に渡す文字数を`-1`していたので直しました。(はずい)
* 2020/11/12
  * 雑だけどレスポンシブ対応したバージョンに差し替えました。
* 2020/11/19
  * UnoPlatformを使った画面付きの湯婆婆が現れました。（リセットボタンと注意書きパクります）  
[C# で湯婆婆を Windows アプリと WebAssembly 対応してみた（無駄に MVVM）](https://qiita.com/okazuki/items/abf0469527c2e67b4840)