kazpgmの日記

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

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

9:41
①昨日の、『Flutter(スマホ)側のダイヤログ表示時に入力日付がわかるようにマークを付ける。も含めて、完成した。多分これなら使えると思う。(罫線の外枠がちょっと細いのは、このままでいいかなって思っている。)』の、”罫線の外枠がちょっと細い”という部分をどうしたら解決できるか考えた。これならいけそう。
・曜日行セルの罫線上部を太くする。さらに、日曜の左側、土曜の右側をを太くする。
・日付セル
 ・日曜の左側、土曜の右側をを太くする。
 ・表示されている月の日付のセルで、カレンダー最下位行になる日付セルの罫線下部を太くする。
  ロジックは、
   ・WK1=表示されている月の1日の曜日により、日:0、月:1、火:2、水:3、木:4、金:5、土:6
   ・WK2=(表示されている月の末日+WK1)/7日
   ・WK3=WK2計算の余りにより、0:6、以外は、余り-1
   ・WK4=表示されている月末日-WK3
   カレンダー最下位行になる日付セル=WK4の日~末日
 ・表示されている月の翌月となる日付セルの罫線下部を太くする。

15:05
①罫線の見づらさは解消された。
 ・カレンダー最下位行になる日付セルの算出ロジックを、日付ダイヤログメソッド呼出し直後と、『onPageChanged: (focusedDay)』の時に行ったが、全然だめだったので、てこづった。『onPageChanged: (focusedDay)』の時では、遅い(すでに次の月表示が始まっている)みたいだった。だから、outsideBuilder、disabledBuilder、defaultBuilder、todayBuilder。dowBuilder、selectedBuilderの中でfocusedDayを見て、カレンダー最下位行になる日付セルの算出ロジックを走らせた。
 ・実機でも遅いのでどうだろう、使えるかどうか。でも、時代の流れでCPUは早くなるのでいいかなと思う。
②できたFlutter(スマホ)側カレンダーダイヤグラム

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

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

翌月を表示する。

前月を表示する。選択してあった4月28日が赤枠(選択済み)になる。

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

前月を表示する。

翌月を表示する。選択してあった4月29日が赤枠(選択済み)になる。

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

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

15:32
①できたFlutter(スマホ)側カレンダーダイヤグラムプログラム
②昨日もソース載せたけど、変わったので今日も載せておこう。思いっきり大きなPGMになってしまった感があるけどこれで、思った感じのカレンダーダイヤログになった。ので、うれしい
■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;

    /// 罫線表示に使用するのため、表示されている月の日付のセルで、カレンダー最下位行になる日付セルの最初の日付を戻す
    DateTime _getLastLineFromDay(DateTime focusedDay) {
      DateTime _lastLineFromDay;
      //表示されている月の1日
      DateTime _firstDay = DateTime(focusedDay.year, focusedDay.month, 1, 0, 0);
      //表示されている月の末日
      DateTime _lastDay = DateTime(focusedDay.year, focusedDay.month+1, 1, 0, 0).add(const Duration(days: -1));
      // 表示されている月の1日の曜日により、日:0、月:1、火:2、水:3、木:4、金:5、土:6
      // weekdayは1が月曜日、7が日曜日なので。
      int _wk1;
      if (_firstDay.weekday == 7) {
        _wk1 = 0;
      } else {
        _wk1 = _firstDay.weekday;
      }
      // (表示されている月の末日+WK1)/7日の余りにより、0:6、以外は、余り-1
      int _wk2 = (_lastDay.day + _wk1) % 7;
      int _wk3;
      if (_wk2 == 0) {
        _wk3 = 6;
      } else {
        _wk3 = _wk2 - 1;
      }
      // 表示されている月末日-WK3
      int _wk4 = _lastDay.day - _wk3;
      _lastLineFromDay = DateTime(focusedDay.year, focusedDay.month, _wk4, 0, 0);
      return _lastLineFromDay;
    }
    // 罫線表示に使用するのための、表示されている月の日付のセルで、カレンダー最下位行になる日付セルの最初の日付
    DateTime _lastLineFromDay = _getLastLineFromDay(_focusedDay);
    // 罫線表示に使用するのための、カレンダー最下位行になる日付セルの最初の日付の対象月
    DateTime _lastLineFromDayForFocusedDay = _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 _boxDecorationNormal(DateTime day, DateTime focusedDay, DateTime _lastLineFromDay) {
      BoxDecoration _boxDecoration;
      double _leftSize = 0.5;
      double _rightSize = 0.5;
      double _bottomSize = 0.5;
      double _topSize = 0.5;
      // 日曜の日付セル用
      if (day.weekday == DateTime.sunday) {
        _leftSize = 1.0;
      }
      // 土曜の日付セル用
      if (day.weekday == DateTime.saturday) {
        _rightSize = 1.0;
      }
      // 翌月の日付セル用
      if (day.year == focusedDay.year && day.month > focusedDay.month) {
        _bottomSize = 1.0;
      }
      // カレンダー最下位行になる当月の日付セル用
      if (day.year == focusedDay.year && day.month == focusedDay.month &&
          day.day >= _lastLineFromDay.day) {
        _bottomSize = 1.0;
      }
      _boxDecoration = BoxDecoration(
        border: Border(
          left: BorderSide(
            color: Colors.green[600]!,
            width: _leftSize,
          ),
          top: BorderSide(
            color: Colors.green[600]!,
            width: _topSize,
          ),
          right: BorderSide(
            color: Colors.green[600]!,
            width: _rightSize,
          ),
          bottom: BorderSide(
            color: Colors.green[600]!,
            width: _bottomSize,
          ),
        ),
      );
      return _boxDecoration;
    }

    /// 曜日セルの罫線をグリーンにする
    BoxDecoration _boWBoxDecoration(DateTime day) {
      BoxDecoration _boxDecoration;
      double _leftSize = 0.5;
      double _rightSize = 0.5;
      double _bottomSize = 0.5;
      double _topSize = 0.5;
      // 日曜の日付セル用
      if (day.weekday == DateTime.sunday) {
        _leftSize = 1.0;
      }
      // 土曜の日付セル用
      if (day.weekday == DateTime.saturday) {
        _rightSize = 1.0;
      }
      _topSize = 1.0;
      _boxDecoration = BoxDecoration(
        border: Border(
          left: BorderSide(
            color: Colors.green[600]!,
            width: _leftSize,
          ),
          top: BorderSide(
            color: Colors.green[600]!,
            width: _topSize,
          ),
          right: BorderSide(
            color: Colors.green[600]!,
            width: _rightSize,
          ),
          bottom: BorderSide(
            color: Colors.green[600]!,
            width: _bottomSize,
          ),
        ),
      );
      return _boxDecoration;
    }

    /// 項目入力日及び選択日の罫線を赤にする。以外はグリーンにする
    BoxDecoration _boxDecoration(DateTime day, DateTime focusedDay, DateTime _lastLineFromDay) {
      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 = _boxDecorationNormal(day, focusedDay, _lastLineFromDay);
      }
      return _boxDecoration;
    }

    /// todayBuilder向け。今日の罫線枠を赤にして、今日の日付を青丸文字にする
    AnimatedContainer _animatedContainerForToday(DateTime day, DateTime focusedDay, DateTime _lastLineFromDay) {
      return AnimatedContainer(
        duration: const Duration(milliseconds: 250),
        margin: EdgeInsets.zero,
        decoration: _boxDecoration(day, focusedDay, _lastLineFromDay),
        // 今日の日付を青丸文字にする
        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, DateTime focusedDay, DateTime _lastLineFromDay) {
      AnimatedContainer _animatedContainer;
      // 日付が今日の時
      if (isSameDay(day, DateTime.now())) {
        _animatedContainer = _animatedContainerForToday(day, focusedDay, _lastLineFromDay);
      } 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;
    }

    try {
      // 祝祭日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);
    } catch (e) {
      // holidays.xmlが取得できない場合、何もしない。
    }

    // 日付入力ダイヤログを表示する
    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) {
                      if (!isSameDay(_lastLineFromDayForFocusedDay, focusedDay)) {
                        _lastLineFromDay = _getLastLineFromDay(focusedDay);
                        _lastLineFromDayForFocusedDay = focusedDay;
                      }
                      return AnimatedContainer(
                        duration: const Duration(milliseconds: 250),
                        margin: EdgeInsets.zero,
                        decoration: _boxDecoration(day, focusedDay, _lastLineFromDay),
                        alignment: Alignment.topCenter,
                        child: Text(
                          day.day.toString(),
                          style: const TextStyle(
                            color: Colors.grey,
                          ),
                        ),
                      );
                    },
                    /// 有効範囲(firstDay~lastDay)以外の日付部分を生成する
                    disabledBuilder: (
                        BuildContext context, DateTime day, DateTime focusedDay) {
                      if (!isSameDay(_lastLineFromDayForFocusedDay, focusedDay)) {
                        _lastLineFromDay = _getLastLineFromDay(focusedDay);
                        _lastLineFromDayForFocusedDay = focusedDay;
                      }
                      return AnimatedContainer(
                        duration: const Duration(milliseconds: 250),
                        margin: EdgeInsets.zero,
                        decoration: _boxDecoration(day, focusedDay, _lastLineFromDay),
                        alignment: Alignment.topCenter,
                        child: Text(
                          day.day.toString(),
                          style: const TextStyle(
                            color: Colors.grey,
                          ),
                        ),
                      );
                    },
                    /// デフォルトのセルを枠線のついたContainerに置き換える
                    defaultBuilder: (
                        BuildContext context, DateTime day, DateTime focusedDay) {
                      if (!isSameDay(_lastLineFromDayForFocusedDay, focusedDay)) {
                        _lastLineFromDay = _getLastLineFromDay(focusedDay);
                        _lastLineFromDayForFocusedDay = focusedDay;
                      }
                      return AnimatedContainer(
                        duration: const Duration(milliseconds: 250),
                        margin: EdgeInsets.zero,
                        decoration: _boxDecoration(day, focusedDay, _lastLineFromDay),
                        alignment: Alignment.topCenter,
                        child: Text(
                          day.day.toString(),
                          style: TextStyle(
                            color: _textColor(day),
                          ),
                        ),
                      );
                    },
                    /// 今日のセルを枠線のついたContainerに置き換える
                    todayBuilder: (
                        BuildContext context, DateTime day, DateTime focusedDay) {
                      if (!isSameDay(_lastLineFromDayForFocusedDay, focusedDay)) {
                        _lastLineFromDay = _getLastLineFromDay(focusedDay);
                        _lastLineFromDayForFocusedDay = focusedDay;
                      }
                      return _animatedContainerForToday(day, focusedDay, _lastLineFromDay);
                    },
                    /// 曜日のセルを枠線のついたContainerに置き換える
                    dowBuilder: (BuildContext context, DateTime day) {
                      final dowText =
                          const DaysOfWeekStyle().dowTextFormatter?.call(day, 'ja') ??
                              DateFormat.E('ja').format(day);
                      return Container(
                        decoration: _boWBoxDecoration(day),
                        child: Center(
                          child: Text(
                            dowText,
                            style: TextStyle(
                              color: _textColor(day),
                            ),
                          ),
                        ),
                      );
                    },
                    // 選択されたセルを枠線のついたContainerに置き換える
                    selectedBuilder: (
                        BuildContext context, DateTime day, DateTime focusedDay) {
                      if (!isSameDay(_lastLineFromDayForFocusedDay, focusedDay)) {
                        _lastLineFromDay = _getLastLineFromDay(focusedDay);
                        _lastLineFromDayForFocusedDay = focusedDay;
                      }
                      return _animatedContainerForSelect(day, focusedDay, _lastLineFromDay);
                    },
                    /// イベントマーカーを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;
  }

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