Flutter 앱: MVCS 아키텍처 리팩토링 (일정 화면)
- controllers/ 폴더 추가 - ScheduleController 생성 (Riverpod Notifier) - ScheduleState 상태 클래스 분리 - ScheduleView를 ConsumerStatefulWidget으로 변경 - View는 UI 렌더링만, 비즈니스 로직은 Controller로 분리 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
221aaa2bb4
commit
bc37abe473
2 changed files with 195 additions and 122 deletions
132
app/lib/controllers/schedule_controller.dart
Normal file
132
app/lib/controllers/schedule_controller.dart
Normal file
|
|
@ -0,0 +1,132 @@
|
||||||
|
/// 일정 컨트롤러 (MVCS의 Controller 레이어)
|
||||||
|
///
|
||||||
|
/// 비즈니스 로직과 상태 관리를 담당합니다.
|
||||||
|
/// View는 이 Controller를 통해 데이터에 접근합니다.
|
||||||
|
library;
|
||||||
|
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import '../models/schedule.dart';
|
||||||
|
import '../services/schedules_service.dart';
|
||||||
|
|
||||||
|
/// 일정 상태
|
||||||
|
class ScheduleState {
|
||||||
|
final DateTime selectedDate;
|
||||||
|
final List<Schedule> schedules;
|
||||||
|
final bool isLoading;
|
||||||
|
final String? error;
|
||||||
|
|
||||||
|
const ScheduleState({
|
||||||
|
required this.selectedDate,
|
||||||
|
this.schedules = const [],
|
||||||
|
this.isLoading = false,
|
||||||
|
this.error,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// 상태 복사 (불변성 유지)
|
||||||
|
ScheduleState copyWith({
|
||||||
|
DateTime? selectedDate,
|
||||||
|
List<Schedule>? schedules,
|
||||||
|
bool? isLoading,
|
||||||
|
String? error,
|
||||||
|
}) {
|
||||||
|
return ScheduleState(
|
||||||
|
selectedDate: selectedDate ?? this.selectedDate,
|
||||||
|
schedules: schedules ?? this.schedules,
|
||||||
|
isLoading: isLoading ?? this.isLoading,
|
||||||
|
error: error,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 선택된 날짜의 일정 목록
|
||||||
|
List<Schedule> get selectedDateSchedules {
|
||||||
|
final dateStr = DateFormat('yyyy-MM-dd').format(selectedDate);
|
||||||
|
return schedules.where((s) => s.date.split('T')[0] == dateStr).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 특정 날짜의 일정 (점 표시용, 최대 3개)
|
||||||
|
List<Schedule> getDaySchedules(DateTime date) {
|
||||||
|
final dateStr = DateFormat('yyyy-MM-dd').format(date);
|
||||||
|
return schedules.where((s) => s.date.split('T')[0] == dateStr).take(3).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 해당 달의 모든 날짜 배열
|
||||||
|
List<DateTime> get daysInMonth {
|
||||||
|
final year = selectedDate.year;
|
||||||
|
final month = selectedDate.month;
|
||||||
|
final lastDay = DateTime(year, month + 1, 0).day;
|
||||||
|
return List.generate(lastDay, (i) => DateTime(year, month, i + 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 일정 컨트롤러
|
||||||
|
class ScheduleController extends Notifier<ScheduleState> {
|
||||||
|
@override
|
||||||
|
ScheduleState build() {
|
||||||
|
// 초기 상태
|
||||||
|
final initialState = ScheduleState(selectedDate: DateTime.now());
|
||||||
|
// 초기 데이터 로드
|
||||||
|
Future.microtask(() => loadSchedules());
|
||||||
|
return initialState;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 월별 일정 로드
|
||||||
|
Future<void> loadSchedules() async {
|
||||||
|
state = state.copyWith(isLoading: true, error: null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
final schedules = await getSchedules(
|
||||||
|
state.selectedDate.year,
|
||||||
|
state.selectedDate.month,
|
||||||
|
);
|
||||||
|
state = state.copyWith(schedules: schedules, isLoading: false);
|
||||||
|
} catch (e) {
|
||||||
|
state = state.copyWith(isLoading: false, error: e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 날짜 선택
|
||||||
|
void selectDate(DateTime date) {
|
||||||
|
state = state.copyWith(selectedDate: date);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 월 변경
|
||||||
|
void changeMonth(int delta) {
|
||||||
|
final newDate = DateTime(
|
||||||
|
state.selectedDate.year,
|
||||||
|
state.selectedDate.month + delta,
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
final today = DateTime.now();
|
||||||
|
|
||||||
|
// 이번 달이면 오늘 날짜, 다른 달이면 1일 선택
|
||||||
|
final selectedDay = (newDate.year == today.year && newDate.month == today.month)
|
||||||
|
? today.day
|
||||||
|
: 1;
|
||||||
|
|
||||||
|
state = state.copyWith(
|
||||||
|
selectedDate: DateTime(newDate.year, newDate.month, selectedDay),
|
||||||
|
);
|
||||||
|
loadSchedules();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 오늘 여부
|
||||||
|
bool isToday(DateTime date) {
|
||||||
|
final today = DateTime.now();
|
||||||
|
return date.year == today.year &&
|
||||||
|
date.month == today.month &&
|
||||||
|
date.day == today.day;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 선택된 날짜 여부
|
||||||
|
bool isSelected(DateTime date) {
|
||||||
|
return date.year == state.selectedDate.year &&
|
||||||
|
date.month == state.selectedDate.month &&
|
||||||
|
date.day == state.selectedDate.day;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 일정 Provider
|
||||||
|
final scheduleProvider = NotifierProvider<ScheduleController, ScheduleState>(
|
||||||
|
ScheduleController.new,
|
||||||
|
);
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
/// 일정 화면
|
/// 일정 화면 (MVCS의 View 레이어)
|
||||||
|
///
|
||||||
|
/// UI 렌더링만 담당하고, 비즈니스 로직은 Controller에 위임합니다.
|
||||||
library;
|
library;
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import '../../core/constants.dart';
|
import '../../core/constants.dart';
|
||||||
import '../../models/schedule.dart';
|
import '../../models/schedule.dart';
|
||||||
import '../../services/schedules_service.dart';
|
import '../../controllers/schedule_controller.dart';
|
||||||
|
|
||||||
/// HTML 엔티티 디코딩
|
/// HTML 엔티티 디코딩
|
||||||
String decodeHtmlEntities(String text) {
|
String decodeHtmlEntities(String text) {
|
||||||
|
|
@ -18,25 +20,17 @@ String decodeHtmlEntities(String text) {
|
||||||
.replaceAll(' ', ' ');
|
.replaceAll(' ', ' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
class ScheduleView extends StatefulWidget {
|
class ScheduleView extends ConsumerStatefulWidget {
|
||||||
const ScheduleView({super.key});
|
const ScheduleView({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ScheduleView> createState() => _ScheduleViewState();
|
ConsumerState<ScheduleView> createState() => _ScheduleViewState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ScheduleViewState extends State<ScheduleView> {
|
class _ScheduleViewState extends ConsumerState<ScheduleView> {
|
||||||
DateTime _selectedDate = DateTime.now();
|
|
||||||
List<Schedule> _schedules = [];
|
|
||||||
bool _isLoading = true;
|
|
||||||
final ScrollController _dateScrollController = ScrollController();
|
final ScrollController _dateScrollController = ScrollController();
|
||||||
final ScrollController _contentScrollController = ScrollController();
|
final ScrollController _contentScrollController = ScrollController();
|
||||||
|
DateTime? _lastSelectedDate;
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_loadSchedules();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
|
@ -45,35 +39,15 @@ class _ScheduleViewState extends State<ScheduleView> {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 월별 일정 로드
|
|
||||||
Future<void> _loadSchedules() async {
|
|
||||||
setState(() => _isLoading = true);
|
|
||||||
try {
|
|
||||||
final schedules = await getSchedules(
|
|
||||||
_selectedDate.year,
|
|
||||||
_selectedDate.month,
|
|
||||||
);
|
|
||||||
setState(() {
|
|
||||||
_schedules = schedules;
|
|
||||||
_isLoading = false;
|
|
||||||
});
|
|
||||||
// 선택된 날짜로 스크롤
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
||||||
_scrollToSelectedDate();
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
setState(() => _isLoading = false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 선택된 날짜로 스크롤
|
/// 선택된 날짜로 스크롤
|
||||||
void _scrollToSelectedDate() {
|
void _scrollToSelectedDate(DateTime selectedDate) {
|
||||||
if (!_dateScrollController.hasClients) return;
|
if (!_dateScrollController.hasClients) return;
|
||||||
|
|
||||||
final dayIndex = _selectedDate.day - 1;
|
final dayIndex = selectedDate.day - 1;
|
||||||
const itemWidth = 52.0; // 44 + 8 (gap)
|
const itemWidth = 52.0; // 44 + 8 (gap)
|
||||||
final targetOffset = (dayIndex * itemWidth) -
|
final targetOffset = (dayIndex * itemWidth) -
|
||||||
(MediaQuery.of(context).size.width / 2) + (itemWidth / 2);
|
(MediaQuery.of(context).size.width / 2) +
|
||||||
|
(itemWidth / 2);
|
||||||
_dateScrollController.animateTo(
|
_dateScrollController.animateTo(
|
||||||
targetOffset.clamp(0, _dateScrollController.position.maxScrollExtent),
|
targetOffset.clamp(0, _dateScrollController.position.maxScrollExtent),
|
||||||
duration: const Duration(milliseconds: 150),
|
duration: const Duration(milliseconds: 150),
|
||||||
|
|
@ -81,51 +55,16 @@ class _ScheduleViewState extends State<ScheduleView> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 월 변경
|
/// 날짜 선택 핸들러
|
||||||
void _changeMonth(int delta) {
|
void _onDateSelected(DateTime date) {
|
||||||
final newDate = DateTime(_selectedDate.year, _selectedDate.month + delta, 1);
|
// 일정 목록 맨 위로 즉시 이동
|
||||||
final today = DateTime.now();
|
|
||||||
|
|
||||||
// 이번 달이면 오늘 날짜, 다른 달이면 1일 선택
|
|
||||||
final selectedDay = (newDate.year == today.year && newDate.month == today.month)
|
|
||||||
? today.day
|
|
||||||
: 1;
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
_selectedDate = DateTime(newDate.year, newDate.month, selectedDay);
|
|
||||||
});
|
|
||||||
_loadSchedules();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 날짜 선택
|
|
||||||
void _selectDate(DateTime date) {
|
|
||||||
// 일정 목록 맨 위로 즉시 이동 (애니메이션과 겹치지 않도록)
|
|
||||||
if (_contentScrollController.hasClients) {
|
if (_contentScrollController.hasClients) {
|
||||||
_contentScrollController.jumpTo(0);
|
_contentScrollController.jumpTo(0);
|
||||||
}
|
}
|
||||||
setState(() => _selectedDate = date);
|
// Controller에 날짜 선택 요청
|
||||||
// 선택된 날짜 중앙으로 스크롤
|
ref.read(scheduleProvider.notifier).selectDate(date);
|
||||||
_scrollToSelectedDate();
|
// 선택된 날짜로 스크롤
|
||||||
}
|
_scrollToSelectedDate(date);
|
||||||
|
|
||||||
/// 해당 달의 모든 날짜 배열
|
|
||||||
List<DateTime> get _daysInMonth {
|
|
||||||
final year = _selectedDate.year;
|
|
||||||
final month = _selectedDate.month;
|
|
||||||
final lastDay = DateTime(year, month + 1, 0).day;
|
|
||||||
return List.generate(lastDay, (i) => DateTime(year, month, i + 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 선택된 날짜의 일정
|
|
||||||
List<Schedule> get _selectedDateSchedules {
|
|
||||||
final dateStr = DateFormat('yyyy-MM-dd').format(_selectedDate);
|
|
||||||
return _schedules.where((s) => s.date.split('T')[0] == dateStr).toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 특정 날짜의 일정 (점 표시용)
|
|
||||||
List<Schedule> _getDaySchedules(DateTime date) {
|
|
||||||
final dateStr = DateFormat('yyyy-MM-dd').format(date);
|
|
||||||
return _schedules.where((s) => s.date.split('T')[0] == dateStr).take(3).toList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 요일 이름
|
/// 요일 이름
|
||||||
|
|
@ -134,21 +73,6 @@ class _ScheduleViewState extends State<ScheduleView> {
|
||||||
return days[date.weekday % 7];
|
return days[date.weekday % 7];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 오늘 여부
|
|
||||||
bool _isToday(DateTime date) {
|
|
||||||
final today = DateTime.now();
|
|
||||||
return date.year == today.year &&
|
|
||||||
date.month == today.month &&
|
|
||||||
date.day == today.day;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 선택된 날짜 여부
|
|
||||||
bool _isSelected(DateTime date) {
|
|
||||||
return date.year == _selectedDate.year &&
|
|
||||||
date.month == _selectedDate.month &&
|
|
||||||
date.day == _selectedDate.day;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 카테고리 색상 파싱
|
/// 카테고리 색상 파싱
|
||||||
Color _parseColor(String? colorStr) {
|
Color _parseColor(String? colorStr) {
|
||||||
if (colorStr == null || colorStr.isEmpty) return AppColors.textTertiary;
|
if (colorStr == null || colorStr.isEmpty) return AppColors.textTertiary;
|
||||||
|
|
@ -162,22 +86,33 @@ class _ScheduleViewState extends State<ScheduleView> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final scheduleState = ref.watch(scheduleProvider);
|
||||||
|
final controller = ref.read(scheduleProvider.notifier);
|
||||||
|
|
||||||
|
// 날짜가 변경되면 스크롤
|
||||||
|
if (_lastSelectedDate != scheduleState.selectedDate) {
|
||||||
|
_lastSelectedDate = scheduleState.selectedDate;
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
_scrollToSelectedDate(scheduleState.selectedDate);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
// 자체 툴바
|
// 자체 툴바
|
||||||
_buildToolbar(),
|
_buildToolbar(scheduleState, controller),
|
||||||
// 날짜 선택기
|
// 날짜 선택기
|
||||||
_buildDateSelector(),
|
_buildDateSelector(scheduleState, controller),
|
||||||
// 일정 목록
|
// 일정 목록
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _buildScheduleList(),
|
child: _buildScheduleList(scheduleState),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 툴바 빌드
|
/// 툴바 빌드
|
||||||
Widget _buildToolbar() {
|
Widget _buildToolbar(ScheduleState state, ScheduleController controller) {
|
||||||
return Container(
|
return Container(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
|
|
@ -197,7 +132,7 @@ class _ScheduleViewState extends State<ScheduleView> {
|
||||||
),
|
),
|
||||||
// 이전 월
|
// 이전 월
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () => _changeMonth(-1),
|
onPressed: () => controller.changeMonth(-1),
|
||||||
icon: const Icon(Icons.chevron_left, size: 24),
|
icon: const Icon(Icons.chevron_left, size: 24),
|
||||||
color: AppColors.textPrimary,
|
color: AppColors.textPrimary,
|
||||||
),
|
),
|
||||||
|
|
@ -205,7 +140,7 @@ class _ScheduleViewState extends State<ScheduleView> {
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
'${_selectedDate.year}년 ${_selectedDate.month}월',
|
'${state.selectedDate.year}년 ${state.selectedDate.month}월',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
|
|
@ -216,7 +151,7 @@ class _ScheduleViewState extends State<ScheduleView> {
|
||||||
),
|
),
|
||||||
// 다음 월
|
// 다음 월
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () => _changeMonth(1),
|
onPressed: () => controller.changeMonth(1),
|
||||||
icon: const Icon(Icons.chevron_right, size: 24),
|
icon: const Icon(Icons.chevron_right, size: 24),
|
||||||
color: AppColors.textPrimary,
|
color: AppColors.textPrimary,
|
||||||
),
|
),
|
||||||
|
|
@ -236,7 +171,7 @@ class _ScheduleViewState extends State<ScheduleView> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 날짜 선택기 빌드
|
/// 날짜 선택기 빌드
|
||||||
Widget _buildDateSelector() {
|
Widget _buildDateSelector(ScheduleState state, ScheduleController controller) {
|
||||||
return Container(
|
return Container(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
child: Container(
|
child: Container(
|
||||||
|
|
@ -255,16 +190,16 @@ class _ScheduleViewState extends State<ScheduleView> {
|
||||||
controller: _dateScrollController,
|
controller: _dateScrollController,
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8),
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8),
|
||||||
itemCount: _daysInMonth.length,
|
itemCount: state.daysInMonth.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final date = _daysInMonth[index];
|
final date = state.daysInMonth[index];
|
||||||
final isSelected = _isSelected(date);
|
final isSelected = controller.isSelected(date);
|
||||||
final isToday = _isToday(date);
|
final isToday = controller.isToday(date);
|
||||||
final dayOfWeek = date.weekday;
|
final dayOfWeek = date.weekday;
|
||||||
final daySchedules = _getDaySchedules(date);
|
final daySchedules = state.getDaySchedules(date);
|
||||||
|
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () => _selectDate(date),
|
onTap: () => _onDateSelected(date),
|
||||||
child: Container(
|
child: Container(
|
||||||
width: 44,
|
width: 44,
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 4),
|
margin: const EdgeInsets.symmetric(horizontal: 4),
|
||||||
|
|
@ -315,7 +250,8 @@ class _ScheduleViewState extends State<ScheduleView> {
|
||||||
return Container(
|
return Container(
|
||||||
width: 4,
|
width: 4,
|
||||||
height: 4,
|
height: 4,
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 1),
|
margin:
|
||||||
|
const EdgeInsets.symmetric(horizontal: 1),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: _parseColor(schedule.categoryColor),
|
color: _parseColor(schedule.categoryColor),
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
|
|
@ -336,17 +272,17 @@ class _ScheduleViewState extends State<ScheduleView> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 일정 목록 빌드
|
/// 일정 목록 빌드
|
||||||
Widget _buildScheduleList() {
|
Widget _buildScheduleList(ScheduleState state) {
|
||||||
if (_isLoading) {
|
if (state.isLoading) {
|
||||||
return const Center(
|
return const Center(
|
||||||
child: CircularProgressIndicator(color: AppColors.primary),
|
child: CircularProgressIndicator(color: AppColors.primary),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_selectedDateSchedules.isEmpty) {
|
if (state.selectedDateSchedules.isEmpty) {
|
||||||
return Center(
|
return Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
'${_selectedDate.month}월 ${_selectedDate.day}일 일정이 없습니다',
|
'${state.selectedDate.month}월 ${state.selectedDate.day}일 일정이 없습니다',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
color: AppColors.textTertiary,
|
color: AppColors.textTertiary,
|
||||||
|
|
@ -358,13 +294,14 @@ class _ScheduleViewState extends State<ScheduleView> {
|
||||||
return ListView.builder(
|
return ListView.builder(
|
||||||
controller: _contentScrollController,
|
controller: _contentScrollController,
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
itemCount: _selectedDateSchedules.length,
|
itemCount: state.selectedDateSchedules.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final schedule = _selectedDateSchedules[index];
|
final schedule = state.selectedDateSchedules[index];
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: EdgeInsets.only(bottom: index < _selectedDateSchedules.length - 1 ? 12 : 0),
|
padding: EdgeInsets.only(
|
||||||
|
bottom: index < state.selectedDateSchedules.length - 1 ? 12 : 0),
|
||||||
child: _AnimatedScheduleCard(
|
child: _AnimatedScheduleCard(
|
||||||
key: ValueKey('${schedule.id}_${_selectedDate.toString()}'),
|
key: ValueKey('${schedule.id}_${state.selectedDate.toString()}'),
|
||||||
index: index,
|
index: index,
|
||||||
schedule: schedule,
|
schedule: schedule,
|
||||||
categoryColor: _parseColor(schedule.categoryColor),
|
categoryColor: _parseColor(schedule.categoryColor),
|
||||||
|
|
@ -489,7 +426,8 @@ class _ScheduleCard extends StatelessWidget {
|
||||||
// 시간 뱃지
|
// 시간 뱃지
|
||||||
if (schedule.formattedTime != null)
|
if (schedule.formattedTime != null)
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
|
padding:
|
||||||
|
const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: categoryColor,
|
color: categoryColor,
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
|
@ -518,7 +456,8 @@ class _ScheduleCard extends StatelessWidget {
|
||||||
// 카테고리 뱃지
|
// 카테고리 뱃지
|
||||||
if (schedule.categoryName != null)
|
if (schedule.categoryName != null)
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
|
padding:
|
||||||
|
const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: categoryColor.withValues(alpha: 0.1),
|
color: categoryColor.withValues(alpha: 0.1),
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
|
@ -583,7 +522,9 @@ class _ScheduleCard extends StatelessWidget {
|
||||||
? [
|
? [
|
||||||
_MemberChip(name: '프로미스나인'),
|
_MemberChip(name: '프로미스나인'),
|
||||||
]
|
]
|
||||||
: memberList.map((name) => _MemberChip(name: name)).toList(),
|
: memberList
|
||||||
|
.map((name) => _MemberChip(name: name))
|
||||||
|
.toList(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue