LunchPick(오늘 뭐 먹Z?) Flutter 앱의 초기 구현입니다. 주요 기능: - 네이버 지도 연동 맛집 추가 - 랜덤 메뉴 추천 시스템 - 날씨 기반 거리 조정 - 방문 기록 관리 - Bluetooth 맛집 공유 - 다크모드 지원 기술 스택: - Flutter 3.8.1+ - Riverpod 상태 관리 - Hive 로컬 DB - Clean Architecture 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
264 lines
8.4 KiB
Dart
264 lines
8.4 KiB
Dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:lunchpick/domain/repositories/settings_repository.dart';
|
|
import 'package:lunchpick/domain/entities/user_settings.dart';
|
|
import 'package:lunchpick/presentation/providers/di_providers.dart';
|
|
|
|
/// 재방문 금지 일수 Provider
|
|
final daysToExcludeProvider = FutureProvider<int>((ref) async {
|
|
final repository = ref.watch(settingsRepositoryProvider);
|
|
return repository.getDaysToExclude();
|
|
});
|
|
|
|
/// 우천시 최대 거리 Provider
|
|
final maxDistanceRainyProvider = FutureProvider<int>((ref) async {
|
|
final repository = ref.watch(settingsRepositoryProvider);
|
|
return repository.getMaxDistanceRainy();
|
|
});
|
|
|
|
/// 평상시 최대 거리 Provider
|
|
final maxDistanceNormalProvider = FutureProvider<int>((ref) async {
|
|
final repository = ref.watch(settingsRepositoryProvider);
|
|
return repository.getMaxDistanceNormal();
|
|
});
|
|
|
|
/// 알림 지연 시간 Provider
|
|
final notificationDelayMinutesProvider = FutureProvider<int>((ref) async {
|
|
final repository = ref.watch(settingsRepositoryProvider);
|
|
return repository.getNotificationDelayMinutes();
|
|
});
|
|
|
|
/// 알림 활성화 여부 Provider
|
|
final notificationEnabledProvider = FutureProvider<bool>((ref) async {
|
|
final repository = ref.watch(settingsRepositoryProvider);
|
|
return repository.isNotificationEnabled();
|
|
});
|
|
|
|
/// 다크모드 활성화 여부 Provider
|
|
final darkModeEnabledProvider = FutureProvider<bool>((ref) async {
|
|
final repository = ref.watch(settingsRepositoryProvider);
|
|
return repository.isDarkModeEnabled();
|
|
});
|
|
|
|
/// 첫 실행 여부 Provider
|
|
final isFirstRunProvider = FutureProvider<bool>((ref) async {
|
|
final repository = ref.watch(settingsRepositoryProvider);
|
|
return repository.isFirstRun();
|
|
});
|
|
|
|
/// 설정 스트림 Provider
|
|
final settingsStreamProvider = StreamProvider<Map<String, dynamic>>((ref) {
|
|
final repository = ref.watch(settingsRepositoryProvider);
|
|
return repository.watchSettings();
|
|
});
|
|
|
|
/// UserSettings Provider
|
|
final userSettingsProvider = FutureProvider<UserSettings>((ref) async {
|
|
final repository = ref.watch(settingsRepositoryProvider);
|
|
return repository.getUserSettings();
|
|
});
|
|
|
|
/// UserSettings 스트림 Provider
|
|
final userSettingsStreamProvider = StreamProvider<UserSettings>((ref) {
|
|
final repository = ref.watch(settingsRepositoryProvider);
|
|
return repository.watchUserSettings();
|
|
});
|
|
|
|
/// 설정 관리 StateNotifier
|
|
class SettingsNotifier extends StateNotifier<AsyncValue<void>> {
|
|
final SettingsRepository _repository;
|
|
|
|
SettingsNotifier(this._repository) : super(const AsyncValue.data(null));
|
|
|
|
/// 재방문 금지 일수 설정
|
|
Future<void> setDaysToExclude(int days) async {
|
|
state = const AsyncValue.loading();
|
|
try {
|
|
await _repository.setDaysToExclude(days);
|
|
state = const AsyncValue.data(null);
|
|
} catch (e, stack) {
|
|
state = AsyncValue.error(e, stack);
|
|
}
|
|
}
|
|
|
|
/// 우천시 최대 거리 설정
|
|
Future<void> setMaxDistanceRainy(int meters) async {
|
|
state = const AsyncValue.loading();
|
|
try {
|
|
await _repository.setMaxDistanceRainy(meters);
|
|
state = const AsyncValue.data(null);
|
|
} catch (e, stack) {
|
|
state = AsyncValue.error(e, stack);
|
|
}
|
|
}
|
|
|
|
/// 평상시 최대 거리 설정
|
|
Future<void> setMaxDistanceNormal(int meters) async {
|
|
state = const AsyncValue.loading();
|
|
try {
|
|
await _repository.setMaxDistanceNormal(meters);
|
|
state = const AsyncValue.data(null);
|
|
} catch (e, stack) {
|
|
state = AsyncValue.error(e, stack);
|
|
}
|
|
}
|
|
|
|
/// 알림 지연 시간 설정
|
|
Future<void> setNotificationDelayMinutes(int minutes) async {
|
|
state = const AsyncValue.loading();
|
|
try {
|
|
await _repository.setNotificationDelayMinutes(minutes);
|
|
state = const AsyncValue.data(null);
|
|
} catch (e, stack) {
|
|
state = AsyncValue.error(e, stack);
|
|
}
|
|
}
|
|
|
|
/// 알림 활성화 설정
|
|
Future<void> setNotificationEnabled(bool enabled) async {
|
|
state = const AsyncValue.loading();
|
|
try {
|
|
await _repository.setNotificationEnabled(enabled);
|
|
state = const AsyncValue.data(null);
|
|
} catch (e, stack) {
|
|
state = AsyncValue.error(e, stack);
|
|
}
|
|
}
|
|
|
|
/// 다크모드 설정
|
|
Future<void> setDarkModeEnabled(bool enabled) async {
|
|
state = const AsyncValue.loading();
|
|
try {
|
|
await _repository.setDarkModeEnabled(enabled);
|
|
state = const AsyncValue.data(null);
|
|
} catch (e, stack) {
|
|
state = AsyncValue.error(e, stack);
|
|
}
|
|
}
|
|
|
|
/// 첫 실행 상태 업데이트
|
|
Future<void> setFirstRun(bool isFirst) async {
|
|
state = const AsyncValue.loading();
|
|
try {
|
|
await _repository.setFirstRun(isFirst);
|
|
state = const AsyncValue.data(null);
|
|
} catch (e, stack) {
|
|
state = AsyncValue.error(e, stack);
|
|
}
|
|
}
|
|
|
|
/// 설정 초기화
|
|
Future<void> resetSettings() async {
|
|
state = const AsyncValue.loading();
|
|
try {
|
|
await _repository.resetSettings();
|
|
state = const AsyncValue.data(null);
|
|
} catch (e, stack) {
|
|
state = AsyncValue.error(e, stack);
|
|
}
|
|
}
|
|
|
|
/// UserSettings 업데이트
|
|
Future<void> updateUserSettings(UserSettings settings) async {
|
|
state = const AsyncValue.loading();
|
|
try {
|
|
await _repository.updateUserSettings(settings);
|
|
state = const AsyncValue.data(null);
|
|
} catch (e, stack) {
|
|
state = AsyncValue.error(e, stack);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// SettingsNotifier Provider
|
|
final settingsNotifierProvider = StateNotifierProvider<SettingsNotifier, AsyncValue<void>>((ref) {
|
|
final repository = ref.watch(settingsRepositoryProvider);
|
|
return SettingsNotifier(repository);
|
|
});
|
|
|
|
/// 설정 프리셋
|
|
enum SettingsPreset {
|
|
normal(
|
|
name: '일반 모드',
|
|
daysToExclude: 7,
|
|
maxDistanceNormal: 1000,
|
|
maxDistanceRainy: 500,
|
|
),
|
|
economic(
|
|
name: '절약 모드',
|
|
daysToExclude: 3,
|
|
maxDistanceNormal: 500,
|
|
maxDistanceRainy: 300,
|
|
),
|
|
convenience(
|
|
name: '편의 모드',
|
|
daysToExclude: 14,
|
|
maxDistanceNormal: 2000,
|
|
maxDistanceRainy: 1000,
|
|
);
|
|
|
|
final String name;
|
|
final int daysToExclude;
|
|
final int maxDistanceNormal;
|
|
final int maxDistanceRainy;
|
|
|
|
const SettingsPreset({
|
|
required this.name,
|
|
required this.daysToExclude,
|
|
required this.maxDistanceNormal,
|
|
required this.maxDistanceRainy,
|
|
});
|
|
}
|
|
|
|
/// 프리셋 적용 Provider
|
|
final applyPresetProvider = Provider.family<Future<void>, SettingsPreset>((ref, preset) async {
|
|
final notifier = ref.read(settingsNotifierProvider.notifier);
|
|
|
|
await notifier.setDaysToExclude(preset.daysToExclude);
|
|
await notifier.setMaxDistanceNormal(preset.maxDistanceNormal);
|
|
await notifier.setMaxDistanceRainy(preset.maxDistanceRainy);
|
|
});
|
|
|
|
/// 현재 위치 Provider
|
|
final currentLocationProvider = StateProvider<({double latitude, double longitude})?>((ref) => null);
|
|
|
|
/// 선호 카테고리 Provider
|
|
final preferredCategoriesProvider = StateProvider<List<String>>((ref) => []);
|
|
|
|
/// 제외 카테고리 Provider
|
|
final excludedCategoriesProvider = StateProvider<List<String>>((ref) => []);
|
|
|
|
/// 언어 설정 Provider
|
|
final languageProvider = StateProvider<String>((ref) => 'ko');
|
|
|
|
/// 위치 권한 상태 Provider
|
|
final locationPermissionProvider = StateProvider<bool>((ref) => false);
|
|
|
|
/// 알림 권한 상태 Provider
|
|
final notificationPermissionProvider = StateProvider<bool>((ref) => false);
|
|
|
|
/// 모든 설정 상태를 통합한 Provider
|
|
final allSettingsProvider = Provider<Map<String, dynamic>>((ref) {
|
|
final daysToExclude = ref.watch(daysToExcludeProvider).value ?? 7;
|
|
final maxDistanceRainy = ref.watch(maxDistanceRainyProvider).value ?? 500;
|
|
final maxDistanceNormal = ref.watch(maxDistanceNormalProvider).value ?? 1000;
|
|
final notificationDelay = ref.watch(notificationDelayMinutesProvider).value ?? 90;
|
|
final notificationEnabled = ref.watch(notificationEnabledProvider).value ?? false;
|
|
final darkMode = ref.watch(darkModeEnabledProvider).value ?? false;
|
|
final currentLocation = ref.watch(currentLocationProvider);
|
|
final preferredCategories = ref.watch(preferredCategoriesProvider);
|
|
final excludedCategories = ref.watch(excludedCategoriesProvider);
|
|
final language = ref.watch(languageProvider);
|
|
|
|
return {
|
|
'daysToExclude': daysToExclude,
|
|
'maxDistanceRainy': maxDistanceRainy,
|
|
'maxDistanceNormal': maxDistanceNormal,
|
|
'notificationDelayMinutes': notificationDelay,
|
|
'notificationEnabled': notificationEnabled,
|
|
'darkModeEnabled': darkMode,
|
|
'currentLocation': currentLocation,
|
|
'preferredCategories': preferredCategories,
|
|
'excludedCategories': excludedCategories,
|
|
'language': language,
|
|
};
|
|
}); |