From 7671f47afb49c831ebcef70fa355d894bb8ef069 Mon Sep 17 00:00:00 2001 From: jay Date: Tue, 11 Jun 2024 08:01:11 +0900 Subject: [PATCH 1/3] =?UTF-8?q?=E2=9C=A8=20HOME=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=9D=BC=EB=B6=80=20=EA=B5=AC=ED=98=84=20db=EB=AA=85=EC=84=B8?= =?UTF-8?q?=EC=84=9C=EC=97=90=20=EB=94=B0=EB=9D=BC=20tbl=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1.=20firebase=EC=97=90=EC=84=9C=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=EB=A5=BC=20=EB=B0=9B=EC=95=84=EC=98=A4=EB=8A=94=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EC=9E=91=EC=97=85=20=ED=95=84=EC=9A=94.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/domain/repository/memo_repository.dart | 12 + .../repository/schedule_repository.dart | 23 + lib/domain/use_case/memo_use_case.dart | 12 + lib/domain/use_case/schedule_use_case.dart | 23 + lib/entity/memo_tbl.dart | 39 ++ lib/entity/schedule_tbl.dart | 60 +++ lib/entity/user_tbl.dart | 49 ++- .../home/controller/home_memo_controller.dart | 20 + .../controller/home_schedule_controller.dart | 19 + lib/ui/home/view/home_main.dart | 411 +++++++++++------- lib/ui/todo/view/todo_main_screen.dart | 243 ++++++++--- lib/ui/todo/widget/calendar_cell_custom.dart | 189 ++++++++ lib/ui/todo/widget/calendar_tab.dart | 15 - lib/ui/todo/widget/calendar_widget.dart | 81 ---- lib/ui/todo/widget/main_tab_bar.dart | 1 - lib/ui/todo/widget/select_share_option.dart | 3 +- lib/ui/todo/widget/todo_controller.dart | 54 +-- .../todo/widget/todo_edit_bottom_sheet.dart | 323 ++++++-------- lib/ui/todo/widget/todo_item.dart | 2 +- lib/ui/todo/widget/todo_item_data.dart | 11 + lib/ui/todo/widget/todo_list_tab.dart | 53 --- lib/ui/todo/widget/todo_page_share.dart | 3 +- lib/ui/todo/widget/todo_share_with_list.dart | 1 + 23 files changed, 1038 insertions(+), 609 deletions(-) create mode 100644 lib/domain/repository/memo_repository.dart create mode 100644 lib/domain/repository/schedule_repository.dart create mode 100644 lib/domain/use_case/memo_use_case.dart create mode 100644 lib/domain/use_case/schedule_use_case.dart create mode 100644 lib/entity/memo_tbl.dart create mode 100644 lib/entity/schedule_tbl.dart create mode 100644 lib/ui/home/controller/home_memo_controller.dart create mode 100644 lib/ui/home/controller/home_schedule_controller.dart create mode 100644 lib/ui/todo/widget/calendar_cell_custom.dart delete mode 100644 lib/ui/todo/widget/calendar_tab.dart delete mode 100644 lib/ui/todo/widget/calendar_widget.dart create mode 100644 lib/ui/todo/widget/todo_item_data.dart delete mode 100644 lib/ui/todo/widget/todo_list_tab.dart diff --git a/lib/domain/repository/memo_repository.dart b/lib/domain/repository/memo_repository.dart new file mode 100644 index 0000000..294c1c0 --- /dev/null +++ b/lib/domain/repository/memo_repository.dart @@ -0,0 +1,12 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:schedule_with/entity/memo_tbl.dart'; + +class MemoRepository { + final FirebaseFirestore _db = FirebaseFirestore.instance; + + Stream> getMemos() { + return _db.collection('memo') + .snapshots() + .map((snapshot) => snapshot.docs.map((doc) => Memo.fromFirestore(doc.data() as Map)).toList()); + } +} diff --git a/lib/domain/repository/schedule_repository.dart b/lib/domain/repository/schedule_repository.dart new file mode 100644 index 0000000..c37565f --- /dev/null +++ b/lib/domain/repository/schedule_repository.dart @@ -0,0 +1,23 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:schedule_with/entity/schedule_tbl.dart'; + +class ScheduleRepository { + final FirebaseFirestore _db = FirebaseFirestore.instance; + + // 스케줄 목록을 가져오는 메서드 + Stream> getSchedules() { + return _db.collection('schedule') + .snapshots() + .map((snapshot) => snapshot.docs.map((doc) => Schedule.fromFirestore(doc.data() as Map)).toList()); + } + + // 스케줄을 업데이트하는 메서드 + Future updateSchedule(String id, Map data) async { + await _db.collection('schedule').doc(id).update(data); + } + + // 새로운 스케줄을 추가하는 메서드 + Future addSchedule(Map data) async { + await _db.collection('schedule').add(data); + } +} diff --git a/lib/domain/use_case/memo_use_case.dart b/lib/domain/use_case/memo_use_case.dart new file mode 100644 index 0000000..ecdc24e --- /dev/null +++ b/lib/domain/use_case/memo_use_case.dart @@ -0,0 +1,12 @@ +import 'package:schedule_with/domain/repository/memo_repository.dart'; +import 'package:schedule_with/entity/memo_tbl.dart'; + +class MemoUseCase { + final MemoRepository repository; + + MemoUseCase(this.repository); + + Stream> getMemos() { + return repository.getMemos(); + } +} diff --git a/lib/domain/use_case/schedule_use_case.dart b/lib/domain/use_case/schedule_use_case.dart new file mode 100644 index 0000000..073c44e --- /dev/null +++ b/lib/domain/use_case/schedule_use_case.dart @@ -0,0 +1,23 @@ +import 'package:schedule_with/domain/repository/schedule_repository.dart'; +import 'package:schedule_with/entity/schedule_tbl.dart'; + +class ScheduleUseCase { + final ScheduleRepository repository; + + ScheduleUseCase(this.repository); + + // 스케줄 목록을 가져오는 메서드 + Stream> getSchedules() { + return repository.getSchedules(); + } + + // 스케줄을 업데이트하는 메서드 + Future updateSchedule(String id, Map data) { + return repository.updateSchedule(id, data); + } + + // 새로운 스케줄을 추가하는 메서드 + Future addSchedule(Map data) { + return repository.addSchedule(data); + } +} diff --git a/lib/entity/memo_tbl.dart b/lib/entity/memo_tbl.dart new file mode 100644 index 0000000..99d3089 --- /dev/null +++ b/lib/entity/memo_tbl.dart @@ -0,0 +1,39 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; + +class Memo { + final int idx; + final int userIdx; + final int? groupIdx; // Nullable + final String title; + final String content; + final bool publicStatus; + final String status; + final DateTime regDt; + final DateTime modDt; + + Memo({ + required this.idx, + required this.userIdx, + this.groupIdx, + required this.title, + required this.content, + required this.publicStatus, + required this.status, + required this.regDt, + required this.modDt, + }); + + factory Memo.fromFirestore(Map data) { + return Memo( + idx: data['idx'], + userIdx: data['user_idx'], + groupIdx: data['group_idx'], + title: data['title'], + content: data['content'], + publicStatus: data['public_status'], + status: data['status'], + regDt: (data['reg_dt'] as Timestamp).toDate(), + modDt: (data['mod_dt'] as Timestamp).toDate(), + ); + } +} diff --git a/lib/entity/schedule_tbl.dart b/lib/entity/schedule_tbl.dart new file mode 100644 index 0000000..0154e50 --- /dev/null +++ b/lib/entity/schedule_tbl.dart @@ -0,0 +1,60 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; + +class Schedule { + final int? idx; + final int? userIdx; + final int? groupIdx; + final int? alarmIdx; + final String? title; + final DateTime? startDt; + final DateTime? endDt; + final String? startTime; + final String? endTime; + final String? color; + final String? content; + final String? publicStatus; + final String? status; + final DateTime? regDt; + final DateTime? modDt; + final bool? alarmStatus; + + Schedule({ + this.idx, + this.userIdx, + this.groupIdx, + this.alarmIdx, + this.title, + this.startDt, + this.endDt, + this.startTime, + this.endTime, + this.color, + this.content, + this.publicStatus, + this.status, + this.regDt, + this.modDt, + this.alarmStatus, + }); + + factory Schedule.fromFirestore(Map data) { + return Schedule( + idx: data['idx'] as int?, + userIdx: data['user_idx'] as int?, + groupIdx: data['group_idx'] as int?, + alarmIdx: data['alarm_idx'] as int?, + title: data['title'] as String?, + startDt: (data['start_dt'] as Timestamp?)?.toDate(), + endDt: (data['end_dt'] as Timestamp?)?.toDate(), + startTime: data['start_time'] as String?, + endTime: data['end_time'] as String?, + color: data['color'] as String?, + content: data['content'] as String?, + publicStatus: data['public'] as String?, + status: data['status'] as String?, + regDt: (data['reg_dt'] as Timestamp?)?.toDate(), + modDt: (data['mod_dt'] as Timestamp?)?.toDate(), + alarmStatus: data['alarm_status'] as bool?, + ); + } +} diff --git a/lib/entity/user_tbl.dart b/lib/entity/user_tbl.dart index 5ea7b8b..8e9d403 100644 --- a/lib/entity/user_tbl.dart +++ b/lib/entity/user_tbl.dart @@ -1,47 +1,62 @@ import 'dart:ffi'; + // firebase에 저장하기 위한 userModel -class Users{ +class Users { // 유저 이름 String? name; + // 유저번호 int idx; + // 비회원 id String server_id; + // 프로필 이미지 String? profile_img; + // 배경 이미지 String? back_img; + // 회원 id String? id; + // 회원 비밀번호 String? pw; + // 회원 이메일 String? email; + // 회원 생일 String? birth; + // 회원 성별 String? gender; + // 회원 상태 String status; + // 등록일 DateTime reg_dt; + // 수정일 DateTime? mod_dt; + // 로그인 실패 횟수 int login_fail_cnt; - Users({ - required this.name, - required this.idx, - required this.server_id, - required this.profile_img, - required this.back_img, - required this.id, - required this.pw, - required this.email, - required this.birth, - required this.gender, - required this.status, - required this.reg_dt, - required this.mod_dt, - required this.login_fail_cnt -});} + + Users( + {required this.name, + required this.idx, + required this.server_id, + required this.profile_img, + required this.back_img, + required this.id, + required this.pw, + required this.email, + required this.birth, + required this.gender, + required this.status, + required this.reg_dt, + required this.mod_dt, + required this.login_fail_cnt}); +} diff --git a/lib/ui/home/controller/home_memo_controller.dart b/lib/ui/home/controller/home_memo_controller.dart new file mode 100644 index 0000000..9733ec9 --- /dev/null +++ b/lib/ui/home/controller/home_memo_controller.dart @@ -0,0 +1,20 @@ +import 'package:get/get.dart'; +import 'package:schedule_with/entity/memo_tbl.dart'; + +import '../../../domain/use_case/memo_use_case.dart'; + +class HomeMemoController extends GetxController { + final MemoUseCase memoUseCase; + var memos = [].obs; + + HomeMemoController({required this.memoUseCase}); + + @override + void onInit() { + super.onInit(); + memoUseCase.getMemos().listen((data) { + print('Firestore data: $data'); // Firestore에서 가져온 데이터를 출력하여 확인 + memos.value = data; + }); + } +} diff --git a/lib/ui/home/controller/home_schedule_controller.dart b/lib/ui/home/controller/home_schedule_controller.dart new file mode 100644 index 0000000..037e52f --- /dev/null +++ b/lib/ui/home/controller/home_schedule_controller.dart @@ -0,0 +1,19 @@ +import 'package:get/get.dart'; +import 'package:schedule_with/entity/schedule_tbl.dart'; +import '../../../domain/use_case/schedule_use_case.dart'; + +class HomeScheduleController extends GetxController { + final ScheduleUseCase scheduleUseCase; + var schedules = [].obs; + + HomeScheduleController({required this.scheduleUseCase}); + + @override + void onInit() { + super.onInit(); + scheduleUseCase.getSchedules().listen((data) { + print(data); // 데이터 로그 출력 + schedules.value = data; + }); + } +} diff --git a/lib/ui/home/view/home_main.dart b/lib/ui/home/view/home_main.dart index 443a916..a72b1bb 100644 --- a/lib/ui/home/view/home_main.dart +++ b/lib/ui/home/view/home_main.dart @@ -8,6 +8,13 @@ import 'package:schedule_with/ui/home/widget/icon_and_text.dart'; import 'package:schedule_with/widget/main_app_bar.dart'; import 'package:smooth_page_indicator/smooth_page_indicator.dart'; import 'package:syncfusion_flutter_calendar/calendar.dart'; +import '../../../domain/repository/memo_repository.dart'; +import '../../../domain/repository/schedule_repository.dart'; +import '../../../domain/use_case/memo_use_case.dart'; +import '../../../domain/use_case/schedule_use_case.dart'; +import '../../../entity/schedule_tbl.dart'; +import '../controller/home_memo_controller.dart'; +import '../controller/home_schedule_controller.dart'; class HomeMain extends StatefulWidget { const HomeMain({super.key}); @@ -17,7 +24,6 @@ class HomeMain extends StatefulWidget { } class _HomeMainState extends State { - var adImages = [ Image.asset("lib/assets/image/logo.png"), Image.asset("lib/assets/image/logo.png"), @@ -28,6 +34,18 @@ class _HomeMainState extends State { var imagePosition = 0; + @override + void initState() { + super.initState(); + final scheduleRepository = ScheduleRepository(); + final scheduleUseCase = ScheduleUseCase(scheduleRepository); + Get.put(HomeScheduleController(scheduleUseCase: scheduleUseCase)); + + final memoRepository = MemoRepository(); + final memoUseCase = MemoUseCase(memoRepository); + Get.put(HomeMemoController(memoUseCase: memoUseCase)); + } + @override Widget build(BuildContext context) { return MaterialApp( @@ -36,43 +54,36 @@ class _HomeMainState extends State { GlobalWidgetsLocalizations.delegate ], supportedLocales: [ - const Locale("ko","KO") + const Locale("ko", "KO") ], debugShowCheckedModeBanner: false, home: Scaffold( - // 화면 배경색 설정 backgroundColor: Colors.white, - // 상단 툴바 appBar: PreferredSize( preferredSize: Size.fromHeight(50), child: MainAppBar(), ), - // 홈화면에 보여줄 부분 body: SingleChildScrollView( child: Column( children: [ // 그룹 Container( - // 그룹View의 여백 설정 margin: EdgeInsets.fromLTRB(10, 10, 10, 10), - // 그룹View의 그림자 및 radius 설정 decoration: BoxDecoration( - borderRadius: BorderRadius.circular(15), - color: Colors.white, - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.7), - spreadRadius: 0, - blurRadius: 8.0, - offset: Offset(0, 5), // changes position of shadow - ) - ] + borderRadius: BorderRadius.circular(15), + color: Colors.white, + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.7), + spreadRadius: 0, + blurRadius: 8.0, + offset: Offset(0, 5), + ) + ], ), - // 그룹 내용 child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // 아이콘 및 "그룹" 텍스트 출력 Container( child: IconAndText( iconRoute: 'lib/assets/icon/icon_group_outline.svg', @@ -82,14 +93,11 @@ class _HomeMainState extends State { color: Colors.transparent, padding: EdgeInsets.fromLTRB(10, 10, 10, 5), ), - // 그룹 초대 및 그룹들 리스트 Container( - height: 100, // 원하는 높이 설정 + height: 100, padding: EdgeInsets.fromLTRB(10, 10, 10, 5), child: ListView.builder( - // 가로 스크롤 scrollDirection: Axis.horizontal, - // 보여질 항목의 개수 itemCount: 1, itemBuilder: (context, index) { return GroupListItem(); @@ -103,16 +111,16 @@ class _HomeMainState extends State { Container( margin: EdgeInsets.fromLTRB(10, 10, 10, 10), decoration: BoxDecoration( - borderRadius: BorderRadius.circular(15), - color: Colors.white, - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.7), - spreadRadius: 0, - blurRadius: 8.0, - offset: Offset(0, 5), // changes position of shadow - ) - ] + borderRadius: BorderRadius.circular(15), + color: Colors.white, + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.7), + spreadRadius: 0, + blurRadius: 8.0, + offset: Offset(0, 5), + ) + ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -126,29 +134,38 @@ class _HomeMainState extends State { padding: EdgeInsets.fromLTRB(10, 10, 10, 5), ), Container( - margin: EdgeInsets.fromLTRB(15, 0, 15, 15), - child: Row( + margin: EdgeInsets.fromLTRB(15, 0, 15, 15), + child: Obx(() { + final memoController = Get.find(); + if (memoController.memos.isEmpty) { + return Text( + "등록된 메모가 없습니다.", + style: TextStyle(color: grey3), + ); + } + final memo = memoController.memos.first; + return Row( children: [ Expanded( - flex: 3, - child: Container( - margin: EdgeInsets.only(right: 10), - child: Text( - "메모내용입니다.메모내용입니다.메모내용입니다.메모내용입니다.메모내용입니다.메모내용입니다.메모내용입니다.메모내용입니다.메모내용입니다.메모내용입니다.메모내용입니다.메모내용입니다.메모내용입니다.메모내용입니다.메모내용입니다.메모내용입니다.메모내용입니다.메모내용입니다.메모내용입니다.", - maxLines: 4, - overflow: TextOverflow.ellipsis, - style: TextStyle( - fontSize: 12 - ), - ), - ) + flex: 3, + child: Container( + margin: EdgeInsets.only(right: 10), + child: Text( + memo.content, + maxLines: 4, + overflow: TextOverflow.ellipsis, + style: TextStyle(fontSize: 12), + ), + ), ), - Expanded( - flex: 1, - child: Image.asset("lib/assets/image/logo.png"), - ) + if (memo.title.isNotEmpty) // 이미지가 있다면 이미지 표시 + Expanded( + flex: 1, + child: Image.network(memo.title), // title 필드를 이미지 URL로 사용 + ), ], - ) + ); + }), ), ], ), @@ -157,16 +174,16 @@ class _HomeMainState extends State { Container( margin: EdgeInsets.fromLTRB(10, 10, 10, 10), decoration: BoxDecoration( - borderRadius: BorderRadius.circular(15), - color: Colors.white, - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.7), - spreadRadius: 0, - blurRadius: 8.0, - offset: Offset(0, 5), // changes position of shadow - ) - ] + borderRadius: BorderRadius.circular(15), + color: Colors.white, + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.7), + spreadRadius: 0, + blurRadius: 8.0, + offset: Offset(0, 5), + ) + ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -184,7 +201,7 @@ class _HomeMainState extends State { "lib/assets/icon/icon_plus2.svg", width: 17, height: 17, - ) + ), ], ), color: Colors.transparent, @@ -194,13 +211,11 @@ class _HomeMainState extends State { padding: EdgeInsets.fromLTRB(10, 10, 10, 20), child: Center( child: Text( - "등록된 TODO 리스트가 없습니다.", - style: TextStyle( - color: grey3, - ), + "등록된 TODO 리스트가 없습니다.", + style: TextStyle(color: grey3), ), ), - ) + ), ], ), ), @@ -208,101 +223,127 @@ class _HomeMainState extends State { Container( margin: EdgeInsets.fromLTRB(10, 10, 10, 10), decoration: BoxDecoration( - borderRadius: BorderRadius.circular(15), - color: Colors.white, - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.7), - spreadRadius: 0, - blurRadius: 8.0, - offset: Offset(0, 5), // changes position of shadow - ) - ] + borderRadius: BorderRadius.circular(15), + color: Colors.white, + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.7), + spreadRadius: 0, + blurRadius: 8.0, + offset: Offset(0, 5), + ) + ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( - color: Colors.transparent, - padding: EdgeInsets.fromLTRB(10, 10, 10, 5), - child: Column( - children: [ - IconAndText( - iconRoute: 'lib/assets/icon/icon_calender_outline.svg', - text: "오늘의 스케쥴", - height: 16, - ), - Padding(padding: EdgeInsets.only(top: 10)), - Container( - height: 500, - child: SfCalendar( - view: CalendarView.day, - headerHeight: 0, - viewHeaderHeight: 0, - backgroundColor: Colors.white, - showCurrentTimeIndicator: false, - viewNavigationMode: ViewNavigationMode.none, - timeSlotViewSettings: TimeSlotViewSettings( + color: Colors.transparent, + padding: EdgeInsets.fromLTRB(10, 10, 10, 5), + child: Column( + children: [ + IconAndText( + iconRoute: 'lib/assets/icon/icon_calender_outline.svg', + text: "오늘의 스케쥴", + height: 16, + ), + Padding(padding: EdgeInsets.only(top: 10)), + Container( + height: 500, + child: Obx(() { + final scheduleController = Get.find(); + if (scheduleController.schedules.isEmpty) { + return SfCalendar( + view: CalendarView.day, + headerHeight: 0, + viewHeaderHeight: 0, + backgroundColor: Colors.white, + showCurrentTimeIndicator: false, + viewNavigationMode: ViewNavigationMode.none, + timeSlotViewSettings: TimeSlotViewSettings( startHour: 5, endHour: 24, timeFormat: 'a HH:mm', - timeRulerSize: 70 - ), - ), - ), - ], - ) - + timeRulerSize: 70, + ), + ); + } + return Stack( + children: [ + SfCalendar( + view: CalendarView.day, + headerHeight: 0, + viewHeaderHeight: 0, + backgroundColor: Colors.white, + showCurrentTimeIndicator: false, + viewNavigationMode: ViewNavigationMode.none, + timeSlotViewSettings: TimeSlotViewSettings( + startHour: 5, + endHour: 24, + timeFormat: 'a HH:mm', + timeRulerSize: 70, + ), + ), + Positioned.fill( + child: CustomPaint( + painter: ScheduleBarPainter(scheduleController.schedules), + ), + ), + ], + ); + }), + ), + ], + ), ), ], ), ), // 광고 Container( - margin: EdgeInsets.fromLTRB(10, 10, 10, 10), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(15), - color: Colors.white, - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.7), - spreadRadius: 0, - blurRadius: 8.0, - offset: Offset(0, 5), // changes position of shadow - ) - ] - ), - child: Column( - children: [ - CarouselSlider( - items: adImages, - options: CarouselOptions( - viewportFraction: 1.0, - autoPlay: true, - autoPlayInterval: Duration(seconds: 5), - onPageChanged: (index, reason) { - setState(() { - imagePosition = index; - }); - }, - ), + margin: EdgeInsets.fromLTRB(10, 10, 10, 10), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(15), + color: Colors.white, + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.7), + spreadRadius: 0, + blurRadius: 8.0, + offset: Offset(0, 5), + ) + ], + ), + child: Column( + children: [ + CarouselSlider( + items: adImages, + options: CarouselOptions( + viewportFraction: 1.0, + autoPlay: true, + autoPlayInterval: Duration(seconds: 5), + onPageChanged: (index, reason) { + setState(() { + imagePosition = index; + }); + }, ), - AnimatedSmoothIndicator( - activeIndex: imagePosition, - count: adImages.length, - effect: JumpingDotEffect( - dotWidth: 5, - dotHeight: 5, - dotColor: grey3, - paintStyle: PaintingStyle.fill, - activeDotColor: mainOrange - ), + ), + AnimatedSmoothIndicator( + activeIndex: imagePosition, + count: adImages.length, + effect: JumpingDotEffect( + dotWidth: 5, + dotHeight: 5, + dotColor: grey3, + paintStyle: PaintingStyle.fill, + activeDotColor: mainOrange, ), - Padding(padding: EdgeInsets.only(top: 7)) - ], - ) + ), + Padding(padding: EdgeInsets.only(top: 7)), + ], + ), ), - ], ), ), @@ -318,27 +359,25 @@ Widget GroupListItem() { Get.toNamed('/groupInvite'); }, child: Container( - margin: EdgeInsets.symmetric(horizontal: 5), // 아이템 간의 간격을 설정 + margin: EdgeInsets.symmetric(horizontal: 5), child: Column( children: [ - // 그룹 프사 설정 Container( - width: 55, - height: 55, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20), - color: grey1, - border: Border.all(color: grey2,width: 0.5) - ), - margin: EdgeInsets.only(bottom: 5), - child: SvgPicture.asset( - "lib/assets/icon/icon_plus.svg", - width: 15, - height: 15, - fit: BoxFit.scaleDown, - ) + width: 55, + height: 55, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + color: grey1, + border: Border.all(color: grey2, width: 0.5), + ), + margin: EdgeInsets.only(bottom: 5), + child: SvgPicture.asset( + "lib/assets/icon/icon_plus.svg", + width: 15, + height: 15, + fit: BoxFit.scaleDown, + ), ), - // "그룹추가" 텍스트 출력 Text( "그룹 추가", style: TextStyle( @@ -351,3 +390,49 @@ Widget GroupListItem() { ), ); } + +class ScheduleDataSource extends CalendarDataSource { + ScheduleDataSource(List schedules) { + appointments = schedules.map((schedule) { + return Appointment( + startTime: schedule.startDt!, + endTime: schedule.endDt!, + subject: schedule.title ?? '', + color: Color(int.parse('0xff${schedule.color ?? 'ffffff'}')), + ); + }).toList(); + } +} + +class ScheduleBarPainter extends CustomPainter { + final List schedules; + + ScheduleBarPainter(this.schedules); + + @override + void paint(Canvas canvas, Size size) { + final paint = Paint() + ..style = PaintingStyle.fill; + + final double hourHeight = size.height / 19; // 5AM ~ 24PM + + for (var schedule in schedules) { + final startHour = schedule.startDt!.hour - 5; + final endHour = schedule.endDt!.hour - 5; + final top = startHour * hourHeight; + final height = (endHour - startHour) * hourHeight; + + paint.color = Color(int.parse('0xff${schedule.color ?? 'ffffff'}')); + + canvas.drawRect( + Rect.fromLTWH(0, top, size.width, height), + paint, + ); + } + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) { + return true; + } +} diff --git a/lib/ui/todo/view/todo_main_screen.dart b/lib/ui/todo/view/todo_main_screen.dart index f8ce278..7fe84a5 100644 --- a/lib/ui/todo/view/todo_main_screen.dart +++ b/lib/ui/todo/view/todo_main_screen.dart @@ -1,92 +1,205 @@ import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; import 'package:get/get.dart'; -import 'package:get/get_core/src/get_main.dart'; +import 'package:schedule_with/assets/colors/color.dart'; import 'package:schedule_with/ui/schedule/widget/calendar_cell_custom.dart'; -import 'package:schedule_with/ui/todo/widget/todo_add_bottom_sheet.dart'; -import '../../../assets/colors/color.dart'; +import '../widget/todo_controller.dart'; +import '../widget/select_share_option.dart'; +import '../widget/todo_add_bottom_sheet.dart'; +import '../widget/todo_edit_bottom_sheet.dart'; +import '../widget/todo_item_data.dart'; class TodoMainScreen extends StatefulWidget { const TodoMainScreen({super.key}); @override - State createState() => _TodoMainScreenState(); + _TodoMainScreenState createState() => _TodoMainScreenState(); } -class _TodoMainScreenState extends State { - final List _todoItems = [ - Todo(title: '할일1', description: '할일 설명1'), - Todo(title: '할일2', description: '할일 설명2'), - Todo(title: '할일3', description: '할일 설명3'), - Todo(title: '할일4', description: '할일 설명4'), +class _TodoMainScreenState extends State with SingleTickerProviderStateMixin { + late TabController _tabController; + DateTime? selectedDate; + bool isEditing = false; - ]; + final TextEditingController _dateController = TextEditingController(); + final TextEditingController _contentController = TextEditingController(); + final TodoController _todoController = Get.put(TodoController()); - void _addTodoItem(String title, String description) { - if (title.isNotEmpty && description.isNotEmpty) { - setState(() { - _todoItems.add(Todo( - title: title, - description: description, - )); - }); - } + @override + void initState() { + super.initState(); + _tabController = TabController(length: 3, vsync: this); + _tabController.index = 1; } - @override - Widget build(BuildContext context) { - return Scaffold( - body: SingleChildScrollView( - child: Column( - children: [ - // 화면에서 캘린더가 차지할 크기 (0.60이하 overflow) - SizedBox( - height: MediaQuery.of(context).size.height * 0.7, - // 달성률 캘린더 - child: CalendarCellCustom(), - ), - // 투두 리스트 넣을 부분 - _buildTodoList(), - ], + void dispose() { + _tabController.dispose(); + _dateController.dispose(); + _contentController.dispose(); + super.dispose(); + } + + void onDateSelected(DateTime date) { + setState(() { + selectedDate = date; + _dateController.text = DateFormat('yyyy-MM-dd').format(date); + }); + } + + void onTodoAdd() { + if (selectedDate != null) { + showModalBottomSheet( + context: context, + isScrollControlled: true, + builder: (context) => TodoAddBottomSheet( + dateController: _dateController, + contentController: _contentController, + onSave: () { + String dateString = DateFormat('yyyy-MM-dd').format(selectedDate!); + _todoController.addTodoItem(dateString, _contentController.text); + _contentController.clear(); + Navigator.of(context).pop(); + setState(() {}); // 상태를 새로고침하여 UI 업데이트 + }, ), - ), - // 플로팅 버튼 - floatingActionButton: FloatingActionButton( - onPressed: () { + ); + } + } + void onTodoEdit(TodoItemData todoItemData) { + showModalBottomSheet( + context: context, + isScrollControlled: true, + builder: (context) => TodoEditBottomSheet( + todoItemData: todoItemData, + onCancel: () { + Navigator.of(context).pop(); + }, + onSave: (oldDate, updatedTodoItem) { + setState(() { + _todoController.updateTodoItem(oldDate, updatedTodoItem); + }); + Navigator.of(context).pop(); // 수정 바텀 시트 닫기 + }, + onDelete: () { + setState(() { + _todoController.deleteTodoItem(todoItemData.date, todoItemData); + }); + Navigator.of(context).pop(); }, - backgroundColor: mainOrange, - child: Icon(Icons.add, color: Colors.white), - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(30)), ), ); } + void onDownload() { + if (selectedDate != null) { + String dateString = DateFormat('yyyy-MM-dd').format(selectedDate!); + List todosForSelectedDate = _todoController.getTodoItemsForDate(dateString); - - // - Widget _buildTodoList() { - return ListView.builder( - shrinkWrap: true, - itemBuilder: (_, index) { - return ListTile( - title: Text(_todoItems[index].title), - subtitle: Text(_todoItems[index].description), - ); - }, - itemCount: _todoItems.length, - ); + showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.transparent, + builder: (context) => SelectShareOption( + todosForSelectedDate: todosForSelectedDate, + completionRate: _todoController.calculateCompletionRate(dateString), + selectedDate: selectedDate!, + ), + ); + } } -} - -// Todo -class Todo { - final String title; - final String description; + @override + Widget build(BuildContext context) { + return Scaffold( + resizeToAvoidBottomInset: false, + body: Column( + children: [ + CalendarCellCustom(), + if (selectedDate != null) + Padding( + padding: const EdgeInsets.fromLTRB(16, 0, 16, 0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Text( + DateFormat('yyyy년 MM월 dd일').format(selectedDate!), + style: TextStyle(fontSize: 16), + ), + IconButton( + icon: Icon(Icons.download, color: grey3), + onPressed: onDownload, + ), + ], + ), + Row( + children: [ + Obx(() { + double completionRate = selectedDate != null + ? _todoController.calculateCompletionRate(DateFormat('yyyy-MM-dd').format(selectedDate!)) + : 0.0; + return Text( + ' ${(completionRate * 100).toStringAsFixed(0)}%', + style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: mainOrange), + ); + }), + ], + ), + ], + ), + ), + Expanded( + child: Obx(() { + List todosForSelectedDate = []; + if (selectedDate != null) { + String dateString = DateFormat('yyyy-MM-dd').format(selectedDate!); + todosForSelectedDate = _todoController.getTodoItemsForDate(dateString); + } - Todo({ - required this.title, - required this.description, - }); + return ListView.builder( + itemCount: todosForSelectedDate.length, + itemBuilder: (context, index) { + TodoItemData todoItem = todosForSelectedDate[index]; + return Padding( + padding: const EdgeInsets.symmetric(vertical: 2.0), // 상하 간격 조정 + child: ListTile( + contentPadding: EdgeInsets.only(left: 0, right: 16.0), // ListTile의 패딩 조정 + leading: Checkbox( + value: todoItem.isCompleted, + onChanged: (bool? value) { + setState(() { + todoItem.isCompleted = value ?? false; + _todoController.updateTodoItem(todoItem.date, todoItem); + }); + }, + activeColor: mainOrange, // 체크박스 색상 + side: BorderSide(color: mainOrange), // 체크박스 테두리 색상 + ), + title: Text( + todoItem.content, + style: TextStyle(fontSize: 14), // 텍스트 크기 조정 + ), + onTap: () { + onTodoEdit(todoItem); // TODO 편집 기능 추가 + }, + ), + ); + }, + ); + }), + ), + ], + ), + floatingActionButton: FloatingActionButton( + onPressed: onTodoAdd, + backgroundColor: mainOrange, + shape: CircleBorder(), + child: const Icon(Icons.add, color: Colors.white), + ), + floatingActionButtonLocation: FloatingActionButtonLocation.endFloat, + ); + } } diff --git a/lib/ui/todo/widget/calendar_cell_custom.dart b/lib/ui/todo/widget/calendar_cell_custom.dart new file mode 100644 index 0000000..c7e35e5 --- /dev/null +++ b/lib/ui/todo/widget/calendar_cell_custom.dart @@ -0,0 +1,189 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:schedule_with/assets/colors/color.dart'; +import 'package:schedule_with/ui/group/widget/my_todo_indicator.dart'; +import 'package:schedule_with/ui/schedule/widget/schedule_edit_bottom_sheet.dart'; +import 'package:syncfusion_flutter_calendar/calendar.dart'; + +// GetX 컨트롤러 +class CalendarController extends GetxController { + var currentMonth = DateTime.now().month.obs; + var selectedDate = DateTime.now().obs; + + void updateMonth(int month) { + currentMonth.value = month; + } + + void updateSelectedDate(DateTime date) { + selectedDate.value = date; + } +} + +class CalendarCellCustom extends StatefulWidget { + CalendarCellCustom({super.key}); + + @override + State createState() => _CalendarCellCustomState(); +} + +class _CalendarCellCustomState extends State { + // GetX Controller 초기화 + final CalendarController calendarControllerGetX = + Get.put(CalendarController()); + + // 현재 표시되는 날짜 (보이는 날짜의 중간) + void _onViewChanged(ViewChangedDetails details) { + DateTime displayDate = + details.visibleDates[details.visibleDates.length ~/ 2]; + calendarControllerGetX.updateMonth(displayDate.month); + } + + // 선택된 셀의 날짜 확인 + void _onCalendarTap(CalendarTapDetails details) { + // 약속이 있으면 스케줄 수정 바텀 시트를 띄운다 + var todayAppoint = details.appointments?.isNotEmpty ?? false; + print(details.targetElement); + + if (details.targetElement.name == CalendarElement.calendarCell) { + Get.back(); + } + + if (todayAppoint == true) { + showModalBottomSheet( + // 바텀 시트 높이 지정하려면 isScrollControlled: true, + isScrollControlled: true, + context: context, + builder: (BuildContext context) { + return EditScheduleBottomSheet(); + }); + } else { + // 약속이 없을 때의 동작 추가 (필요한 경우) + } + } + + @override + Widget build(BuildContext context) { + return SizedBox( + child: SfCalendar( + // 캘린더 스타일 설정 + onViewChanged: _onViewChanged, + onTap: _onCalendarTap, + // controller: _calendarController, + view: CalendarView.month, + viewHeaderHeight: -1, + cellBorderColor: grey1, + backgroundColor: Colors.transparent, + todayHighlightColor: mainOrange, + todayTextStyle: TextStyle(color: Colors.white), + headerDateFormat: 'yyyy년 MM월', + showDatePickerButton: true, + // showNavigationArrow: true, + // allowViewNavigation: true, + // allowedViews: [ + // CalendarView.month, + // // CalendarView.timelineMonth, + // ], + monthViewSettings: MonthViewSettings( + // 캘린더에 몇 주 표시할지 + numberOfWeeksInView: 2 + // showTrailingAndLeadingDates: false + ), + timeSlotViewSettings: TimeSlotViewSettings(), + showTodayButton: true, + headerHeight: 50, + headerStyle: const CalendarHeaderStyle( + backgroundColor: Colors.white, + textStyle: TextStyle( + fontSize: 14, + color: Colors.black, + ), + ), + // 특정 날짜 선택시 셀 스타일 설정 + selectionDecoration: BoxDecoration( + color: Colors.transparent, + border: Border.all( + color: mainOrange, + width: 2, + ), + ), + // 커스텀 셀 + monthCellBuilder: + (BuildContext buildContext, MonthCellDetails details) { + return Obx(() { + return CustomMonthCell( + details: details, + currentMonth: calendarControllerGetX.currentMonth.value, + selectedDate: calendarControllerGetX.selectedDate.value, + ); + }); + }, + ), + ); + } +} + + + +// 캘린더 셀 내용에 달성률 indicator 넣기 위해 커스터마이징 +class CustomMonthCell extends StatelessWidget { + final MonthCellDetails details; + final int currentMonth; + final DateTime selectedDate; + + CustomMonthCell({ + super.key, + required this.details, + required this.currentMonth, + required this.selectedDate, + }); + + @override + Widget build(BuildContext context) { + bool isCurrentMonthCell = (details.date.month == currentMonth); + bool isSelectedDate = details.date == selectedDate; + bool isCurrentMonthSelected = selectedDate.month == currentMonth; + + return + Column(children: [ + Container( + height: 70, + // 셀 테두리 색상 설정 + decoration: BoxDecoration( + border: Border.all( + color: isSelectedDate && !isCurrentMonthSelected + ? Colors.transparent + : grey2, + width: 0.6, + ), + ), + child: SizedBox( + // 달력 셀 높이 + height: 10, + child: Column( + children: [ + // 날짜 텍스트 위 여백 + SizedBox(height: 5), + Text( + details.date.day.toString(), + style: TextStyle( + color: isCurrentMonthCell ? Colors.black87 : grey4, + ), + ), + SizedBox(height: 4), + // 셀 내부에 들어갈 내용 + Container( + height: 30, + alignment: Alignment.center, + // 현재 달만 indicator 보이도록 함 + child: isCurrentMonthCell + ? MyTodoIndicator(totalTodo: 10, doneTodo: 8) + : SizedBox(), + ) + ], + )), + ) + ]); + } +} + + diff --git a/lib/ui/todo/widget/calendar_tab.dart b/lib/ui/todo/widget/calendar_tab.dart deleted file mode 100644 index da57799..0000000 --- a/lib/ui/todo/widget/calendar_tab.dart +++ /dev/null @@ -1,15 +0,0 @@ -import 'package:flutter/material.dart'; - -class CalendarTab extends StatelessWidget { - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text('캘린더'), - ), - body: Center( - child: Text('캘린더 화면'), - ), - ); - } -} diff --git a/lib/ui/todo/widget/calendar_widget.dart b/lib/ui/todo/widget/calendar_widget.dart deleted file mode 100644 index dca0e33..0000000 --- a/lib/ui/todo/widget/calendar_widget.dart +++ /dev/null @@ -1,81 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:schedule_with/assets/colors/color.dart'; -import 'package:syncfusion_flutter_calendar/calendar.dart'; - -class CalendarWidget extends StatefulWidget { - final Function(DateTime) onDateSelected; - - const CalendarWidget({super.key, required this.onDateSelected}); - - @override - State createState() => _CalendarWidgetState(); -} - -class _CalendarWidgetState extends State { - // 현재 달력 범위를 벗어나는 날짜 클릭시 테두리 색을 다르게 설정하기 위한 변수 - bool isCurrentMonth = true; - // 캘린더 컨트롤러 - CalendarController _calendarController = CalendarController(); - - // 선택된 셀의 날짜 확인 - void _onCalendarTap(CalendarTapDetails details) { - // 선택한 요소의 인덱스 (0 = 헤더 , 1 = 요일 , 2 = 셀) - var onTapElement = details.targetElement.index; - // 선택된 날짜 - var selectedDate = details.date; - // 달력상 날짜 - var calendarMonth = _calendarController.displayDate?.month; - if (onTapElement == 2) { - // UI 변경 - setState(() { - if (selectedDate?.month == calendarMonth) { - isCurrentMonth = true; - } else { - isCurrentMonth = false; - } - widget.onDateSelected(selectedDate!); - }); - } - } - - @override - Widget build(BuildContext context) { - return SizedBox( - height: 400, - child: SfCalendar( - view: CalendarView.month, - controller: _calendarController, - onTap: _onCalendarTap, - // 임시 데이터 - dataSource: getDataSource(), - allowedViews: const [ - CalendarView.month, - CalendarView.week, - ], - backgroundColor: Colors.transparent, - todayHighlightColor: mainBrown, - todayTextStyle: TextStyle(color: Colors.white), - showDatePickerButton: true, - showTodayButton: true, - cellBorderColor: grey3, - headerDateFormat: 'yyyy년 MM월', - headerHeight: 50, - headerStyle: const CalendarHeaderStyle( - backgroundColor: Colors.white, - textStyle: TextStyle( - fontSize: 14, - color: Colors.black, - ), - ), - selectionDecoration: BoxDecoration( - color: Colors.transparent, - border: Border.all(color: isCurrentMonth ? mainOrange : Colors.transparent, width: 2), - ), - monthViewSettings: MonthViewSettings(numberOfWeeksInView: 6), - timeSlotViewSettings: TimeSlotViewSettings(timeInterval: Duration(hours: 2)), - ), - ); - } - - getDataSource() {} -} diff --git a/lib/ui/todo/widget/main_tab_bar.dart b/lib/ui/todo/widget/main_tab_bar.dart index 7dd831f..2b0066b 100644 --- a/lib/ui/todo/widget/main_tab_bar.dart +++ b/lib/ui/todo/widget/main_tab_bar.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:schedule_with/assets/colors/color.dart'; import 'package:schedule_with/ui/todo/view/todo_main_screen.dart'; -import 'calendar_tab.dart'; class MainTabBar extends StatefulWidget { final TabController tabController; diff --git a/lib/ui/todo/widget/select_share_option.dart b/lib/ui/todo/widget/select_share_option.dart index 1948bbc..ca4da70 100644 --- a/lib/ui/todo/widget/select_share_option.dart +++ b/lib/ui/todo/widget/select_share_option.dart @@ -1,10 +1,9 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:schedule_with/assets/colors/color.dart'; -import 'todo_controller.dart'; +import 'package:schedule_with/ui/todo/widget/todo_item_data.dart'; import 'todo_share_with_list.dart'; import 'todo_share_without_list.dart'; -import '../view/todo_main_screen.dart'; class SelectShareOption extends StatelessWidget { final List todosForSelectedDate; diff --git a/lib/ui/todo/widget/todo_controller.dart b/lib/ui/todo/widget/todo_controller.dart index a351fae..832ac14 100644 --- a/lib/ui/todo/widget/todo_controller.dart +++ b/lib/ui/todo/widget/todo_controller.dart @@ -1,45 +1,45 @@ import 'package:get/get.dart'; +import 'package:schedule_with/ui/todo/widget/todo_item_data.dart'; class TodoController extends GetxController { - var todoItems = [].obs; - - List getTodoItemsForDate(String date) { - return todoItems.where((item) => item.date == date).toList(); - } + var selectedDate = DateTime.now().obs; + var todoList = [].obs; void addTodoItem(String date, String content) { - todoItems.add(TodoItemData(date: date, content: content, isCompleted: false)); + final newItem = TodoItemData(date: date, content: content); + todoList.add(newItem); + print('Todo item added: $date - $content'); } - void updateTodoItem(String date, TodoItemData updatedItem) { - int index = todoItems.indexWhere((item) => item.date == date && item.content == updatedItem.content); + void updateTodoItem(String oldDate, TodoItemData updatedItem) { + int index = todoList.indexWhere((item) => item.date == oldDate && item.content == updatedItem.content); if (index != -1) { - todoItems[index] = updatedItem; + todoList[index] = updatedItem; + } else { + todoList.removeWhere((item) => item.date == oldDate && item.content == updatedItem.content); + addTodoItem(updatedItem.date, updatedItem.content); } + print('Todo item updated: ${updatedItem.date} - ${updatedItem.content}'); } void deleteTodoItem(String date, TodoItemData itemToDelete) { - todoItems.removeWhere((item) => item.date == date && item.content == itemToDelete.content); + todoList.removeWhere((item) => item.date == date && item.content == itemToDelete.content); + print('Todo item deleted: $date - ${itemToDelete.content}'); } - double calculateCompletionRate(String date) { - List itemsForDate = getTodoItemsForDate(date); - if (itemsForDate.isEmpty) { - return 0.0; - } - int completedCount = itemsForDate.where((item) => item.isCompleted).length; - return completedCount / itemsForDate.length; + List getTodoItemsForDate(String date) { + return todoList.where((item) => item.date == date).toList(); } -} -class TodoItemData { - final String date; - String content; - bool isCompleted; + void updateSelectedDate(DateTime date) { + selectedDate.value = date; + print('Selected date updated: $date'); + } - TodoItemData({ - required this.date, - required this.content, - this.isCompleted = false, - }); + double calculateCompletionRate(String date) { + final todosForDate = getTodoItemsForDate(date); + if (todosForDate.isEmpty) return 0.0; + final completedCount = todosForDate.where((item) => item.isCompleted).length; + return completedCount / todosForDate.length; + } } diff --git a/lib/ui/todo/widget/todo_edit_bottom_sheet.dart b/lib/ui/todo/widget/todo_edit_bottom_sheet.dart index e85e772..2b9b946 100644 --- a/lib/ui/todo/widget/todo_edit_bottom_sheet.dart +++ b/lib/ui/todo/widget/todo_edit_bottom_sheet.dart @@ -1,16 +1,16 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:intl/intl.dart'; import 'package:schedule_with/assets/colors/color.dart'; -import 'package:schedule_with/ui/todo/widget/todo_cancel_bottom_sheet.dart'; -import '../../../widget/date_picker.dart'; -import '../../../widget/main_two_button.dart'; -import 'todo_controller.dart'; -import '../view/todo_main_screen.dart'; +import 'package:schedule_with/ui/todo/widget/todo_controller.dart'; +import 'package:schedule_with/ui/todo/widget/todo_item_data.dart'; +import 'select_date.dart'; class TodoEditBottomSheet extends StatefulWidget { final TodoItemData todoItemData; final VoidCallback onCancel; - final Function(TodoItemData) onSave; + final Function(String, TodoItemData) onSave; final VoidCallback onDelete; const TodoEditBottomSheet({ @@ -26,6 +26,7 @@ class TodoEditBottomSheet extends StatefulWidget { } class _TodoEditBottomSheetState extends State { + final TodoController _todoController = Get.find(); late TextEditingController _contentController; String selectedDate = ''; @@ -34,6 +35,7 @@ class _TodoEditBottomSheetState extends State { super.initState(); _contentController = TextEditingController(text: widget.todoItemData.content); selectedDate = widget.todoItemData.date; + print('Edit BottomSheet initialized with date: $selectedDate and content: ${_contentController.text}'); } @override @@ -42,29 +44,8 @@ class _TodoEditBottomSheetState extends State { super.dispose(); } - // 취소 확인 바텀 시트를 보여주는 함수 - void _showCancelBottomSheet(BuildContext context) { - showModalBottomSheet( - context: context, - isScrollControlled: true, - builder: (context) => TodoCancelBottomSheet( - onConfirm: () { - Navigator.of(context).pop(); // 확인 바텀 시트 닫기 - Navigator.of(context).pop(); // 수정 바텀 시트 닫기 - widget.onCancel(); // 추가적인 취소 로직 실행 - }, - onContinue: () { - Navigator.of(context).pop(); // 확인 바텀 시트 닫기 - }, - ), - ); - } - - // 날짜 선택 바텀 시트를 열고 날짜를 선택하는 함수 Future _openSelectDateBottomSheet(BuildContext context) async { - Navigator.of(context).pop(); // 현재 바텀 시트 닫기 - - final DateTime? pickedDate = await showModalBottomSheet( + await showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: Colors.transparent, @@ -72,52 +53,43 @@ class _TodoEditBottomSheetState extends State { return DraggableScrollableSheet( expand: false, builder: (BuildContext context, ScrollController scrollController) { - return Container( - padding: const EdgeInsets.all(16.0), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.only( - topLeft: Radius.circular(20), - topRight: Radius.circular(20), - ), - ), - child: DatePicker( - back_page: this.widget, - title: '날짜', - ), + return SelectDate( + onDateSelected: (dateTime) { + setState(() { + selectedDate = DateFormat('yyyy년 MM월 dd일').format(dateTime); + print('Date selected: $selectedDate'); + }); + Navigator.of(context).pop(); // Close the date picker + + // Reopen the bottom sheet with updated date + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) { + _showEditBottomSheet(context); + } + }); + }, ); }, ); }, ); - - if (pickedDate != null) { - setState(() { - selectedDate = '${pickedDate.year}-${pickedDate.month}-${pickedDate.day}'; - }); - - WidgetsBinding.instance.addPostFrameCallback((_) { - if (mounted) { - _showEditBottomSheet(context); // 업데이트된 날짜로 수정 바텀 시트를 다시 열기 - } - }); - } } - // 수정 바텀 시트를 다시 여는 함수 void _showEditBottomSheet(BuildContext context) { showModalBottomSheet( context: context, isScrollControlled: true, - builder: (context) => TodoEditBottomSheet( - todoItemData: TodoItemData( - date: selectedDate, - content: _contentController.text, - ), - onCancel: widget.onCancel, - onSave: widget.onSave, - onDelete: widget.onDelete, - ), + builder: (BuildContext context) { + return TodoEditBottomSheet( + todoItemData: TodoItemData( + date: selectedDate, + content: _contentController.text, + ), + onCancel: widget.onCancel, + onSave: widget.onSave, + onDelete: widget.onDelete, + ); + }, ); } @@ -130,7 +102,7 @@ class _TodoEditBottomSheetState extends State { child: SingleChildScrollView( padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom), child: Container( - height: MediaQuery.of(context).size.height * 0.5, // 화면 높이의 50%로 설정 + height: MediaQuery.of(context).size.height * 0.5, decoration: const BoxDecoration( color: Colors.white, borderRadius: BorderRadius.only( @@ -140,119 +112,119 @@ class _TodoEditBottomSheetState extends State { ), child: Column( children: [ - // 정렬된 요소가 있는 커스텀 BottomSheetTitle 위젯 - BottomSheetTitle(title: ' TODO 수정', closeIcon: true), + BottomSheetTitle(), Expanded( child: Padding( padding: const EdgeInsets.fromLTRB(16.0, 0.0, 16.0, 16.0), child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - GestureDetector( - onTap: () { - _openSelectDateBottomSheet(context); - }, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Text( - '날짜', - style: TextStyle(fontSize: 16), - ), - Text( - selectedDate, - style: const TextStyle(fontSize: 16), - ), - ], - ), - ), - const SizedBox(height: 8.0), - const Divider(), - Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + GestureDetector( + onTap: () { + _openSelectDateBottomSheet(context); + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( - '내용', + '날짜', style: TextStyle(fontSize: 16), ), - Expanded( - child: Align( - alignment: Alignment.centerRight, - child: TextField( - controller: _contentController, - decoration: const InputDecoration( - hintText: '내용을 입력하세요', - border: InputBorder.none, - ), - textAlign: TextAlign.right, - ), - ), + Text( + selectedDate, + style: const TextStyle(fontSize: 16), ), ], ), - Spacer(), - Padding( - padding: const EdgeInsets.symmetric(vertical: 10), - child: Row( - children: [ - Expanded( - child: ElevatedButton( - onPressed: widget.onDelete, - style: ElevatedButton.styleFrom( - backgroundColor: mainBrown, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - ), - padding: const EdgeInsets.symmetric(vertical: 16), + ), + const SizedBox(height: 8.0), + const Divider(), + Row( + children: [ + const Text( + '내용', + style: TextStyle(fontSize: 16), + ), + Expanded( + child: Align( + alignment: Alignment.centerRight, + child: TextField( + controller: _contentController, + decoration: const InputDecoration( + hintText: '내용을 입력하세요', + border: InputBorder.none, + ), + textAlign: TextAlign.right, + ), + ), + ), + ], + ), + Spacer(), + Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: Row( + children: [ + Expanded( + child: ElevatedButton( + onPressed: widget.onDelete, + style: ElevatedButton.styleFrom( + backgroundColor: mainBrown, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), ), - child: const Center( - child: Text( - '삭제하기', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: CupertinoColors.white, - ), + padding: const EdgeInsets.symmetric(vertical: 16), + ), + child: const Center( + child: Text( + '삭제하기', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: CupertinoColors.white, ), ), ), ), - SizedBox(width: 10), // 버튼 사이 간격 추가 - Expanded( - child: ElevatedButton( - onPressed: () { - if (selectedDate.isNotEmpty && _contentController.text.isNotEmpty) { - widget.onSave(TodoItemData( - date: selectedDate, - content: _contentController.text, - )); - if (Navigator.canPop(context)) { - Navigator.of(context).pop(); // 수정 바텀 시트 닫기 - } - } - }, - style: ElevatedButton.styleFrom( - backgroundColor: mainOrange, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - ), - padding: const EdgeInsets.symmetric(vertical: 16), + ), + SizedBox(width: 10), + Expanded( + child: ElevatedButton( + onPressed: () { + if (selectedDate.isNotEmpty && _contentController.text.isNotEmpty) { + print('Save button pressed with date: $selectedDate and content: ${_contentController.text}'); + final updatedItem = TodoItemData( + date: selectedDate, + content: _contentController.text, + ); + widget.onSave(widget.todoItemData.date, updatedItem); + Navigator.of(context).pop(); + } + }, + style: ElevatedButton.styleFrom( + backgroundColor: mainOrange, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), ), - child: const Center( - child: Text( - '수정 완료', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: CupertinoColors.white, - ), + padding: const EdgeInsets.symmetric(vertical: 16), + ), + child: const Center( + child: Text( + '수정 완료', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: CupertinoColors.white, ), ), ), ), - ], - ), + ), + ], ), - ]), + ), + ], + ), ), ), ], @@ -263,40 +235,27 @@ class _TodoEditBottomSheetState extends State { } } -// 바텀 시트 제목 설정 class BottomSheetTitle extends StatelessWidget { - final String title; - final bool closeIcon; - - const BottomSheetTitle({ - Key? key, - required this.title, - this.closeIcon = false, - }) : super(key: key); - @override Widget build(BuildContext context) { return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Expanded( - child: Padding( - padding: EdgeInsets.only(top: 10), - child: Text( - title, - textAlign: TextAlign.center, - style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), - ), + SizedBox(width: 50), + Container( + alignment: Alignment.center, + height: 45, + child: Text( + "TODO 수정", + style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), ), - if (closeIcon) - IconButton( - padding: EdgeInsets.only(top: 10), - onPressed: () { - Navigator.of(context).pop(); - }, - icon: Icon(Icons.close), - alignment: Alignment.topRight, - ), + IconButton( + onPressed: () { + Get.back(); + }, + icon: Icon(Icons.close), + ), ], ); } diff --git a/lib/ui/todo/widget/todo_item.dart b/lib/ui/todo/widget/todo_item.dart index 3211a70..62c752b 100644 --- a/lib/ui/todo/widget/todo_item.dart +++ b/lib/ui/todo/widget/todo_item.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'todo_controller.dart'; +import 'package:schedule_with/ui/todo/widget/todo_item_data.dart'; class TodoItem extends StatelessWidget { final TodoItemData todoItemData; diff --git a/lib/ui/todo/widget/todo_item_data.dart b/lib/ui/todo/widget/todo_item_data.dart new file mode 100644 index 0000000..350e0dd --- /dev/null +++ b/lib/ui/todo/widget/todo_item_data.dart @@ -0,0 +1,11 @@ +class TodoItemData { + final String date; + String content; + bool isCompleted; + + TodoItemData({ + required this.date, + required this.content, + this.isCompleted = false, + }); +} diff --git a/lib/ui/todo/widget/todo_list_tab.dart b/lib/ui/todo/widget/todo_list_tab.dart deleted file mode 100644 index 002b863..0000000 --- a/lib/ui/todo/widget/todo_list_tab.dart +++ /dev/null @@ -1,53 +0,0 @@ -import 'package:flutter/material.dart'; -import 'todo_controller.dart'; -import '../widget/calendar_widget.dart'; -import 'todo_item.dart'; - -class TodoListTab extends StatelessWidget { - final List todoItems; - final void Function(TodoItemData) onTodoEdit; - final String selectedDate; - final ValueChanged onDateSelected; - final void Function(TodoItemData, bool?) onCheckboxChanged; - - const TodoListTab({ - super.key, - required this.todoItems, - required this.onTodoEdit, - required this.selectedDate, - required this.onDateSelected, - required this.onCheckboxChanged, - }); - - @override - Widget build(BuildContext context) { - return Column( - children: [ - Expanded( - flex: 3, - child: CalendarWidget(onDateSelected: onDateSelected), // 캘린더 위젯 추가 - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: Text('$selectedDate', style: const TextStyle(fontSize: 16)), - ), - Expanded( - flex: 2, - child: ListView.builder( - itemCount: todoItems.length, - itemBuilder: (context, index) { - final todoItem = todoItems[index]; - return TodoItem( - todoItemData: todoItem, - onCheckboxChanged: (bool? value) { - onCheckboxChanged(todoItem, value); - }, - onEdit: () => onTodoEdit(todoItem), - ); - }, - ), - ), - ], - ); - } -} diff --git a/lib/ui/todo/widget/todo_page_share.dart b/lib/ui/todo/widget/todo_page_share.dart index a8e5f04..0427df7 100644 --- a/lib/ui/todo/widget/todo_page_share.dart +++ b/lib/ui/todo/widget/todo_page_share.dart @@ -1,8 +1,7 @@ import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; +import 'package:schedule_with/ui/todo/widget/todo_item_data.dart'; import '../../../assets/colors/color.dart'; -import 'todo_controller.dart'; -import '../view/todo_main_screen.dart'; class TodoSharePage extends StatelessWidget { final List todosForSelectedDate; diff --git a/lib/ui/todo/widget/todo_share_with_list.dart b/lib/ui/todo/widget/todo_share_with_list.dart index 8e3ad7b..a226a61 100644 --- a/lib/ui/todo/widget/todo_share_with_list.dart +++ b/lib/ui/todo/widget/todo_share_with_list.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:schedule_with/assets/colors/color.dart'; +import 'package:schedule_with/ui/todo/widget/todo_item_data.dart'; import 'todo_controller.dart'; import '../view/todo_main_screen.dart'; From 478e50ca2cf3f368d62dcc5419268670f98b70bc Mon Sep 17 00:00:00 2001 From: jay Date: Tue, 11 Jun 2024 10:29:11 +0900 Subject: [PATCH 2/3] =?UTF-8?q?=E2=9C=A8=20HOME=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=9D=BC=EB=B6=80=20=EA=B5=AC=ED=98=84=20db=EB=AA=85=EC=84=B8?= =?UTF-8?q?=EC=84=9C=EC=97=90=20=EB=94=B0=EB=9D=BC=20tbl,usecase,repositor?= =?UTF-8?q?y,controller=20=EC=9E=91=EC=84=B1.=20memo,=20schedule=20firebas?= =?UTF-8?q?e=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EB=B6=88=EB=9F=AC=EC=98=A4?= =?UTF-8?q?=EA=B8=B0=20=EB=AF=B8=EC=99=84=EC=84=B1.=20todo=20=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=84=B0=20=EB=B6=88=EB=9F=AC=EC=98=A4=EA=B8=B0=20?= =?UTF-8?q?=EC=99=84=EC=84=B1.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/domain/repository/todo_repository.dart | 23 +++ lib/domain/use_case/todo_use_case.dart | 16 +++ lib/entity/memo_tbl.dart | 6 +- lib/entity/todo_tbl.dart | 39 +++++ .../home/controller/home_todo_controller.dart | 23 +++ lib/ui/home/view/home_main.dart | 136 ++++++++++-------- 6 files changed, 179 insertions(+), 64 deletions(-) create mode 100644 lib/domain/repository/todo_repository.dart create mode 100644 lib/domain/use_case/todo_use_case.dart create mode 100644 lib/entity/todo_tbl.dart create mode 100644 lib/ui/home/controller/home_todo_controller.dart diff --git a/lib/domain/repository/todo_repository.dart b/lib/domain/repository/todo_repository.dart new file mode 100644 index 0000000..8e78bb1 --- /dev/null +++ b/lib/domain/repository/todo_repository.dart @@ -0,0 +1,23 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:schedule_with/entity/todo_tbl.dart'; + +class TodoRepository { + final FirebaseFirestore _db = FirebaseFirestore.instance; + + Stream> getTodos() { + return _db.collection('todo') + .snapshots() + .map((snapshot) => snapshot.docs.map((doc) => Todo.fromFirestore(doc.data() as Map)).toList()); + } + + Future updateTodoStatus(int idx, bool isCompleted) { + return _db.collection('todo') + .where('idx', isEqualTo: idx) + .get() + .then((snapshot) { + for (var doc in snapshot.docs) { + doc.reference.update({'todo_status': isCompleted ? 'Y' : 'N'}); + } + }); + } +} diff --git a/lib/domain/use_case/todo_use_case.dart b/lib/domain/use_case/todo_use_case.dart new file mode 100644 index 0000000..e36fd64 --- /dev/null +++ b/lib/domain/use_case/todo_use_case.dart @@ -0,0 +1,16 @@ +import 'package:schedule_with/domain/repository/todo_repository.dart'; +import 'package:schedule_with/entity/todo_tbl.dart'; + +class TodoUseCase { + final TodoRepository repository; + + TodoUseCase(this.repository); + + Stream> getTodos() { + return repository.getTodos(); + } + + Future updateTodoStatus(int idx, bool isCompleted) { + return repository.updateTodoStatus(idx, isCompleted); + } +} diff --git a/lib/entity/memo_tbl.dart b/lib/entity/memo_tbl.dart index 99d3089..d706668 100644 --- a/lib/entity/memo_tbl.dart +++ b/lib/entity/memo_tbl.dart @@ -9,7 +9,7 @@ class Memo { final bool publicStatus; final String status; final DateTime regDt; - final DateTime modDt; + final DateTime? modDt; // Nullable Memo({ required this.idx, @@ -20,7 +20,7 @@ class Memo { required this.publicStatus, required this.status, required this.regDt, - required this.modDt, + this.modDt, }); factory Memo.fromFirestore(Map data) { @@ -33,7 +33,7 @@ class Memo { publicStatus: data['public_status'], status: data['status'], regDt: (data['reg_dt'] as Timestamp).toDate(), - modDt: (data['mod_dt'] as Timestamp).toDate(), + modDt: data['mod_dt'] != null ? (data['mod_dt'] as Timestamp).toDate() : null, ); } } diff --git a/lib/entity/todo_tbl.dart b/lib/entity/todo_tbl.dart new file mode 100644 index 0000000..5f60147 --- /dev/null +++ b/lib/entity/todo_tbl.dart @@ -0,0 +1,39 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; + +class Todo { + final int idx; + final int userIdx; + final int? groupIdx; + final DateTime todoDt; + final String title; + final String todoStatus; + final String status; + final DateTime regDt; + final DateTime? modDt; + + Todo({ + required this.idx, + required this.userIdx, + this.groupIdx, + required this.todoDt, + required this.title, + required this.todoStatus, + required this.status, + required this.regDt, + this.modDt, + }); + + factory Todo.fromFirestore(Map data) { + return Todo( + idx: data['idx'], + userIdx: data['user_idx'], + groupIdx: data['group_idx'], + todoDt: (data['todo_dt'] as Timestamp).toDate(), + title: data['title'], + todoStatus: data['todo_status'], + status: data['status'], + regDt: (data['reg_dt'] as Timestamp).toDate(), + modDt: data['mod_dt'] != null ? (data['mod_dt'] as Timestamp).toDate() : null, + ); + } +} diff --git a/lib/ui/home/controller/home_todo_controller.dart b/lib/ui/home/controller/home_todo_controller.dart new file mode 100644 index 0000000..e0850f8 --- /dev/null +++ b/lib/ui/home/controller/home_todo_controller.dart @@ -0,0 +1,23 @@ +import 'package:get/get.dart'; +import 'package:schedule_with/entity/todo_tbl.dart'; +import '../../../domain/use_case/todo_use_case.dart'; + +class HomeTodoController extends GetxController { + final TodoUseCase todoUseCase; + var todos = [].obs; + + HomeTodoController({required this.todoUseCase}); + + @override + void onInit() { + super.onInit(); + todoUseCase.getTodos().listen((data) { + print('Firestore data: $data'); // Firestore에서 가져온 데이터를 출력하여 확인 + todos.value = data; + }); + } + + void toggleTodoStatus(int idx, bool isCompleted) { + todoUseCase.updateTodoStatus(idx, isCompleted); + } +} diff --git a/lib/ui/home/view/home_main.dart b/lib/ui/home/view/home_main.dart index a72b1bb..8531bf3 100644 --- a/lib/ui/home/view/home_main.dart +++ b/lib/ui/home/view/home_main.dart @@ -10,11 +10,14 @@ import 'package:smooth_page_indicator/smooth_page_indicator.dart'; import 'package:syncfusion_flutter_calendar/calendar.dart'; import '../../../domain/repository/memo_repository.dart'; import '../../../domain/repository/schedule_repository.dart'; +import '../../../domain/repository/todo_repository.dart'; import '../../../domain/use_case/memo_use_case.dart'; import '../../../domain/use_case/schedule_use_case.dart'; +import '../../../domain/use_case/todo_use_case.dart'; import '../../../entity/schedule_tbl.dart'; import '../controller/home_memo_controller.dart'; import '../controller/home_schedule_controller.dart'; +import '../controller/home_todo_controller.dart'; class HomeMain extends StatefulWidget { const HomeMain({super.key}); @@ -44,6 +47,10 @@ class _HomeMainState extends State { final memoRepository = MemoRepository(); final memoUseCase = MemoUseCase(memoRepository); Get.put(HomeMemoController(memoUseCase: memoUseCase)); + + final todoRepository = TodoRepository(); + final todoUseCase = TodoUseCase(todoRepository); + Get.put(HomeTodoController(todoUseCase: todoUseCase)); } @override @@ -208,13 +215,41 @@ class _HomeMainState extends State { padding: EdgeInsets.fromLTRB(10, 10, 10, 5), ), Container( - padding: EdgeInsets.fromLTRB(10, 10, 10, 20), - child: Center( - child: Text( - "등록된 TODO 리스트가 없습니다.", - style: TextStyle(color: grey3), - ), - ), + margin: EdgeInsets.fromLTRB(15, 0, 15, 15), + child: Obx(() { + final todoController = Get.find(); + if (todoController.todos.isEmpty) { + return Text( + "등록된 TODO 리스트가 없습니다.", + style: TextStyle(color: grey3), + ); + } + return Column( + children: todoController.todos.map((todo) { + return Padding( + padding: const EdgeInsets.only(left: 0, right: 16.0), // 패딩을 통해 제목을 왼쪽으로 이동 + child: Row( + children: [ + Checkbox( + value: todo.todoStatus == 'Y', + onChanged: (bool? value) { + if (value != null) { + todoController.toggleTodoStatus(todo.idx, value); + } + }, + activeColor: Color(0xFF627BFF), // 체크박스 테두리 색상 설정 + checkColor: Colors.white, // 체크표시 색상 설정 + side: BorderSide(color: Color(0xFF627BFF)), // 체크박스 테두리 색상 설정 + ), + Expanded( + child: Text(todo.title), + ), + ], + ), + ); + }).toList(), + ); + }), ), ], ), @@ -268,28 +303,41 @@ class _HomeMainState extends State { ), ); } - return Stack( - children: [ - SfCalendar( - view: CalendarView.day, - headerHeight: 0, - viewHeaderHeight: 0, - backgroundColor: Colors.white, - showCurrentTimeIndicator: false, - viewNavigationMode: ViewNavigationMode.none, - timeSlotViewSettings: TimeSlotViewSettings( - startHour: 5, - endHour: 24, - timeFormat: 'a HH:mm', - timeRulerSize: 70, + return SfCalendar( + view: CalendarView.day, + headerHeight: 0, + viewHeaderHeight: 0, + backgroundColor: Colors.white, + showCurrentTimeIndicator: false, + viewNavigationMode: ViewNavigationMode.none, + timeSlotViewSettings: TimeSlotViewSettings( + startHour: 5, + endHour: 24, + timeFormat: 'a HH:mm', + timeRulerSize: 70, + ), + dataSource: ScheduleDataSource(scheduleController.schedules), + appointmentBuilder: (BuildContext context, CalendarAppointmentDetails details) { + final Schedule schedule = details.appointments.first; + return Container( + margin: EdgeInsets.only(left: 2, right: 2, top: 2, bottom: 2), + decoration: BoxDecoration( + color: Color(int.parse('0xff${schedule.color ?? 'ffffff'}')), + borderRadius: BorderRadius.circular(4), ), - ), - Positioned.fill( - child: CustomPaint( - painter: ScheduleBarPainter(scheduleController.schedules), + child: Center( + child: Text( + schedule.title ?? '', + style: TextStyle( + color: Colors.white, + fontSize: 12, + fontWeight: FontWeight.bold, + ), + overflow: TextOverflow.ellipsis, + ), ), - ), - ], + ); + }, ); }), ), @@ -352,7 +400,6 @@ class _HomeMainState extends State { } } -// 그룹 항목들의 리스트 아이템 Widget GroupListItem() { return InkWell( onTap: () { @@ -403,36 +450,3 @@ class ScheduleDataSource extends CalendarDataSource { }).toList(); } } - -class ScheduleBarPainter extends CustomPainter { - final List schedules; - - ScheduleBarPainter(this.schedules); - - @override - void paint(Canvas canvas, Size size) { - final paint = Paint() - ..style = PaintingStyle.fill; - - final double hourHeight = size.height / 19; // 5AM ~ 24PM - - for (var schedule in schedules) { - final startHour = schedule.startDt!.hour - 5; - final endHour = schedule.endDt!.hour - 5; - final top = startHour * hourHeight; - final height = (endHour - startHour) * hourHeight; - - paint.color = Color(int.parse('0xff${schedule.color ?? 'ffffff'}')); - - canvas.drawRect( - Rect.fromLTWH(0, top, size.width, height), - paint, - ); - } - } - - @override - bool shouldRepaint(covariant CustomPainter oldDelegate) { - return true; - } -} From 34675fbf0635d1f7339acdf7b3bbaed2fcb238d4 Mon Sep 17 00:00:00 2001 From: jay Date: Tue, 11 Jun 2024 10:52:34 +0900 Subject: [PATCH 3/3] =?UTF-8?q?=E2=9C=A8=20HOME=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=9D=BC=EB=B6=80=20=EA=B5=AC=ED=98=84=20db=EB=AA=85=EC=84=B8?= =?UTF-8?q?=EC=84=9C=EC=97=90=20=EB=94=B0=EB=9D=BC=20tbl,usecase,repositor?= =?UTF-8?q?y,controller=20=EC=9E=91=EC=84=B1.=20memo,=20schedule=20firebas?= =?UTF-8?q?e=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EB=B6=88=EB=9F=AC=EC=98=A4?= =?UTF-8?q?=EA=B8=B0=20=EB=AF=B8=EC=99=84=EC=84=B1.=20todo=20=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=84=B0=20=EB=B6=88=EB=9F=AC=EC=98=A4=EA=B8=B0=20?= =?UTF-8?q?=EC=99=84=EC=84=B1.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/ui/home/view/home_main.dart | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/ui/home/view/home_main.dart b/lib/ui/home/view/home_main.dart index 8531bf3..51b1d8a 100644 --- a/lib/ui/home/view/home_main.dart +++ b/lib/ui/home/view/home_main.dart @@ -144,6 +144,7 @@ class _HomeMainState extends State { margin: EdgeInsets.fromLTRB(15, 0, 15, 15), child: Obx(() { final memoController = Get.find(); + print('Memos in UI: ${memoController.memos}'); if (memoController.memos.isEmpty) { return Text( "등록된 메모가 없습니다.", @@ -218,6 +219,7 @@ class _HomeMainState extends State { margin: EdgeInsets.fromLTRB(15, 0, 15, 15), child: Obx(() { final todoController = Get.find(); + print('Todos in UI: ${todoController.todos}'); if (todoController.todos.isEmpty) { return Text( "등록된 TODO 리스트가 없습니다.", @@ -287,6 +289,7 @@ class _HomeMainState extends State { height: 500, child: Obx(() { final scheduleController = Get.find(); + print('Schedules in UI: ${scheduleController.schedules}'); if (scheduleController.schedules.isEmpty) { return SfCalendar( view: CalendarView.day, @@ -318,16 +321,16 @@ class _HomeMainState extends State { ), dataSource: ScheduleDataSource(scheduleController.schedules), appointmentBuilder: (BuildContext context, CalendarAppointmentDetails details) { - final Schedule schedule = details.appointments.first; + final Appointment appointment = details.appointments.first; return Container( margin: EdgeInsets.only(left: 2, right: 2, top: 2, bottom: 2), decoration: BoxDecoration( - color: Color(int.parse('0xff${schedule.color ?? 'ffffff'}')), + color: appointment.color, borderRadius: BorderRadius.circular(4), ), child: Center( child: Text( - schedule.title ?? '', + appointment.subject, style: TextStyle( color: Colors.white, fontSize: 12,