16장 일정 관리 앱 만들기

송기영·2024년 1월 3일
0

플러터

목록 보기
18/25

이번 장은 17장과 이어지는 내용입니다.

16.1 사전지식

16.1.1 table_calendar 플러그인

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

void main() {
  runApp(MaterialApp(
    home: Scaffold(
        body: TableCalendar(
      focusedDay: DateTime.now(),
      firstDay: DateTime(1900, 1, 1),
      lastDay: DateTime(2999, 12, 31),
      // 선택된 날짜를 인식하는 함수
      selectedDayPredicate: (day) {
        final now = DateTime.now();
        return DateTime(day.year, day.month, day.day)
            .isAtSameMomentAs(DateTime(now.year, now.month, now.day));
      },
      // 날짜가 선택됐을 때
      onDaySelected: (selectedDay, focusedDay) {},
      // 날짜 페이지가 변경됐을 때
      onPageChanged: (focusedDay) {},
      // 기간 선택 모드
      rangeSelectionMode: RangeSelectionMode.toggledOff,
      // 기간 선택 됐을 때
      onRangeSelected: (start, end, focusedDay) {},
    )),
  ));
}

16.2 사전 준비

16.2.1 pubspec.yaml

dependencies:
  cupertino_icons: ^1.0.6
  dio: ^5.4.0 # 네트워크 요청
  drift: ^2.14.1 # drift 플러그인
  flutter:
    sdk: flutter
  get_it: ^7.6.4 # 프로젝트 전역으로 의존성 주입을 가능하게 하는 플러그인
  intl: ^0.18.1 # 다국어화 기능 지원
  path: ^1.8.3 # 경로 관련 기능을 제공하는 플러그인
  path_provider: ^2.1.1 # 경로 관련 기능을 제공하는 플러그인
  provider: ^6.1.1 # 상태 관리를 가능하게 하는 플러그인
  sqlite3_flutter_libs: ^0.5.18 # SQLite 플러그인
  table_calendar: ^3.0.9 # 달력 기능을 제공하는 플러그인
  uuid: ^4.3.1 # UUID를 생성하는 플러그인

16.3 구현하기

16.3.1 home_screen.dart

// lib/screen/home_screen.dart

import 'package:calendar_scheduler/screen/home_screen.dart';
import 'package:flutter/material.dart';
import 'package:intl/date_symbol_data_local.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  // intl 패키지 초기화(다국어화)
  await initializeDateFormatting("ko_kr", null);
  runApp(MaterialApp(home: HomeScreen()));
}

16.3.2 colors

// lib/const/colors.dart

import 'package:flutter/material.dart';

const PRIMARY_COLOR = Color(0xFF0DB2B2);
final LIGHT_GREY_COLOR = Colors.grey[200]!;
final DARK_GREY_COLOR = Colors.grey[600]!;
final TEXT_FIELD_FILL_COLOR = Colors.grey[300]!;

16.3.3 custom_test_field

// lib/component/custom_test_field.dart

import 'package:calendar_scheduler/const/colors.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

class CustomTextField extends StatelessWidget {
  final String label;
  final bool isTime;
  const CustomTextField({required this.label, required this.isTime, super.key});

  
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text(
          label,
          style: const TextStyle(
            color: PRIMARY_COLOR,
            fontWeight: FontWeight.w600,
          ),
        ),
        Expanded(
          flex: isTime ? 0 : 1,
          child: TextFormField(
            cursorColor: Colors.grey,
            // 시간 관련 텍스트 필드가 아니면 한 줄 이상 작성 가능
            maxLines: isTime ? 1 : null,
            expands: !isTime,
            // 시간 관련 텍스트 필드는 기본 숫자 키보드 아니면 일반 글자 키보드 보여주기
            keyboardType:
                isTime ? TextInputType.number : TextInputType.multiline,
            // 시간 관련 텍스트 필드는 숫자만 입력하도록 제한
            inputFormatters: isTime
                ? [
                    FilteringTextInputFormatter.digitsOnly,
                  ]
                : [],
            decoration: InputDecoration(
                // 테두리 삭제
                border: InputBorder.none,
                // 배경색 지정
                filled: true,
                // 배경색
                fillColor: Colors.grey[300],
                // 접미사 추가
                suffixText: isTime ? "시" : null),
          ),
        ),
      ],
    );
  }
}

16.3.4 main_calendar

// lib/component/main_calendar.dart

import 'package:calendar_scheduler/const/colors.dart';
import 'package:flutter/material.dart';
import 'package:table_calendar/table_calendar.dart';

class MainCalendar extends StatelessWidget {
  final OnDaySelected onDaySelected; // 날짜 선택 시 실행할 함수
  final DateTime selectedDate; // 선택된 날짜

  const MainCalendar(
      {required this.onDaySelected, required this.selectedDate, super.key});

  
  Widget build(BuildContext context) {
    return TableCalendar(
      locale: "ko_kr",
      onDaySelected: onDaySelected,
      selectedDayPredicate: (date) {
        // 선택된 날짜를 구분할 로직
        return date.year == selectedDate.year &&
            date.month == selectedDate.month &&
            date.day == selectedDate.day;
      },
      focusedDay: DateTime.now(),
      firstDay: DateTime(1900, 1, 1),
      lastDay: DateTime(2999, 12, 31),
      headerStyle: const HeaderStyle(
          // 달력 최상단 스타일
          titleCentered: true, // 제목 중앙에 위치하기
          formatButtonVisible: false, // 달력 크기 선택 옵션 없애기 2week
          titleTextStyle:
              TextStyle(fontWeight: FontWeight.w700, fontSize: 16.0)),
      calendarStyle: CalendarStyle(
          isTodayHighlighted: false,
          defaultDecoration: BoxDecoration(
              // 기본 날짜 스타일
              borderRadius: BorderRadius.circular(6.0),
              color: LIGHT_GREY_COLOR),
          weekendDecoration: BoxDecoration(
            // 주말 날짜 스타일
            borderRadius: BorderRadius.circular(6.0),
            color: LIGHT_GREY_COLOR,
          ),
          selectedDecoration: BoxDecoration(
              borderRadius: BorderRadius.circular(6.0),
              border: Border.all(color: PRIMARY_COLOR, width: 1.0)),
          defaultTextStyle:
              TextStyle(fontWeight: FontWeight.w600, color: DARK_GREY_COLOR),
          weekendTextStyle:
              TextStyle(fontWeight: FontWeight.w600, color: DARK_GREY_COLOR),
          selectedTextStyle: const TextStyle(
              fontWeight: FontWeight.w600, color: PRIMARY_COLOR)),
    );
  }
}

16.3.5 schedule_bottom_sheet

// lib/component/schedule_bottom_sheet.dart

import 'package:calendar_scheduler/component/custom_test_field.dart';
import 'package:calendar_scheduler/const/colors.dart';
import 'package:flutter/material.dart';

class ScheduleBottomSheet extends StatefulWidget {
  const ScheduleBottomSheet({super.key});

  
  State<ScheduleBottomSheet> createState() => _ScheduleBottomSheetState();
}

class _ScheduleBottomSheetState extends State<ScheduleBottomSheet> {
  
  Widget build(BuildContext context) {
    // 키보드 높이 가져오기
    final bottomInset = MediaQuery.of(context).viewInsets.bottom;
    return SafeArea(
      child: Container(
        // 화면의 반을 차지
        height: MediaQuery.of(context).size.height / 2 + bottomInset,
        color: Colors.white,
        child: Padding(
          padding:
              // 패딩에 키보드 높이를 추가해서 위젯 전반적으로 위로 올려주기
              EdgeInsets.only(left: 8, right: 8, top: 8, bottom: bottomInset),
          child: Column(
            children: [
              const Row(
                children: [
                  Expanded(
                      child: CustomTextField(
                    label: "시작 시간",
                    isTime: true,
                  )),
                  SizedBox(
                    width: 16.0,
                  ),
                  Expanded(
                      child: CustomTextField(
                    label: "종료 시간",
                    isTime: true,
                  )),
                ],
              ),
              const SizedBox(
                height: 8.0,
              ),
              const Expanded(
                  child: CustomTextField(label: "내용", isTime: false)),
              SizedBox(
                width: double.infinity,
                child: ElevatedButton(
                  onPressed: () {},
                  style: ElevatedButton.styleFrom(
                    shape: RoundedRectangleBorder(
                        borderRadius: BorderRadius.circular(1)),
                    backgroundColor: PRIMARY_COLOR,
                  ),
                  child: const Text(
                    "저장",
                    style: TextStyle(color: Colors.white),
                  ),
                ),
              )
            ],
          ),
        ),
      ),
    );
  }

  void onSavePressed() {}
}

16.3.6 schedule_card

// lib/component/schedule_card.dart

import 'package:calendar_scheduler/const/colors.dart';
import 'package:flutter/material.dart';

class ScheduleCard extends StatelessWidget {
  final int startTime;
  final int endTime;
  final String content;

  const ScheduleCard(
      {required this.startTime,
      required this.endTime,
      required this.content,
      super.key});

  
  Widget build(BuildContext context) {
    return Container(
      decoration: BoxDecoration(
          border: Border.all(width: 1.0, color: PRIMARY_COLOR),
          borderRadius: BorderRadius.circular(8.0)),
      child: Padding(
        padding: const EdgeInsets.all(16.0),
        // 높이를 내부 위젯들의 최대 높이로 설정

        child: IntrinsicHeight(
            child: Row(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            _Time(startTime: startTime, endTime: endTime),
            const SizedBox(
              width: 16.0,
            ),
            _Content(content: content),
            const SizedBox(
              width: 16.0,
            ),
          ],
        )),
      ),
    );
  }
}

class _Time extends StatelessWidget {
  final int startTime; // 시작시간
  final int endTime; // 종료시간

  const _Time({required this.startTime, required this.endTime, super.key});

  
  Widget build(BuildContext context) {
    const textStyle = TextStyle(
      fontWeight: FontWeight.w600,
      color: PRIMARY_COLOR,
      fontSize: 16.0,
    );

    return Column(
      // 시간을 위에서 아래로 배치
      crossAxisAlignment: CrossAxisAlignment.start,

      children: [
        // 숫자가 두 자리수가 안되면 0으로 채워주기
        Text(
          '${startTime.toString().padLeft(2, '0')}:00',
          style: textStyle,
        ),
        Text(
          '${endTime.toString().padLeft(2, '0')}:00',
          style: textStyle.copyWith(fontSize: 16.0),
        )
      ],
    );
  }
}

class _Content extends StatelessWidget {
  final String content; // 내용

  const _Content({required this.content, super.key});

  
  Widget build(BuildContext context) {
    return Expanded(
      child: Text(content),
    );
  }
}

16.3.7 today_banner

// lib/component/today_banner.dart

import 'package:calendar_scheduler/const/colors.dart';
import 'package:flutter/material.dart';

class TodayBanner extends StatelessWidget {
  final DateTime selectedDate;
  final int count;

  const TodayBanner(
      {required this.selectedDate, required this.count, super.key});

  
  Widget build(BuildContext context) {
    const textStyle =
        TextStyle(fontWeight: FontWeight.w600, color: Colors.white);
    return Container(
      color: PRIMARY_COLOR,
      child: Padding(
        padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
        child:
            Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
          Text(
            "${selectedDate.year}년 ${selectedDate.month}월 ${selectedDate.day}일",
            style: textStyle,
          ),
          Text(
            "$count개",
            style: textStyle,
          )
        ]),
      ),
    );
  }
}

💡Tips : IntrinsicHeight 위젯이 이해가 가지 않아 찾아봤는데 자식들중 가장 크기가 큰놈에게 크기를 맞춘다고 한다. 하지만 가능한 사용을 지양하라고 하는데 그래서 대체 방안을 찾아보았으나 딱히 원하는 결과를 찾지는 못했다..

expaned와 flexible의 경우 가로는 정상적으로 작동하지만 세로의 경우는 미미할 수 있다고 한다.

profile
업무하면서 쌓인 노하우를 정리하는 블로그🚀 풀스택 개발자를 지향하고 있습니다👻

0개의 댓글