본문 바로가기
개발/Flutter

flutter widget 달력 만들기(2)

by dev_caleb 2022. 10. 29.
728x90

저번에 만들었던 달력에 기능을 조금 추가해보았다

달력에 기능을 좀 추가했더니 식단표를 만들 수 있었다.

 

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:todaymenu_flutter1/constants/device_info.dart';
import 'package:todaymenu_flutter1/widget/popup/pop_up.dart';

import '../../../constants/app_colors.dart';
import '../../../constants/common_size.dart';
import '../../../constants/logger.dart';
import '../../../tools/time_tools.dart';
import '../../../widget/text_widgets.dart';

class CalendarWidget extends StatefulWidget {
  CalendarWidget({required this.onChanged, Key? key}) : super(key: key);
  ValueChanged<DateTime>? onChanged;

  @override
  State<CalendarWidget> createState() => CalendarWidgetState();
}

class CalendarWidgetState extends State<CalendarWidget> {
  final int columnCount = 7;
  late int rowCount;

  List<DateTime?> monthDateTimeList = [];
  Map<String, String> menuMap = {};
  double weekRowHeight = Get.height * 0.05;
  List weekDayString = ['일', '월', '화', '수', '목', '금', '토'];
  DateTime selectedDate = DateTime.now();
  final TextEditingController _menuTextController = TextEditingController();

  @override
  void initState() {
    initialSettings();
    super.initState();
  }

  @override
  void dispose() {
    _menuTextController.dispose();
    super.dispose();
  }

  void initialSettings() async {
    DateTime selectedMonthDay =
        DateTime(selectedDate.year, selectedDate.month, 1);
    monthDateTimeList =
        List.generate(selectedMonthDay.weekday, (index) => null);
    List<DateTime> days = List.generate(countDayOfMonth(selectedMonthDay),
        (index) => selectedMonthDay.add(Duration(days: index)));
    monthDateTimeList.addAll(days);
    monthDateTimeList.addAll(List.generate(
        columnCount - (monthDateTimeList.length % columnCount),
        (index) => null));
    rowCount = (monthDateTimeList.length ~/ columnCount);
    logger.d('monthDateTimeList length => ${monthDateTimeList.length}');
    logger.d('rowCount => $rowCount');
    loadDataFromFirebase();
  }

  void loadDataFromFirebase() {
    showProgressbar(barrierDismissible: false);

    Get.until((route) => route is! PopupRoute); //팝업창 모두 끄기
  }

  void onSelectDate(DateTime selectDate) {
    widget.onChanged?.call(selectDate);
    setSelectedDate(selectDate);
  }

  void setSelectedDate(DateTime selectedDate) {
    DateTime preSavedDateTime = DateTime(
        this.selectedDate.year, this.selectedDate.month, this.selectedDate.day);
    setState(() {
      this.selectedDate = selectedDate;
      if (!isSameMonth(selectedDate, preSavedDateTime)) {
        initialSettings();
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(builder: (context, constraints) {
      logger.d('max height -> ${constraints.maxHeight}');
      return Table(
          border: TableBorder.all(color: Colors.grey[200]!, width: 1.5),
          children: <TableRow>[
            _buildWeekRow(),
            ...List.generate(
              rowCount,
              (rowIndex) => TableRow(children: <Widget>[
                ...List.generate(columnCount, (columnIndex) {
                  int tableIndex = columnCount * rowIndex + columnIndex;
                  return _buildTableCell(tableIndex, constraints, columnIndex);
                })
              ]),
            )
          ]);
    });
  }

  InkWell _buildTableCell(
      int tableIndex, BoxConstraints constraints, int columnIndex) {
    return InkWell(
      onTap: () {
        onClickCell(tableIndex);
      },
      child: Container(
        color: isSameDay(selectedDate, (monthDateTimeList[tableIndex]))
            ? Colors.grey[300]
            : Colors.white,
        height: (constraints.maxHeight - weekRowHeight) / rowCount,
        child: Column(
          children: [
            Text(
              '${monthDateTimeList[tableIndex]?.day ?? ''}${monthDateTimeList[tableIndex] == null ? '' : '일'}',
              style: TextStyle(color: (columnIndex) == 0 ? Colors.red : null),
            ),
            Expanded(
              child: Visibility(
                visible: monthDateTimeList[tableIndex] != null &&
                    (menuMap[df(monthDateTimeList[tableIndex] ??
                                DateTime(1900, 1, 1))]
                            ?.isNotEmpty ??
                        false),
                child: isWeb
                    ? Text(
                        menuMap[df(monthDateTimeList[tableIndex] ??
                                DateTime(1900, 1, 1))] ??
                            '',
                        style: const TextStyle(fontSize: 12),
                      )
                    : Container(
                        width: 10,
                        height: 10,
                        // margin: EdgeInsets.all(20.0),
                        decoration: const BoxDecoration(
                            color: AppColors.mainColor, shape: BoxShape.circle),
                      ),
              ),
            ),
          ],
        ),
      ),
    );
  }

  TableRow _buildWeekRow() {
    return TableRow(
        children: List.generate(
            columnCount,
            (weekIdx) => SizedBox(
                height: weekRowHeight,
                child: Center(
                    child: Text(
                  weekDayString[weekIdx],
                  style: TextStyle(color: (weekIdx) == 0 ? Colors.red : null),
                )))));
  }

  void readExcelData(List<String> menuList) {
    DateTime selectedMonthDay =
        DateTime(selectedDate.year, selectedDate.month, 1);
    for (int i = 0; i < menuList.length; i++) {
      menuMap[df(selectedMonthDay.add(Duration(days: i)))] = menuList[i] ?? '';
    }
    logger.d('menuMap -> $menuMap');
    setState(() {});
  }

  String df(DateTime dateTime) => DateFormat('yyMMdd').format(dateTime);
  void onClickCell(int index) async {
    if (monthDateTimeList[index] == null) {
      return;
    } else {
      onSelectDate(monthDateTimeList[index]!);
      String? resultStr = await Get.dialog(
        _dialog(index),
        barrierDismissible: true,
        barrierColor: const Color(0x80000000).withOpacity(0),
      );
      if (resultStr != null) {
        logger.d('menuList[$index] -> $resultStr');
        setState(() {
          menuMap[df(monthDateTimeList[index]!)] = resultStr;
        });
      }
    }
  }

  //하면 잔상 같은게 남아서 deprecated 됨
  void _scaleDialog(int index) {
    showGeneralDialog(
      context: context,
      barrierDismissible: true,
      barrierColor: const Color(0x80000000).withOpacity(0),
      barrierLabel: '$index',
      pageBuilder: (ctx, a1, a2) {
        return Container();
      },
      transitionBuilder: (ctx, a1, a2, child) {
        var curve = Curves.easeInOut.transform(a1.value);
        return Transform.scale(
          scale: curve,
          child: _dialog(index),
        );
      },
      transitionDuration: const Duration(milliseconds: 300),
    );
  }

  Widget _dialog(int index) {
    _menuTextController.text = menuMap[df(monthDateTimeList[index]!)] ?? '';
    return AlertDialog(
      title: Text(
          DateFormat('yy.MM.dd(E)', 'ko_KR').format(monthDateTimeList[index]!)),
      titleTextStyle: const TextStyle(
          fontSize: 17, color: Colors.black54, fontWeight: FontWeight.w600),
      content: TextField(
          controller: _menuTextController,
          style:
              const TextStyle(fontSize: fontSizeL, fontWeight: FontWeight.w500),
          keyboardType: TextInputType.multiline,
          minLines: isWeb ? 10 : 5,
          maxLines: isWeb ? 17 : 8,
          autofocus: true,
          decoration: InputDecoration(
              isDense: true,
              border: OutlineInputBorder(
                  borderRadius: BorderRadius.circular(10.0),
                  borderSide: const BorderSide(
                    width: 0,
                    style: BorderStyle.none,
                  )),
              fillColor: Colors.grey[300],
              filled: true)),
      contentPadding:
          const EdgeInsets.only(left: 20, right: 20, top: 10, bottom: 0),
      actions: [
        TextButton(
            onPressed: () {
              Get.back(result: '');
            },
            child: textBigButton('지우기', textColor: AppColors.cancelColor1)),
        TextButton(
            onPressed: () {
              Get.back(result: _menuTextController.text.trim());
              _menuTextController.clear();
            },
            child: textBigButton('확인'))
      ],
    );
  }
}

근데 내가 봤을 때 문제는 무엇이냐.. 큰 문제는 아니지만 외부에서 위젯 내부의 함수를 호출할 때, 

key를 통해서 접근하고 있다는 뜻이다. 가령

GlobalKey<CalendarWidgetState> calendarKey = GlobalKey<CalendarWidgetState>();
calendarKey.currentState?.onSelectDate(pickedDate);

이런 식으로 위젯 내부의 키에 접근하게 되는데 이건 좋은 방법은 아닌 것 같다. 내가 경험했던 대부분의 widget library는 controller를 사용해서 해결했던 것 같다. 그래서 나도 컨트롤러를 만들어주기로 했다. 

 

https://www.flutterclutter.dev/flutter/tutorials/create-a-controller-for-a-custom-widget/2021/2149/

 

Create a controller for a custom widget

This tutorial shows how to create a custom controller for a self-implemented widget to manage its state from the outside.

www.flutterclutter.dev

 

이걸 보고 학습해보았다. ChangeNotifier 는 Provider에 있던 건데.. 공부하다보니 뭔가 세계관 통합? 같은 느낌이어서 재미있다.

공부를 하면서 코드를 수행하다보니, 사실 변수들이나 함수를 GetX controller에 넣지 않고 widget 에 넣어놓음으로써 관심사를 분리할 수 있어서 좋았는데, controller class 를 만들어주게 되면 또 controller에서 변수를 오가게 되어 좀 더 복잡해지는 느낌이 있었다. 굳이 이래야하나? 싶은 생각이 들었다. 해당 컨트롤러가 여러 개의 widget을 컨트롤 할 수 있다면 몰라도( ex. textEditController, scrollController) 굳이 1개를 위해서 만드는 것이 코드가 예뻐보이지가 않는다. 그래서 일단은 state로 function이나, 변수를 가져와서 계속 쓰기로 했다^^

 

728x90

'개발 > Flutter' 카테고리의 다른 글

flutter open_file library -> REQUEST_INSTALL_PACKAGES error  (0) 2022.10.30
list to map, map to list in dart  (0) 2022.10.29
flutter enum  (0) 2022.10.29
excel library 오류  (0) 2022.10.29
flutter multiline Text overFlow  (0) 2022.10.28