feat(schedule): 달력 UI 개선 및 검색 준비

- ExpandablePageView로 달력 높이 동적 조절 (월별 주 수에 따라)
- 데이트픽커 년도 변경 시 스와이프 애니메이션 추가
- 달력 월 변경 시 일정 점 비동기 업데이트 (캐시 기반)
- 모든 달력/데이트픽커 텍스트에 Pretendard 폰트 적용
- 데이트픽커 화살표 터치 영역 확대 및 ripple effect 추가
- 데이트픽커 펼침 시 툴바 좌우 화살표 숨김 (페이드 애니메이션)
- expandable_page_view 패키지 추가

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
caadiq 2026-01-13 20:37:00 +09:00
parent 895d9c26a3
commit c4cbdc7d33
4 changed files with 557 additions and 298 deletions

View file

@ -15,12 +15,15 @@ class ScheduleState {
final List<Schedule> schedules; final List<Schedule> schedules;
final bool isLoading; final bool isLoading;
final String? error; final String? error;
// (key: "yyyy-MM")
final Map<String, List<Schedule>> calendarCache;
const ScheduleState({ const ScheduleState({
required this.selectedDate, required this.selectedDate,
this.schedules = const [], this.schedules = const [],
this.isLoading = false, this.isLoading = false,
this.error, this.error,
this.calendarCache = const {},
}); });
/// ( ) /// ( )
@ -29,12 +32,14 @@ class ScheduleState {
List<Schedule>? schedules, List<Schedule>? schedules,
bool? isLoading, bool? isLoading,
String? error, String? error,
Map<String, List<Schedule>>? calendarCache,
}) { }) {
return ScheduleState( return ScheduleState(
selectedDate: selectedDate ?? this.selectedDate, selectedDate: selectedDate ?? this.selectedDate,
schedules: schedules ?? this.schedules, schedules: schedules ?? this.schedules,
isLoading: isLoading ?? this.isLoading, isLoading: isLoading ?? this.isLoading,
error: error, error: error,
calendarCache: calendarCache ?? this.calendarCache,
); );
} }
@ -45,11 +50,29 @@ class ScheduleState {
} }
/// ( , 3) /// ( , 3)
/// , schedules에서
List<Schedule> getDaySchedules(DateTime date) { List<Schedule> getDaySchedules(DateTime date) {
final dateStr = DateFormat('yyyy-MM-dd').format(date); final dateStr = DateFormat('yyyy-MM-dd').format(date);
final cacheKey = DateFormat('yyyy-MM').format(date);
//
if (calendarCache.containsKey(cacheKey)) {
return calendarCache[cacheKey]!
.where((s) => s.date.split('T')[0] == dateStr)
.take(3)
.toList();
}
// schedules에서
return schedules.where((s) => s.date.split('T')[0] == dateStr).take(3).toList(); return schedules.where((s) => s.date.split('T')[0] == dateStr).take(3).toList();
} }
///
bool hasMonthCache(int year, int month) {
final cacheKey = '$year-${month.toString().padLeft(2, '0')}';
return calendarCache.containsKey(cacheKey);
}
/// ///
List<DateTime> get daysInMonth { List<DateTime> get daysInMonth {
final year = selectedDate.year; final year = selectedDate.year;
@ -79,12 +102,38 @@ class ScheduleController extends Notifier<ScheduleState> {
state.selectedDate.year, state.selectedDate.year,
state.selectedDate.month, state.selectedDate.month,
); );
state = state.copyWith(schedules: schedules, isLoading: false); //
final cacheKey = '${state.selectedDate.year}-${state.selectedDate.month.toString().padLeft(2, '0')}';
final newCache = Map<String, List<Schedule>>.from(state.calendarCache);
newCache[cacheKey] = schedules;
state = state.copyWith(
schedules: schedules,
isLoading: false,
calendarCache: newCache,
);
} catch (e) { } catch (e) {
state = state.copyWith(isLoading: false, error: e.toString()); state = state.copyWith(isLoading: false, error: e.toString());
} }
} }
/// (UI )
Future<void> loadCalendarMonth(int year, int month) async {
final cacheKey = '$year-${month.toString().padLeft(2, '0')}';
//
if (state.calendarCache.containsKey(cacheKey)) return;
try {
final schedules = await getSchedules(year, month);
//
final newCache = Map<String, List<Schedule>>.from(state.calendarCache);
newCache[cacheKey] = schedules;
state = state.copyWith(calendarCache: newCache);
} catch (e) {
// ( )
}
}
/// ///
void selectDate(DateTime date) { void selectDate(DateTime date) {
state = state.copyWith(selectedDate: date); state = state.copyWith(selectedDate: date);

File diff suppressed because it is too large Load diff

View file

@ -169,6 +169,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.1" version: "2.1.1"
expandable_page_view:
dependency: "direct main"
description:
name: expandable_page_view
sha256: "2d2c9e6fbbaa153f761054200c199eb69dc45948c8018b98a871212c67b60608"
url: "https://pub.dev"
source: hosted
version: "1.2.0"
fake_async: fake_async:
dependency: transitive dependency: transitive
description: description:

View file

@ -49,6 +49,7 @@ dependencies:
modal_bottom_sheet: ^3.0.0 modal_bottom_sheet: ^3.0.0
video_player: ^2.9.2 video_player: ^2.9.2
chewie: ^1.8.5 chewie: ^1.8.5
expandable_page_view: ^1.0.17
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: