import 'package:flutter/material.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:adaptive_theme/adaptive_theme.dart'; import 'package:permission_handler/permission_handler.dart'; import '../../../core/constants/app_colors.dart'; import '../../../core/constants/app_typography.dart'; import '../../providers/settings_provider.dart'; import '../../providers/notification_provider.dart'; class SettingsScreen extends ConsumerStatefulWidget { const SettingsScreen({super.key}); @override ConsumerState createState() => _SettingsScreenState(); } class _SettingsScreenState extends ConsumerState { int _daysToExclude = 7; int _notificationMinutes = 90; bool _notificationEnabled = true; @override void initState() { super.initState(); _loadSettings(); } Future _loadSettings() async { final daysToExclude = await ref.read(daysToExcludeProvider.future); final notificationMinutes = await ref.read( notificationDelayMinutesProvider.future, ); final notificationEnabled = await ref.read( notificationEnabledProvider.future, ); if (mounted) { setState(() { _daysToExclude = daysToExclude; _notificationMinutes = notificationMinutes; _notificationEnabled = notificationEnabled; }); } } @override Widget build(BuildContext context) { final isDark = Theme.of(context).brightness == Brightness.dark; return Scaffold( backgroundColor: isDark ? AppColors.darkBackground : AppColors.lightBackground, appBar: AppBar( title: const Text('설정'), backgroundColor: isDark ? AppColors.darkPrimary : AppColors.lightPrimary, foregroundColor: Colors.white, elevation: 0, ), body: ListView( children: [ // 추천 설정 _buildSection('추천 설정', [ Card( margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), color: isDark ? AppColors.darkSurface : AppColors.lightSurface, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), child: ListTile( title: const Text('중복 방문 제외 기간'), subtitle: Text('$_daysToExclude일 이내 방문한 곳은 추천에서 제외'), trailing: FittedBox( fit: BoxFit.scaleDown, child: Row( mainAxisSize: MainAxisSize.min, children: [ IconButton( icon: const Icon(Icons.remove_circle_outline), onPressed: _daysToExclude > 1 ? () async { setState(() => _daysToExclude--); await ref .read(settingsNotifierProvider.notifier) .setDaysToExclude(_daysToExclude); } : null, color: AppColors.lightPrimary, ), Container( padding: const EdgeInsets.symmetric( horizontal: 12, vertical: 4, ), decoration: BoxDecoration( color: AppColors.lightPrimary.withOpacity(0.1), borderRadius: BorderRadius.circular(8), ), child: Text( '$_daysToExclude일', style: const TextStyle( fontWeight: FontWeight.bold, color: AppColors.lightPrimary, ), ), ), IconButton( icon: const Icon(Icons.add_circle_outline), onPressed: () async { setState(() => _daysToExclude++); await ref .read(settingsNotifierProvider.notifier) .setDaysToExclude(_daysToExclude); }, color: AppColors.lightPrimary, ), ], ), ), ), ), ], isDark), // 권한 설정 _buildSection('권한 관리', [ FutureBuilder( future: Permission.location.status, builder: (context, snapshot) { final status = snapshot.data; final isGranted = status?.isGranted ?? false; return _buildPermissionTile( icon: Icons.location_on, title: '위치 권한', subtitle: '주변 맛집 거리 계산에 필요', isGranted: isGranted, onRequest: _requestLocationPermission, isDark: isDark, ); }, ), if (!kIsWeb) FutureBuilder( future: Permission.bluetooth.status, builder: (context, snapshot) { final status = snapshot.data; final isGranted = status?.isGranted ?? false; return _buildPermissionTile( icon: Icons.bluetooth, title: '블루투스 권한', subtitle: '맛집 리스트 공유에 필요', isGranted: isGranted, onRequest: _requestBluetoothPermission, isDark: isDark, ); }, ), FutureBuilder( future: Permission.notification.status, builder: (context, snapshot) { final status = snapshot.data; final isGranted = status?.isGranted ?? false; return _buildPermissionTile( icon: Icons.notifications, title: '알림 권한', subtitle: '방문 확인 알림에 필요', isGranted: isGranted, onRequest: _requestNotificationPermission, isDark: isDark, ); }, ), if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) FutureBuilder( future: ref .read(notificationServiceProvider) .canScheduleExactAlarms(), builder: (context, snapshot) { final canExact = snapshot.data; // 권한이 이미 허용된 경우 UI 생략 if (canExact == true) { return const SizedBox.shrink(); } return _buildPermissionTile( icon: Icons.alarm, title: '정확 알람 권한', subtitle: '정확한 예약 알림을 위해 필요합니다', isGranted: canExact ?? false, onRequest: _requestExactAlarmPermission, isDark: isDark, ); }, ), ], isDark), // 알림 설정 _buildSection('알림 설정', [ Card( margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), color: isDark ? AppColors.darkSurface : AppColors.lightSurface, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), child: SwitchListTile( title: const Text('방문 확인 알림'), subtitle: const Text('맛집 방문 후 확인 알림을 받습니다'), value: _notificationEnabled, onChanged: (value) async { setState(() => _notificationEnabled = value); await ref .read(settingsNotifierProvider.notifier) .setNotificationEnabled(value); }, activeColor: AppColors.lightPrimary, ), ), Card( margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), color: isDark ? AppColors.darkSurface : AppColors.lightSurface, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), child: ListTile( enabled: _notificationEnabled, title: const Text('방문 확인 알림 시간'), subtitle: Text('추천 후 $_notificationMinutes분 뒤 알림'), trailing: FittedBox( fit: BoxFit.scaleDown, child: Row( mainAxisSize: MainAxisSize.min, children: [ IconButton( icon: const Icon(Icons.remove_circle_outline), onPressed: _notificationEnabled && _notificationMinutes > 60 ? () async { setState(() => _notificationMinutes -= 30); await ref .read(settingsNotifierProvider.notifier) .setNotificationDelayMinutes( _notificationMinutes, ); } : null, color: AppColors.lightPrimary, ), Container( padding: const EdgeInsets.symmetric( horizontal: 12, vertical: 4, ), decoration: BoxDecoration( color: AppColors.lightPrimary.withOpacity(0.1), borderRadius: BorderRadius.circular(8), ), child: Text( '${_notificationMinutes ~/ 60}시간 ${_notificationMinutes % 60}분', style: TextStyle( fontWeight: FontWeight.bold, color: _notificationEnabled ? AppColors.lightPrimary : Colors.grey, ), ), ), IconButton( icon: const Icon(Icons.add_circle_outline), onPressed: _notificationEnabled && _notificationMinutes < 360 ? () async { setState(() => _notificationMinutes += 30); await ref .read(settingsNotifierProvider.notifier) .setNotificationDelayMinutes( _notificationMinutes, ); } : null, color: AppColors.lightPrimary, ), ], ), ), ), ), ], isDark), // 테마 설정 _buildSection('테마', [ Card( margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), color: isDark ? AppColors.darkSurface : AppColors.lightSurface, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), child: ListTile( leading: Icon( isDark ? Icons.dark_mode : Icons.light_mode, color: AppColors.lightPrimary, ), title: const Text('테마 설정'), subtitle: Text(isDark ? '다크 모드' : '라이트 모드'), trailing: Switch( value: isDark, onChanged: (value) { if (value) { AdaptiveTheme.of(context).setDark(); } else { AdaptiveTheme.of(context).setLight(); } }, activeColor: AppColors.lightPrimary, ), ), ), ], isDark), // 앱 정보 _buildSection('앱 정보', [ Card( margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), color: isDark ? AppColors.darkSurface : AppColors.lightSurface, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), child: Column( children: [ const ListTile( leading: Icon( Icons.info_outline, color: AppColors.lightPrimary, ), title: Text('버전'), subtitle: Text('1.0.0'), ), ], ), ), ], isDark), const SizedBox(height: 24), ], ), ); } Widget _buildSection(String title, List children, bool isDark) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: const EdgeInsets.fromLTRB(20, 20, 20, 8), child: Text( title, style: AppTypography.body2(isDark).copyWith( color: AppColors.lightPrimary, fontWeight: FontWeight.w600, ), ), ), ...children, ], ); } Widget _buildPermissionTile({ required IconData icon, required String title, required String subtitle, required bool isGranted, required VoidCallback onRequest, required bool isDark, }) { return Card( margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), color: isDark ? AppColors.darkSurface : AppColors.lightSurface, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), child: ListTile( leading: Icon(icon, color: isGranted ? Colors.green : Colors.grey), title: Text(title), subtitle: Text(subtitle), trailing: isGranted ? const Icon(Icons.check_circle, color: Colors.green) : ElevatedButton( onPressed: onRequest, style: ElevatedButton.styleFrom( backgroundColor: AppColors.lightPrimary, foregroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), child: const Text('허용'), ), enabled: !isGranted, ), ); } Future _requestLocationPermission() async { final status = await Permission.location.request(); if (status.isGranted) { setState(() {}); } else if (status.isPermanentlyDenied) { _showPermissionDialog('위치'); } } Future _requestBluetoothPermission() async { final status = await Permission.bluetooth.request(); if (status.isGranted) { setState(() {}); } else if (status.isPermanentlyDenied) { _showPermissionDialog('블루투스'); } } Future _requestNotificationPermission() async { final status = await Permission.notification.request(); if (status.isGranted) { setState(() {}); } else if (status.isPermanentlyDenied) { _showPermissionDialog('알림'); } } Future _requestExactAlarmPermission() async { final notificationService = ref.read(notificationServiceProvider); final granted = await notificationService.requestExactAlarmsPermission(); if (!mounted) return; setState(() {}); if (!granted) { _showPermissionDialog('정확 알람'); } } void _showPermissionDialog(String permissionName) { final isDark = Theme.of(context).brightness == Brightness.dark; showDialog( context: context, builder: (context) => AlertDialog( backgroundColor: isDark ? AppColors.darkSurface : AppColors.lightSurface, title: const Text('권한 설정 필요'), content: Text('$permissionName 권한이 거부되었습니다. 설정에서 직접 권한을 허용해주세요.'), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('취소'), ), TextButton( onPressed: () { Navigator.pop(context); openAppSettings(); }, style: TextButton.styleFrom( foregroundColor: AppColors.lightPrimary, ), child: const Text('설정으로 이동'), ), ], ), ); } }