kazpgmの日記

『プログラム自動作成@自動生成』作成の日記

フロント側をFlutter(スマホ)Thymeleaf(PC)、バックエンド側SpringBootの自動作成勉強中

9:16
①昨日の、Flutter(スマホ)側の”この辺を調査する。”を調査してる。
 ・”日付選択”っていう文字は消す。(書いてなくてもわかるので)をやった。
   SimpleDialogのtitle: をコメントにしたらできた。簡単だった。

            return SimpleDialog(
//              title: Text('日付選択'), // 日付選択ダイヤログはタイトルを出さない
              children: <Widget>[
                TableCalendar(

9:33
①年月表示が左に寄っているので中央にしたい。をやった。
 HeaderStyleのtitleCenteredをtrueにしたらできた。簡単だった。

                TableCalendar(
・・・
                  // カレンダーフォーマット変更ボタンを表示しない
                  headerStyle: const HeaderStyle(
                    titleCentered: true,


12:07
①罫線が少し見づらい。をやってる。
まず、今日の枠の罫線がないので、少し薄く感じる。ので、todayBuilderを書き換えた。
青っぽい丸に白色の日付がデフォルト表示なので、真似した。以外に難しかった。
まだ終わりじゃない。月の最初に日と最後の日の罫線が気になる。ので調べよう。
さらに、なぜか、有効範囲(firstDay~lastDay)以外の日付部分の罫線が変なので。調査する

                TableCalendar(
・・・
                  calendarBuilders: CalendarBuilders(
・・・
                    /// 今日のセルを枠線のついたContainerに置き換える
                    todayBuilder: (
                        BuildContext context, DateTime day, DateTime focusedDay) {
                      return AnimatedContainer(
                        duration: const Duration(milliseconds: 250),
                        margin: EdgeInsets.zero,
                        decoration: BoxDecoration(
                          border: Border.all(
                            color: Colors.green[600]!,
                            width: 0.5,
                          ),
                        ),
                        child: AnimatedContainer (
                          duration: const Duration(milliseconds: 250),
                          margin: const EdgeInsets.all(5),
                          decoration: const BoxDecoration(
                            color: Color(0xFF9FA8DA),
                            shape: BoxShape.circle,
                          ),
                          alignment: Alignment.center,
                          child: Text(
                            day.day.toString(),
                            style: const TextStyle(
                              fontWeight: FontWeight.bold,
                              color: Colors.white,
                            ),
                          ),
                        ),
                      );
                    },

15:16
①よくよく見てみると「CalendarBuilders」(有効範囲(firstDay~lastDay)以外の日付部分)の「disabledBuilder」が効いていないのだ。と思ってたら、実は「outsideBuilder」(現在フォーカスされている月と一致しない日付部分)を使うのが正解だった。これで、全てのマスに罫線がついた。よかった

                TableCalendar(
・・・
                  calendarBuilders: CalendarBuilders(
・・・
                    /// 現在フォーカスされている月と一致しない日付部分を生成する
                    outsideBuilder: (
                        BuildContext context, DateTime day, DateTime focusedDay) {
                      return AnimatedContainer(
                        duration: const Duration(milliseconds: 250),
                        margin: EdgeInsets.zero,
                        decoration: BoxDecoration(
                          border: Border.all(
                            color: Colors.green[600]!,
                            width: 0.5,
                          ),
                        ),
                        alignment: Alignment.topCenter,
                        child: Text(
                          day.day.toString(),
                          style: const TextStyle(
                            color: Colors.grey,
                          ),
                        ),
                      );
                    },


②さらに、「CalendarBuilders」(有効範囲(firstDay~lastDay)以外の日付部分)の「disabledBuilder」も必要だということも思いついた。カレンダーの開始年月日、終了年月日を指定するので、それ以外の日付は選択できなくなる。っていうふうに使うため。

17:04
①Flutter(スマホ)側のダイヤログ表示時に入力日付がわかるようにマークを付ける。をやった。各Builder(outsideBuilder、disabledBuilder、defaultBuilder、todayBuilder)の「Container」の「decoration: BoxDecoration」を、入力日付の時は赤罫線を出すようにした。

20:40
①Flutter(スマホ)側のダイヤログ表示時に入力日付がわかるようにマークを付ける。も含めて、完成した。多分これなら使えると思う。(罫線の外枠がちょっと細いのは、このままでいいかなって思っている。)
■Flutter(スマホ)側のopenDateDialogCommメソッド呼びもと。入力日付が入っていなくても動く様に修正した。

    //--テスト日付入力 start----------------------------
    contentWidgets.add(
      Row (
//      mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          // テスト日付の入力フォーム
          Container(
            margin: const EdgeInsets.fromLTRB(10.0, 0, 10.0, 5),
            padding: const EdgeInsets.fromLTRB(10.0, 0, 10.0, 0),
            child:Text(_focusedDayStr,
              textAlign: TextAlign.left,
              overflow: TextOverflow.clip,
            ),
          ),
          Container(
            margin: const EdgeInsets.fromLTRB(5, 0, 5, 5),
            padding: const EdgeInsets.fromLTRB(5, 0, 5, 0),
            child: ElevatedButton(
              onPressed: () async {
                DateTime? _date;
                if (_focusedDayStr == "") {
                  _date = null;
                } else {
                  _date = DateTime.utc(int.parse((_focusedDayStr).substring(0, 4)),
                      int.parse((_focusedDayStr).substring(5, 7)),
                      int.parse((_focusedDayStr).substring(8, 10)),0,0);
                }
                String? _str = await CommUtils.openDateDialogComm(context,
                          DateTime.utc(1900, 1, 1), DateTime.utc(2099, 12, 31), _date);
                if (_str != null && _str != Consts.cancel) {
                  setState(() {
                    _focusedDayStr = _str;
                  });
                }
              },
              style: ElevatedButton.styleFrom(
                primary: Colors.blue,
              ),
              child: const Text("日付選択"),
            ),
          ),
        ]
      ),
    );
    //--テスト日付入力 end----------------------------

■Flutter(スマホ)側のCommUtilsクラス(共通ユーティリティクラス)・openDateDialogCommメソッド

  /// 日付入力ダイヤログ
  /// カレンダー表示の開始、終了年月日とフォーカスする年月日を入力し、
  /// 選択日付、CANCELを戻す。
  static Future<String?> openDateDialogComm(BuildContext context,
            DateTime _firstDay, DateTime _lastDay, DateTime? _inFocusedDay) async {
    initializeDateFormatting('ja', null);
    DateFormat outputFormat = DateFormat('yyyy/MM/dd');
    DateTime? _selectedDay;
    DateTime _focusedDay = _inFocusedDay ?? DateTime.now();
    DateTime _originFocusedDay = _focusedDay;

    int getHashCode(DateTime key) {
      return key.day * 1000000 + key.month * 10000 + key.year;
    }
    // TableCalendarでは、カレンダーに読み込むイベントをMapで定義した場合、
    // LinkedHashMap使用が推奨されている。
    final _events = LinkedHashMap<DateTime, List>(
      equals: isSameDay,
      hashCode: getHashCode,
    );
    List getEventForDay(DateTime day) {
      return _events[day] ?? [];
    }
    // 祝祭日リスト
    Map<DateTime, List> _eventsList = {};
    // 日付入力ダイヤログ
    // イベントマーカーを赤丸”休”とする
    Widget _buildEventsMarker(DateTime date, List events) {
      return Positioned(
        right: 5,
        bottom: 5,
        child: AnimatedContainer(
          duration: const Duration(milliseconds: 300),
          decoration: BoxDecoration(
            shape: BoxShape.circle,
            color: Colors.red[300],
          ),
          width: 16.0,
          height: 16.0,
          child: Center(
            child: Text(
              '休',
              style: const TextStyle().copyWith(
                color: Colors.white,
                fontSize: 12.0,
              ),
            ),
          ),
        ),
      );
    }
    /// 日曜日を赤色、土曜日を青色にしたい
    Color _textColor(DateTime day) {
      const _defaultTextColor = Colors.black87;
      if (day.weekday == DateTime.sunday) {
        return Colors.red;
      }
      if (day.weekday == DateTime.saturday) {
        return Colors.blue[600]!;
      }
      return _defaultTextColor;
    }

    /// 項目入力日及び選択日の罫線を赤にする
    BoxDecoration _boxDecoration(DateTime day) {
      BoxDecoration _boxDecoration;
      // ダイヤログ表示後、日付が選択されるまでと、日付が選択された時
      if (_selectedDay == null && isSameDay(day, _originFocusedDay) ||
          _selectedDay != null && isSameDay(day, _selectedDay)) {
        _boxDecoration = BoxDecoration(
          border: Border.all(
            color: Colors.red[800]!,
            width: 3.0,
          ),
        );
      } else {
        _boxDecoration = BoxDecoration(
          border: Border.all(
            color: Colors.green[600]!,
            width: 0.5,
          ),
        );
      }
      return _boxDecoration;
    }

    /// todayBuilder向け。今日の罫線枠を赤にして、今日の日付を青丸文字にする
    AnimatedContainer _animatedContainerForToday(DateTime day) {
      return AnimatedContainer(
        duration: const Duration(milliseconds: 250),
        margin: EdgeInsets.zero,
        decoration: _boxDecoration(day),
        // 今日の日付を青丸文字にする
        child: AnimatedContainer (
          duration: const Duration(milliseconds: 250),
          margin: const EdgeInsets.all(5),
          decoration: const BoxDecoration(
            color: Color(0xFF9FA8DA),
            shape: BoxShape.circle,
          ),
          alignment: Alignment.center,
          child: Text(
            day.day.toString(),
            style: const TextStyle(
              fontWeight: FontWeight.bold,
              color: Colors.white,
            ),
          ),
        ),
      );
    }

    /// selectedBuilder向け。項目入力日及び選択日の罫線を赤にする。さらに、今日の日付を青丸文字にする
    AnimatedContainer _animatedContainerForSelect(DateTime day) {
      AnimatedContainer _animatedContainer;
      // 日付が今日の時
      if (isSameDay(day, DateTime.now())) {
        _animatedContainer = _animatedContainerForToday(day);
      } else {
        // 日付が今日以外の時
        _animatedContainer = AnimatedContainer(
          duration: const Duration(milliseconds: 250),
          margin: EdgeInsets.zero,
          decoration: BoxDecoration(
            border: Border.all(
              color: Colors.red[800]!,
              width: 3.0,
            ),
          ),
          alignment: Alignment.topCenter,
          child: Text(
            day.day.toString(),
            style: TextStyle(
              color: _textColor(day),
            ),
          ),
        );
      }
      return _animatedContainer;
    }

    // 祝祭日XMLを読む
    final url = Uri.parse(Consts.myHttpUrl + '/js/holidays.xml');
    HttpClientRequest request = await HttpClient().getUrl(url);
    HttpClientResponse response = await request.close();
    // 祝祭日XMLのdateエレメントのみ抽出する
    final stream = response.transform(utf8.decoder)
        .toXmlEvents()
        .selectSubtreeEvents((event) => event.name == 'date');
    // _eventsListに祝祭日を登録する
    await stream.forEachEvent(onText: (event) => _eventsList.addAll(
        {DateTime(int.parse((event.text).substring(0,4)),
            int.parse((event.text).substring(5,7)),
            int.parse((event.text).substring(8,10)),0,0): ['祝祭日']}));
    // _eventsListに登録した祝祭日を、eventsに登録する
    _events.addAll(_eventsList);

    // 日付入力ダイヤログを表示する
    var result  = await showDialog<String>(
      context: context,
      barrierDismissible: false,
      builder: (BuildContext context) {
        return StatefulBuilder(
          builder: (context, setState) {
            return SimpleDialog(
//              title: Text('日付選択'), // 日付選択ダイヤログはタイトルを出さない
              children: <Widget>[
                TableCalendar(
                  locale: 'ja',
                  // カレンダーの最初と最後の月を設定
                  firstDay: _firstDay,
                  lastDay: _lastDay,
                  // focusedDayによって表示月が決定される。
                  focusedDay: _focusedDay,
                  // イベントを読み込む。祝祭日に赤丸”休”を表示
                  eventLoader: getEventForDay,
                  // カレンダーフォーマットを月単位とする
                  calendarFormat: CalendarFormat.month,
                  // カレンダーフォーマット変更ボタンを表示しない
                  headerStyle: const HeaderStyle(
                    titleCentered: true,
                    formatButtonVisible: false,
                  ),
                  // どの日が現在選択されているかを判断する。
                  // dayが選択されたら、trueを返す。
                  selectedDayPredicate: (day) {
                    return isSameDay(_selectedDay, day);
                  },
                  // 選択した日付を一時的に保存し、selectedDayPredicateオプションで
                  // 保存された日付情報にフォーカスを当てて印が付けている。
                  onDaySelected: (selectedDay, focusedDay) {
                    if (!isSameDay(_selectedDay, selectedDay)) {
                      setState(() {
                        _selectedDay = selectedDay;
                        _focusedDay = focusedDay;
                      });
                    }
                  },
                  onPageChanged: (focusedDay) {
                      _focusedDay = focusedDay;
                  },
                  calendarBuilders: CalendarBuilders(
                    /// 現在フォーカスされている月と一致しない日付部分を生成する
                    outsideBuilder: (
                        BuildContext context, DateTime day, DateTime focusedDay) {
                      return AnimatedContainer(
                        duration: const Duration(milliseconds: 250),
                        margin: EdgeInsets.zero,
                        decoration: _boxDecoration(day),
                        alignment: Alignment.topCenter,
                        child: Text(
                          day.day.toString(),
                          style: const TextStyle(
                            color: Colors.grey,
                          ),
                        ),
                      );
                    },
                    /// 有効範囲(firstDay~lastDay)以外の日付部分を生成する
                    disabledBuilder: (
                        BuildContext context, DateTime day, DateTime focusedDay) {
                      return AnimatedContainer(
                        duration: const Duration(milliseconds: 250),
                        margin: EdgeInsets.zero,
                        decoration: _boxDecoration(day),
                        alignment: Alignment.topCenter,
                        child: Text(
                          day.day.toString(),
                          style: const TextStyle(
                            color: Colors.grey,
                          ),
                        ),
                      );
                    },
                    /// デフォルトのセルを枠線のついたContainerに置き換える
                    defaultBuilder: (
                        BuildContext context, DateTime day, DateTime focusedDay) {
                      return AnimatedContainer(
                        duration: const Duration(milliseconds: 250),
                        margin: EdgeInsets.zero,
                        decoration: _boxDecoration(day),
                        alignment: Alignment.topCenter,
                        child: Text(
                          day.day.toString(),
                          style: TextStyle(
                            color: _textColor(day),
                          ),
                        ),
                      );
                    },
                    /// 今日のセルを枠線のついたContainerに置き換える
                    todayBuilder: (
                        BuildContext context, DateTime day, DateTime focusedDay) {
                      return _animatedContainerForToday(day);
                    },
                    /// 曜日のセルを枠線のついたContainerに置き換える
                    dowBuilder: (BuildContext context, DateTime day) {
                      final dowText =
                          const DaysOfWeekStyle().dowTextFormatter?.call(day, 'ja') ??
                              DateFormat.E('ja').format(day);
                      return Container(
                        decoration: BoxDecoration(
                          border: Border.all(
                            width: 0.5,
                            color: Colors.green[600]!,
                          ),
                        ),
                        child: Center(
                          child: Text(
                            dowText,
                            style: TextStyle(
                              color: _textColor(day),
                            ),
                          ),
                        ),
                      );
                    },
                    // 選択されたセルを枠線のついたContainerに置き換える
                    selectedBuilder: (
                        BuildContext context, DateTime day, DateTime focusedDay) {
                      return _animatedContainerForSelect(day);
                    },
                    /// イベントマーカーをmarkerBuilderを使用してカスタマイズする
                    markerBuilder: (context, date, events) {
                      if (events.isNotEmpty) {
                        return _buildEventsMarker(date, events);
                      }
                      return null;
                    },
                  ),
                ),
                Row(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children:<Widget>[
                    SimpleDialogOption(child: const Text('OK'), onPressed: () {
                      // 選択した日付を戻す。選択していないときは入力日付か、入力日付がnullの時は今日の日付を戻す。
                      Navigator.pop(context, _selectedDay==null?outputFormat.format(_originFocusedDay):outputFormat.format(_selectedDay!));
                    },),
                    SimpleDialogOption(child: const Text('キャンセル'), onPressed: () {
                      // ”cancel”を戻す
                      Navigator.pop(context, Consts.cancel);
                    },)
                  ],
                ),
              ],
            );
          }
        );
      },
    );
    return result;
  }


日付選択ボタンを押下する。日付が空白だったため今日日付に青丸がつき、赤枠(選択済み)になる。

4月20日を選択する。4月20日が赤枠(選択済み)になる。

OKボタンを押下する。項目が4月20日になる。

日付選択ボタンを押下する。4月20日が赤枠(選択済み)になる。

4月18日を選択する。4月18日が赤枠(選択済み)になる。今日なので、青丸日付表示になる。

OKボタンを押下する。項目が4月18日になる。

■2022/06/15に、勉強した成果:『Flutter_JavaSpringプログラム自動作成◎自動生成ツール』をVectorに載せました。Zenn本も書きました。使ってみての感想や間違いの指定や、こうやったほうがいいとかの情報があればメールください。
Vector
www.vector.co.jp
・Zenn本(Flutter_JavaSpringプログラム自動作成)
zenn.dev