diff --git a/app/lib/controllers/schedule_controller.dart b/app/lib/controllers/schedule_controller.dart index acf2e48..e5658e2 100644 --- a/app/lib/controllers/schedule_controller.dart +++ b/app/lib/controllers/schedule_controller.dart @@ -6,6 +6,7 @@ library; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:intl/intl.dart'; +import 'package:shared_preferences/shared_preferences.dart'; import '../models/schedule.dart'; import '../services/schedules_service.dart'; @@ -363,3 +364,89 @@ class SuggestionController extends Notifier { final suggestionProvider = NotifierProvider( SuggestionController.new, ); + +/// 최근 검색기록 상태 +class RecentSearchState { + final List searches; + + const RecentSearchState({this.searches = const []}); + + RecentSearchState copyWith({List? searches}) { + return RecentSearchState(searches: searches ?? this.searches); + } +} + +/// 최근 검색기록 컨트롤러 +class RecentSearchController extends Notifier { + static const int _maxHistory = 10; + + @override + RecentSearchState build() { + _loadFromStorage(); + return const RecentSearchState(); + } + + /// SharedPreferences에서 로드 + Future _loadFromStorage() async { + try { + final prefs = await SharedPreferences.getInstance(); + final searches = prefs.getStringList('recent_searches'); + if (searches != null) { + state = state.copyWith(searches: searches); + } + } catch (e) { + // 로드 실패 시 무시 + } + } + + /// 검색어 추가 + Future addSearch(String query) async { + if (query.trim().isEmpty) return; + + final trimmed = query.trim(); + final newSearches = [ + trimmed, + ...state.searches.where((s) => s != trimmed), + ].take(_maxHistory).toList(); + + state = state.copyWith(searches: newSearches); + + // SharedPreferences에 저장 + try { + final prefs = await SharedPreferences.getInstance(); + await prefs.setStringList('recent_searches', newSearches); + } catch (e) { + // 저장 실패 시 무시 + } + } + + /// 특정 검색어 삭제 + Future removeSearch(String query) async { + final newSearches = state.searches.where((s) => s != query).toList(); + state = state.copyWith(searches: newSearches); + + try { + final prefs = await SharedPreferences.getInstance(); + await prefs.setStringList('recent_searches', newSearches); + } catch (e) { + // 저장 실패 시 무시 + } + } + + /// 전체 삭제 + Future clearAll() async { + state = const RecentSearchState(); + + try { + final prefs = await SharedPreferences.getInstance(); + await prefs.setStringList('recent_searches', []); + } catch (e) { + // 저장 실패 시 무시 + } + } +} + +/// 최근 검색기록 Provider +final recentSearchProvider = NotifierProvider( + RecentSearchController.new, +); diff --git a/app/lib/views/schedule/schedule_view.dart b/app/lib/views/schedule/schedule_view.dart index 5ae1573..2e85b9c 100644 --- a/app/lib/views/schedule/schedule_view.dart +++ b/app/lib/views/schedule/schedule_view.dart @@ -105,6 +105,11 @@ class _ScheduleViewState extends ConsumerState ref.read(searchProvider.notifier).clear(); ref.read(suggestionProvider.notifier).clear(); _searchFocusNode.unfocus(); + // 검색 종료 시 선택된 날짜로 스크롤 + final selectedDate = ref.read(scheduleProvider).selectedDate; + WidgetsBinding.instance.addPostFrameCallback((_) { + _scrollToSelectedDate(selectedDate); + }); } /// 추천 검색어 화면 표시 (유튜브 스타일) @@ -136,6 +141,7 @@ class _ScheduleViewState extends ConsumerState if (query.trim().isNotEmpty) { _lastSearchTerm = query; // 검색어 저장 ref.read(searchProvider.notifier).search(query); + ref.read(recentSearchProvider.notifier).addSearch(query); // 최근 검색기록 저장 setState(() { _showSuggestions = false; }); @@ -175,7 +181,9 @@ class _ScheduleViewState extends ConsumerState /// 달력 열기 void _openCalendar(DateTime initialDate) { final today = DateTime.now(); - final monthDelta = (initialDate.year - today.year) * 12 + (initialDate.month - today.month); + final monthDelta = + (initialDate.year - today.year) * 12 + + (initialDate.month - today.month); _yearRangeStart = (initialDate.year ~/ 12) * 12; // 년도 PageView 페이지 계산 @@ -218,7 +226,8 @@ class _ScheduleViewState extends ConsumerState final dayIndex = selectedDate.day - 1; const itemWidth = 52.0; // 44 + 8 (gap) - final targetOffset = (dayIndex * itemWidth) - + final targetOffset = + (dayIndex * itemWidth) - (MediaQuery.of(context).size.width / 2) + (itemWidth / 2); _dateScrollController.animateTo( @@ -291,10 +300,7 @@ class _ScheduleViewState extends ConsumerState switchInCurve: Curves.easeOut, switchOutCurve: Curves.easeIn, transitionBuilder: (child, animation) { - return FadeTransition( - opacity: animation, - child: child, - ); + return FadeTransition(opacity: animation, child: child); }, child: _isSearchMode ? KeyedSubtree( @@ -320,14 +326,14 @@ class _ScheduleViewState extends ConsumerState duration: const Duration(milliseconds: 200), child: _isSearchMode ? (_showSuggestions - ? KeyedSubtree( - key: const ValueKey('suggestions'), - child: _buildSuggestions(suggestionState), - ) - : KeyedSubtree( - key: const ValueKey('search_results'), - child: _buildSearchResults(searchState), - )) + ? KeyedSubtree( + key: const ValueKey('suggestions'), + child: _buildSuggestions(suggestionState), + ) + : KeyedSubtree( + key: const ValueKey('search_results'), + child: _buildSearchResults(searchState), + )) : KeyedSubtree( key: const ValueKey('schedule_list'), child: _buildScheduleList(scheduleState), @@ -350,7 +356,9 @@ class _ScheduleViewState extends ConsumerState return GestureDetector( onTap: _closeCalendar, child: Container( - color: Colors.black.withValues(alpha: 0.4 * _calendarAnimation.value), + color: Colors.black.withValues( + alpha: 0.4 * _calendarAnimation.value, + ), ), ); }, @@ -475,33 +483,28 @@ class _ScheduleViewState extends ConsumerState /// 추천 검색어 빌드 (유튜브 스타일) Widget _buildSuggestions(SuggestionState suggestionState) { - // 입력값이 없을 때 + final recentSearchState = ref.watch(recentSearchProvider); + + // 입력값이 없을 때 - 최근 검색기록 표시 if (_searchInputController.text.isEmpty) { - return Padding( - padding: const EdgeInsets.only(top: 100), - child: Align( - alignment: Alignment.topCenter, - child: Text( - '검색어를 입력하세요', - style: const TextStyle( - fontFamily: 'Pretendard', - fontSize: 14, - color: AppColors.textTertiary, + if (recentSearchState.searches.isEmpty) { + return Padding( + padding: const EdgeInsets.only(top: 100), + child: Align( + alignment: Alignment.topCenter, + child: Text( + '검색어를 입력하세요', + style: const TextStyle( + fontFamily: 'Pretendard', + fontSize: 14, + color: AppColors.textTertiary, + ), ), ), - ), - ); - } - - // 로딩 중 - if (suggestionState.isLoading && suggestionState.suggestions.isEmpty) { - return Padding( - padding: const EdgeInsets.only(top: 100), - child: Align( - alignment: Alignment.topCenter, - child: const CircularProgressIndicator(color: AppColors.primary), - ), - ); + ); + } + // 최근 검색기록 목록 + return _buildRecentSearches(recentSearchState.searches); } // 추천 검색어 없음 @@ -528,11 +531,12 @@ class _ScheduleViewState extends ConsumerState itemCount: suggestionState.suggestions.length, itemBuilder: (context, index) { final suggestion = suggestionState.suggestions[index]; - return InkWell( + return GestureDetector( onTap: () { _searchInputController.text = suggestion; _onSearch(suggestion); }, + behavior: HitTestBehavior.opaque, child: Container( padding: const EdgeInsets.symmetric(vertical: 14), decoration: BoxDecoration( @@ -565,9 +569,10 @@ class _ScheduleViewState extends ConsumerState GestureDetector( onTap: () { _searchInputController.text = suggestion; - _searchInputController.selection = TextSelection.fromPosition( - TextPosition(offset: suggestion.length), - ); + _searchInputController.selection = + TextSelection.fromPosition( + TextPosition(offset: suggestion.length), + ); _onSearchInputChanged(suggestion); }, child: const Padding( @@ -587,6 +592,108 @@ class _ScheduleViewState extends ConsumerState ); } + /// 최근 검색기록 빌드 + Widget _buildRecentSearches(List searches) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // 헤더 + Padding( + padding: const EdgeInsets.fromLTRB(16, 16, 16, 8), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text( + '최근 검색', + style: TextStyle( + fontFamily: 'Pretendard', + fontSize: 14, + fontWeight: FontWeight.w600, + color: AppColors.textSecondary, + ), + ), + GestureDetector( + onTap: () { + ref.read(recentSearchProvider.notifier).clearAll(); + }, + child: const Text( + '전체 삭제', + style: TextStyle( + fontFamily: 'Pretendard', + fontSize: 12, + color: AppColors.textTertiary, + ), + ), + ), + ], + ), + ), + // 검색기록 목록 + Expanded( + child: ListView.builder( + padding: const EdgeInsets.symmetric(horizontal: 16), + itemCount: searches.length, + itemBuilder: (context, index) { + final search = searches[index]; + return GestureDetector( + onTap: () { + _searchInputController.text = search; + _onSearch(search); + }, + behavior: HitTestBehavior.opaque, + child: Container( + padding: const EdgeInsets.symmetric(vertical: 14), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: AppColors.divider.withValues(alpha: 0.5), + width: 1, + ), + ), + ), + child: Row( + children: [ + const Icon( + Icons.history, + size: 18, + color: AppColors.textTertiary, + ), + const SizedBox(width: 16), + Expanded( + child: Text( + search, + style: const TextStyle( + fontFamily: 'Pretendard', + fontSize: 15, + color: AppColors.textPrimary, + ), + ), + ), + // 삭제 버튼 + GestureDetector( + onTap: () { + ref.read(recentSearchProvider.notifier).removeSearch(search); + }, + child: const Padding( + padding: EdgeInsets.all(4), + child: Icon( + Icons.close, + size: 16, + color: AppColors.textTertiary, + ), + ), + ), + ], + ), + ), + ); + }, + ), + ), + ], + ); + } + /// 검색 결과 빌드 Widget _buildSearchResults(SearchState searchState) { // 검색어가 없을 때 @@ -643,7 +750,8 @@ class _ScheduleViewState extends ConsumerState key: ValueKey('search_list_${searchState.searchTerm}'), controller: _searchScrollController, padding: const EdgeInsets.all(16), - itemCount: searchState.results.length + (searchState.isFetchingMore ? 1 : 0), + itemCount: + searchState.results.length + (searchState.isFetchingMore ? 1 : 0), itemBuilder: (context, index) { // 로딩 인디케이터 if (index >= searchState.results.length) { @@ -702,7 +810,9 @@ class _ScheduleViewState extends ConsumerState } }, icon: const Icon(Icons.calendar_today_outlined, size: 20), - color: _showCalendar ? AppColors.primary : AppColors.textSecondary, + color: _showCalendar + ? AppColors.primary + : AppColors.textSecondary, ), // 이전 월 (데이트픽커가 펼쳐지면 숨김, 페이드 애니메이션) AnimatedSwitcher( @@ -733,7 +843,8 @@ class _ScheduleViewState extends ConsumerState ? () { setState(() { _showYearMonthPicker = !_showYearMonthPicker; - _yearRangeStart = (_calendarViewDate.year ~/ 12) * 12; + _yearRangeStart = + (_calendarViewDate.year ~/ 12) * 12; }); } : null, @@ -745,8 +856,14 @@ class _ScheduleViewState extends ConsumerState AnimatedSwitcher( duration: const Duration(milliseconds: 200), child: _showCalendar - ? const SizedBox(key: ValueKey('arrow_space'), width: 20) - : const SizedBox(key: ValueKey('no_space'), width: 0), + ? const SizedBox( + key: ValueKey('arrow_space'), + width: 20, + ) + : const SizedBox( + key: ValueKey('no_space'), + width: 0, + ), ), // 년월 텍스트 (항상 가운데 고정) Text( @@ -765,7 +882,9 @@ class _ScheduleViewState extends ConsumerState duration: const Duration(milliseconds: 200), child: _showCalendar ? Row( - key: ValueKey('dropdown_$_showYearMonthPicker'), + key: ValueKey( + 'dropdown_$_showYearMonthPicker', + ), mainAxisSize: MainAxisSize.min, children: [ const SizedBox(width: 2), @@ -780,7 +899,10 @@ class _ScheduleViewState extends ConsumerState ), ], ) - : const SizedBox(key: ValueKey('no_dropdown'), width: 0), + : const SizedBox( + key: ValueKey('no_dropdown'), + width: 0, + ), ), ], ), @@ -822,7 +944,10 @@ class _ScheduleViewState extends ConsumerState } /// 달력 팝업 빌드 - Widget _buildCalendarPopup(ScheduleState state, ScheduleController controller) { + Widget _buildCalendarPopup( + ScheduleState state, + ScheduleController controller, + ) { return AnimatedBuilder( animation: _calendarAnimation, builder: (context, child) { @@ -830,10 +955,7 @@ class _ScheduleViewState extends ConsumerState child: Align( alignment: Alignment.topCenter, heightFactor: _calendarAnimation.value, - child: Opacity( - opacity: _calendarAnimation.value, - child: child, - ), + child: Opacity(opacity: _calendarAnimation.value, child: child), ), ); }, @@ -860,7 +982,8 @@ class _ScheduleViewState extends ConsumerState }, transitionBuilder: (child, animation) { // 년월 선택기에만 슬라이드 효과 추가 - final isYearMonthPicker = child.key == const ValueKey('yearMonth'); + final isYearMonthPicker = + child.key == const ValueKey('yearMonth'); if (isYearMonthPicker) { return FadeTransition( opacity: animation, @@ -943,7 +1066,11 @@ class _ScheduleViewState extends ConsumerState // 년도 라벨 const Text( '년도', - style: TextStyle(fontFamily: 'Pretendard', fontSize: 12, color: AppColors.textTertiary), + style: TextStyle( + fontFamily: 'Pretendard', + fontSize: 12, + color: AppColors.textTertiary, + ), ), const SizedBox(height: 4), // 년도 그리드 (ExpandablePageView로 스와이프 애니메이션) @@ -969,7 +1096,11 @@ class _ScheduleViewState extends ConsumerState // 월 라벨 const Text( '월', - style: TextStyle(fontFamily: 'Pretendard', fontSize: 12, color: AppColors.textTertiary), + style: TextStyle( + fontFamily: 'Pretendard', + fontSize: 12, + color: AppColors.textTertiary, + ), ), const SizedBox(height: 4), // 월 그리드 @@ -987,11 +1118,16 @@ class _ScheduleViewState extends ConsumerState itemBuilder: (context, index) { final month = index + 1; final isSelected = month == _calendarViewDate.month; - final isCurrentMonth = month == today.month && _calendarViewDate.year == today.year; + final isCurrentMonth = + month == today.month && _calendarViewDate.year == today.year; return GestureDetector( onTap: () { setState(() { - _calendarViewDate = DateTime(_calendarViewDate.year, month, 1); + _calendarViewDate = DateTime( + _calendarViewDate.year, + month, + 1, + ); _showYearMonthPicker = false; }); }, @@ -1008,12 +1144,14 @@ class _ScheduleViewState extends ConsumerState style: TextStyle( fontFamily: 'Pretendard', fontSize: 14, - fontWeight: isSelected || isCurrentMonth ? FontWeight.w600 : FontWeight.w400, + fontWeight: isSelected || isCurrentMonth + ? FontWeight.w600 + : FontWeight.w400, color: isSelected ? Colors.white : isCurrentMonth - ? AppColors.primary - : AppColors.textPrimary, + ? AppColors.primary + : AppColors.textPrimary, ), child: Text('$month월'), ), @@ -1093,12 +1231,14 @@ class _ScheduleViewState extends ConsumerState style: TextStyle( fontFamily: 'Pretendard', fontSize: 14, - fontWeight: isSelected || isCurrentYear ? FontWeight.w600 : FontWeight.w400, + fontWeight: isSelected || isCurrentYear + ? FontWeight.w600 + : FontWeight.w400, color: isSelected ? Colors.white : isCurrentYear - ? AppColors.primary - : AppColors.textPrimary, + ? AppColors.primary + : AppColors.textPrimary, ), child: Text('$year'), ), @@ -1109,7 +1249,10 @@ class _ScheduleViewState extends ConsumerState } /// 달력 그리드 - Widget _buildCalendarGrid(ScheduleState state, ScheduleController controller) { + Widget _buildCalendarGrid( + ScheduleState state, + ScheduleController controller, + ) { return Padding( padding: const EdgeInsets.all(16), child: Column( @@ -1117,7 +1260,9 @@ class _ScheduleViewState extends ConsumerState children: [ // 요일 헤더 Row( - children: ['일', '월', '화', '수', '목', '금', '토'].asMap().entries.map((entry) { + children: ['일', '월', '화', '수', '목', '금', '토'].asMap().entries.map(( + entry, + ) { final index = entry.key; final day = entry.value; return Expanded( @@ -1131,15 +1276,15 @@ class _ScheduleViewState extends ConsumerState color: index == 0 ? Colors.red.shade400 : index == 6 - ? Colors.blue.shade400 - : AppColors.textSecondary, + ? Colors.blue.shade400 + : AppColors.textSecondary, ), ), ), ); }).toList(), ), - const SizedBox(height: 4), + const SizedBox(height: 12), // 날짜 그리드 (ExpandablePageView로 높이 자동 조절) ExpandablePageView.builder( controller: _calendarPageController, @@ -1177,7 +1322,7 @@ class _ScheduleViewState extends ConsumerState ); }, ), - const SizedBox(height: 12), + const SizedBox(height: 16), // 오늘 버튼 GestureDetector( onTap: () { @@ -1219,6 +1364,7 @@ class _ScheduleViewState extends ConsumerState }) { return GridView.builder( shrinkWrap: true, + padding: EdgeInsets.zero, physics: const NeverScrollableScrollPhysics(), gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 7, @@ -1233,7 +1379,9 @@ class _ScheduleViewState extends ConsumerState final isSelected = controller.isSelected(date); final isToday = controller.isToday(date); final dayOfWeek = index % 7; - final daySchedules = isCurrentMonth ? state.getDaySchedules(date) : []; + final daySchedules = isCurrentMonth + ? state.getDaySchedules(date) + : []; return GestureDetector( onTap: () { @@ -1268,18 +1416,20 @@ class _ScheduleViewState extends ConsumerState style: TextStyle( fontFamily: 'Pretendard', fontSize: 14, - fontWeight: isSelected || isToday ? FontWeight.bold : FontWeight.w400, + fontWeight: isSelected || isToday + ? FontWeight.bold + : FontWeight.w400, color: !isCurrentMonth ? AppColors.textTertiary.withValues(alpha: 0.5) : isSelected - ? Colors.white - : isToday - ? AppColors.primary - : dayOfWeek == 0 - ? Colors.red.shade500 - : dayOfWeek == 6 - ? Colors.blue.shade500 - : AppColors.textPrimary, + ? Colors.white + : isToday + ? AppColors.primary + : dayOfWeek == 0 + ? Colors.red.shade500 + : dayOfWeek == 6 + ? Colors.blue.shade500 + : AppColors.textPrimary, ), ), ), @@ -1310,91 +1460,95 @@ class _ScheduleViewState extends ConsumerState } /// 날짜 선택기 빌드 - Widget _buildDateSelector(ScheduleState state, ScheduleController controller) { + Widget _buildDateSelector( + ScheduleState state, + ScheduleController controller, + ) { return Container( height: 80, color: Colors.white, child: ListView.builder( - controller: _dateScrollController, - scrollDirection: Axis.horizontal, - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8), - itemCount: state.daysInMonth.length, - itemBuilder: (context, index) { - final date = state.daysInMonth[index]; - final isSelected = controller.isSelected(date); - final isToday = controller.isToday(date); - final dayOfWeek = date.weekday; - final daySchedules = state.getDaySchedules(date); + controller: _dateScrollController, + scrollDirection: Axis.horizontal, + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8), + itemCount: state.daysInMonth.length, + itemBuilder: (context, index) { + final date = state.daysInMonth[index]; + final isSelected = controller.isSelected(date); + final isToday = controller.isToday(date); + final dayOfWeek = date.weekday; + final daySchedules = state.getDaySchedules(date); - return GestureDetector( - onTap: () => _onDateSelected(date), - child: Container( - width: 44, - margin: const EdgeInsets.symmetric(horizontal: 4), - decoration: BoxDecoration( - color: isSelected ? AppColors.primary : Colors.transparent, - borderRadius: BorderRadius.circular(12), - ), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - // 요일 - Text( - _getDayName(date), - style: TextStyle( - fontSize: 10, - fontWeight: FontWeight.w500, - color: isSelected - ? Colors.white.withValues(alpha: 0.8) - : dayOfWeek == 7 - ? Colors.red.shade400 - : dayOfWeek == 6 - ? Colors.blue.shade400 - : AppColors.textTertiary, - ), - ), - const SizedBox(height: 4), - // 날짜 - Text( - '${date.day}', - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w600, - color: isSelected - ? Colors.white - : isToday - ? AppColors.primary - : AppColors.textPrimary, - ), - ), - const SizedBox(height: 4), - // 일정 점 (최대 3개) - SizedBox( - height: 6, - child: !isSelected && daySchedules.isNotEmpty - ? Row( - mainAxisAlignment: MainAxisAlignment.center, - children: daySchedules.map((schedule) { - return Container( - width: 4, - height: 4, - margin: - const EdgeInsets.symmetric(horizontal: 1), - decoration: BoxDecoration( - color: parseColor(schedule.categoryColor), - shape: BoxShape.circle, - ), - ); - }).toList(), - ) - : const SizedBox.shrink(), - ), - ], - ), + return GestureDetector( + onTap: () => _onDateSelected(date), + child: Container( + width: 44, + margin: const EdgeInsets.symmetric(horizontal: 4), + decoration: BoxDecoration( + color: isSelected ? AppColors.primary : Colors.transparent, + borderRadius: BorderRadius.circular(12), ), - ); - }, - ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // 요일 + Text( + _getDayName(date), + style: TextStyle( + fontSize: 10, + fontWeight: FontWeight.w500, + color: isSelected + ? Colors.white.withValues(alpha: 0.8) + : dayOfWeek == 7 + ? Colors.red.shade400 + : dayOfWeek == 6 + ? Colors.blue.shade400 + : AppColors.textTertiary, + ), + ), + const SizedBox(height: 4), + // 날짜 + Text( + '${date.day}', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: isSelected + ? Colors.white + : isToday + ? AppColors.primary + : AppColors.textPrimary, + ), + ), + const SizedBox(height: 4), + // 일정 점 (최대 3개) + SizedBox( + height: 6, + child: !isSelected && daySchedules.isNotEmpty + ? Row( + mainAxisAlignment: MainAxisAlignment.center, + children: daySchedules.map((schedule) { + return Container( + width: 4, + height: 4, + margin: const EdgeInsets.symmetric( + horizontal: 1, + ), + decoration: BoxDecoration( + color: parseColor(schedule.categoryColor), + shape: BoxShape.circle, + ), + ); + }).toList(), + ) + : const SizedBox.shrink(), + ), + ], + ), + ), + ); + }, + ), ); } @@ -1410,10 +1564,7 @@ class _ScheduleViewState extends ConsumerState return Center( child: Text( '${state.selectedDate.month}월 ${state.selectedDate.day}일 일정이 없습니다', - style: const TextStyle( - fontSize: 14, - color: AppColors.textTertiary, - ), + style: const TextStyle(fontSize: 14, color: AppColors.textTertiary), ), ); } @@ -1426,7 +1577,8 @@ class _ScheduleViewState extends ConsumerState final schedule = state.selectedDateSchedules[index]; return Padding( padding: EdgeInsets.only( - bottom: index < state.selectedDateSchedules.length - 1 ? 12 : 0), + bottom: index < state.selectedDateSchedules.length - 1 ? 12 : 0, + ), child: AnimatedScheduleCard( key: ValueKey('${schedule.id}_${state.selectedDate.toString()}'), index: index, diff --git a/app/macos/Flutter/GeneratedPluginRegistrant.swift b/app/macos/Flutter/GeneratedPluginRegistrant.swift index 0dafe0c..743491d 100644 --- a/app/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/app/macos/Flutter/GeneratedPluginRegistrant.swift @@ -7,6 +7,7 @@ import Foundation import package_info_plus import path_provider_foundation +import shared_preferences_foundation import sqflite_darwin import url_launcher_macos import video_player_avfoundation @@ -15,6 +16,7 @@ import wakelock_plus func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) + SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin")) diff --git a/app/pubspec.lock b/app/pubspec.lock index 8806d09..de39110 100644 --- a/app/pubspec.lock +++ b/app/pubspec.lock @@ -664,6 +664,62 @@ packages: url: "https://pub.dev" source: hosted version: "0.28.0" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: "2939ae520c9024cb197fc20dee269cd8cdbf564c8b5746374ec6cacdc5169e64" + url: "https://pub.dev" + source: hosted + version: "2.5.4" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: "83af5c682796c0f7719c2bbf74792d113e40ae97981b8f266fa84574573556bc" + url: "https://pub.dev" + source: hosted + version: "2.4.18" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f" + url: "https://pub.dev" + source: hosted + version: "2.5.6" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 + url: "https://pub.dev" + source: hosted + version: "2.4.3" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" + url: "https://pub.dev" + source: hosted + version: "2.4.1" shelf: dependency: transitive description: diff --git a/app/pubspec.yaml b/app/pubspec.yaml index d7f7135..cd20f5f 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -50,6 +50,7 @@ dependencies: video_player: ^2.9.2 chewie: ^1.8.5 expandable_page_view: ^1.0.17 + shared_preferences: ^2.3.5 dev_dependencies: flutter_test: