import 'package:flutter/material.dart'; import 'package:flutter/foundation.dart' show kIsWeb, kDebugMode; import 'package:provider/provider.dart'; import '../providers/notification_provider.dart'; import 'dart:io'; import '../services/notification_service.dart'; // import '../widgets/glassmorphism_card.dart'; // import '../theme/app_colors.dart'; import '../widgets/common/snackbar/app_snackbar.dart'; import '../l10n/app_localizations.dart'; import '../providers/locale_provider.dart'; import 'package:permission_handler/permission_handler.dart' as permission; import '../services/sms_service.dart'; import '../providers/theme_provider.dart'; import '../theme/adaptive_theme.dart'; import '../widgets/common/layout/page_container.dart'; import '../theme/color_scheme_ext.dart'; import '../widgets/app_navigator.dart'; import '../theme/ui_constants.dart'; class SettingsScreen extends StatelessWidget { const SettingsScreen({super.key}); // 알림 시점 라디오 버튼 생성 헬퍼 메서드 Widget _buildReminderDayRadio(BuildContext context, NotificationProvider provider, int value, String label) { final isSelected = provider.reminderDays == value; return Expanded( child: InkWell( onTap: () => provider.setReminderDays(value), child: Container( margin: const EdgeInsets.symmetric(horizontal: 4), padding: const EdgeInsets.symmetric(vertical: 10), decoration: BoxDecoration( color: isSelected ? Theme.of(context).colorScheme.primary.withValues(alpha: 0.2) : Colors.transparent, borderRadius: BorderRadius.circular(8), border: Border.all( color: isSelected ? Theme.of(context).colorScheme.primary : Theme.of(context) .colorScheme .onSurfaceVariant .withValues(alpha: 0.5), width: isSelected ? 2 : 1, ), ), child: Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked, color: isSelected ? Theme.of(context).colorScheme.primary : Theme.of(context).colorScheme.onSurfaceVariant, size: 24, ), const SizedBox(height: 6), Text( label, style: TextStyle( fontSize: 14, fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, color: isSelected ? Theme.of(context).colorScheme.primary : Theme.of(context).colorScheme.onSurface, ), ), ], ), ), ), ); } @override Widget build(BuildContext context) { final loc = AppLocalizations.of(context); return Column( children: [ Expanded( child: PageContainer( padding: EdgeInsets.zero, child: ListView( padding: const EdgeInsets.fromLTRB( 16, UIConstants.pageTopPadding, 16, 0, ), children: [ // 테마 모드 설정 Card( margin: const EdgeInsets.fromLTRB(16, 0, 16, 8), elevation: 1, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), side: BorderSide( color: Theme.of(context) .colorScheme .outline .withValues(alpha: 0.5), ), ), child: Semantics( button: true, label: loc.paymentCardManagement, hint: loc.paymentCardManagementDescription, child: ListTile( leading: Icon( Icons.credit_card, color: Theme.of(context).colorScheme.onSurfaceVariant, ), title: Text( loc.paymentCardManagement, style: TextStyle( color: Theme.of(context).colorScheme.onSurface, ), ), subtitle: Text( loc.paymentCardManagementDescription, style: TextStyle( color: Theme.of(context).colorScheme.onSurfaceVariant, ), ), trailing: Icon(Icons.chevron_right_rounded, color: Theme.of(context).colorScheme.onSurfaceVariant), onTap: () => AppNavigator.toPaymentCardManagement(context), ), ), ), Card( margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 16), elevation: 1, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), side: BorderSide( color: Theme.of(context) .colorScheme .outline .withValues(alpha: 0.5), ), ), child: Padding( padding: const EdgeInsets.all(12), child: Consumer( builder: (context, themeProvider, child) { final mode = themeProvider.themeMode; final cs = Theme.of(context).colorScheme; final loc = AppLocalizations.of(context); Widget chip(AppThemeMode value, String label) { final selected = mode == value; return ChoiceChip( label: Text(label), selected: selected, onSelected: (_) => themeProvider.setThemeMode(value), labelStyle: TextStyle( color: selected ? cs.onPrimary : cs.onSurface, fontWeight: FontWeight.w600, ), selectedColor: cs.primary, backgroundColor: cs.surface, side: BorderSide( color: selected ? cs.primary : cs.outline.withValues(alpha: 0.6), ), ); } return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ ListTile( contentPadding: EdgeInsets.zero, leading: Icon(Icons.color_lens, color: cs.onSurfaceVariant), title: Text( loc.theme, style: TextStyle(color: cs.onSurface), ), ), const SizedBox(height: 8), Wrap( spacing: 8, runSpacing: 8, children: [ chip(AppThemeMode.system, loc.systemTheme), chip(AppThemeMode.light, loc.lightTheme), chip(AppThemeMode.dark, loc.darkTheme), chip(AppThemeMode.oled, loc.oledTheme), ], ), ], ); }, ), ), ), // 언어 설정 Card( margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 16), elevation: 1, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), side: BorderSide( color: Theme.of(context) .colorScheme .outline .withValues(alpha: 0.5), ), ), child: Padding( padding: const EdgeInsets.all(12), child: Consumer( builder: (context, localeProvider, child) { final loc = AppLocalizations.of(context); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ ListTile( contentPadding: EdgeInsets.zero, leading: Icon( Icons.language, color: Theme.of(context) .colorScheme .onSurfaceVariant, ), title: Text( loc.language, style: TextStyle( color: Theme.of(context).colorScheme.onSurface, ), ), ), DropdownButtonFormField( initialValue: localeProvider.locale.languageCode, isExpanded: true, decoration: InputDecoration( filled: true, fillColor: Theme.of(context).colorScheme.surface, contentPadding: const EdgeInsets.symmetric( horizontal: 16, vertical: 14), border: OutlineInputBorder( borderRadius: BorderRadius.circular(16), borderSide: BorderSide.none, ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(16), borderSide: BorderSide( color: Theme.of(context) .colorScheme .outline .withValues(alpha: 0.6), width: 1, ), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(16), borderSide: BorderSide( color: Theme.of(context).colorScheme.primary, width: 2, ), ), ), items: [ DropdownMenuItem( value: 'ko', child: Text(loc.korean)), DropdownMenuItem( value: 'en', child: Text(loc.english)), DropdownMenuItem( value: 'ja', child: Text(loc.japanese)), DropdownMenuItem( value: 'zh', child: Text(loc.chinese)), ], onChanged: (val) { if (val != null) localeProvider.setLocale(val); }, ), ], ); }, ), ), ), // 앱 잠금 설정 UI 숨김 // Card( // margin: const EdgeInsets.all(16), // child: Consumer( // builder: (context, provider, child) { // return SwitchListTile( // title: const Text('앱 잠금'), // subtitle: const Text('생체 인증으로 앱 잠금'), // value: provider.isEnabled, // onChanged: (value) async { // if (value) { // final isAuthenticated = await provider.authenticate(); // if (isAuthenticated) { // provider.enable(); // } // } else { // provider.disable(); // } // }, // ); // }, // ), // ), // 알림 설정 Card( margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 16), elevation: 1, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), side: BorderSide( color: Theme.of(context) .colorScheme .outline .withValues(alpha: 0.5), ), ), child: Padding( padding: const EdgeInsets.all(8), child: Consumer( builder: (context, provider, child) { return Column( children: [ // Android 12+ 정확 알람 권한 (알람 및 리마인더) if (!kIsWeb && Platform.isAndroid) FutureBuilder( future: NotificationService .canScheduleExactAlarms(), builder: (context, snap) { final can = snap.data ?? true; if (can) return const SizedBox.shrink(); return ListTile( leading: Icon(Icons.alarm, color: Theme.of(context) .colorScheme .onSurfaceVariant), title: Text( loc.exactAlarmPermission, style: TextStyle( color: Theme.of(context) .colorScheme .onSurface), ), subtitle: Text( loc.exactAlarmPermissionDesc, style: TextStyle( color: Theme.of(context) .colorScheme .onSurfaceVariant), ), trailing: ElevatedButton( onPressed: () async { final ok = await NotificationService .requestExactAlarmsPermission(); // 사용자가 설정 화면에서 허용 후 돌아오면 true가 될 수 있음 final recheck = await NotificationService .canScheduleExactAlarms(); if (context.mounted) { if (ok || recheck) { AppSnackBar.showSuccess( context: context, message: loc.permissionGranted, ); } else { AppSnackBar.showInfo( context: context, message: loc.allowAlarmsInSettings, ); } (context as Element).markNeedsBuild(); } }, child: Text(loc.requestPermission), ), ); }, ), FutureBuilder( future: permission.Permission.notification.status, builder: (context, snapshot) { final isLoading = snapshot.connectionState == ConnectionState.waiting; final status = snapshot.data; final hasPermission = status?.isGranted ?? false; final isPermanentlyDenied = status?.isPermanentlyDenied ?? false; return ListTile( title: Text( AppLocalizations.of(context) .notificationPermission, style: TextStyle( color: Theme.of(context) .colorScheme .onSurface), ), subtitle: !hasPermission ? Text( isPermanentlyDenied ? AppLocalizations.of(context) .permanentlyDeniedMessage : AppLocalizations.of(context) .notificationPermissionDesc, style: TextStyle( color: Theme.of(context) .colorScheme .onSurfaceVariant), ) : null, trailing: isLoading ? const SizedBox( width: 20, height: 20, child: CircularProgressIndicator( strokeWidth: 2), ) : hasPermission ? Padding( padding: const EdgeInsets.symmetric( horizontal: 8.0), child: Icon( Icons.check_circle, color: Theme.of(context) .colorScheme .success, ), ) : isPermanentlyDenied ? TextButton( onPressed: () async { await permission .openAppSettings(); }, child: Text( AppLocalizations.of( context) .openSettings), ) : ElevatedButton( onPressed: () async { final granted = await NotificationService .requestPermission(); if (granted) { await provider .setEnabled(true); } else { if (!context.mounted) { return; } AppSnackBar.showError( context: context, message: AppLocalizations .of(context) .notificationPermissionDenied, ); } if (context.mounted) { (context as Element) .markNeedsBuild(); } }, child: Text( AppLocalizations.of( context) .requestPermission), ), ); }, ), const Divider(), // 결제 예정 알림 기본 스위치 SwitchListTile( title: Text( AppLocalizations.of(context) .paymentNotification, style: TextStyle( color: Theme.of(context) .colorScheme .onSurface), ), subtitle: Text( AppLocalizations.of(context) .paymentNotificationDesc, style: TextStyle( color: Theme.of(context) .colorScheme .onSurfaceVariant), ), value: provider.isPaymentEnabled, onChanged: (value) { provider.setPaymentEnabled(value); }, ), // 알림 세부 설정 (알림 활성화된 경우에만 표시) AnimatedSize( duration: const Duration(milliseconds: 300), curve: Curves.easeInOut, child: provider.isPaymentEnabled ? Padding( padding: const EdgeInsets.only( left: 16.0, right: 16.0, bottom: 8.0), child: Card( elevation: 0, color: Theme.of(context) .colorScheme .surfaceContainerHighest .withValues(alpha: 0.3), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), side: BorderSide( color: Theme.of(context) .colorScheme .outline .withValues(alpha: 0.3), ), ), child: Padding( padding: const EdgeInsets.all(12.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 알림 시점 선택 (1일전, 2일전, 3일전) Text( AppLocalizations.of(context) .notificationTiming, style: const TextStyle( fontWeight: FontWeight.bold)), const SizedBox(height: 8), Padding( padding: const EdgeInsets.symmetric( vertical: 8.0), child: Row( mainAxisAlignment: MainAxisAlignment .spaceEvenly, children: [ _buildReminderDayRadio( context, provider, 1, AppLocalizations.of( context) .oneDayBefore), _buildReminderDayRadio( context, provider, 2, AppLocalizations.of( context) .twoDaysBefore), _buildReminderDayRadio( context, provider, 3, AppLocalizations.of( context) .threeDaysBefore), ], ), ), const SizedBox(height: 16), // 알림 시간 선택 Text( AppLocalizations.of(context) .notificationTime, style: const TextStyle( fontWeight: FontWeight.bold)), const SizedBox(height: 12), InkWell( onTap: () async { final TimeOfDay? picked = await showTimePicker( context: context, initialTime: TimeOfDay( hour: provider .reminderHour, minute: provider .reminderMinute), ); if (picked != null) { provider.setReminderTime( picked.hour, picked.minute); } }, child: Container( padding: const EdgeInsets .symmetric( vertical: 12, horizontal: 16), decoration: BoxDecoration( border: Border.all( color: Theme.of(context) .colorScheme .outline .withValues( alpha: 0.5), ), borderRadius: BorderRadius.circular( 8), ), child: Row( children: [ Expanded( child: Row( children: [ Icon( Icons.access_time, color: Theme.of( context) .colorScheme .primary, size: 22, ), const SizedBox( width: 12), Text( '${provider.reminderHour.toString().padLeft(2, '0')}:${provider.reminderMinute.toString().padLeft(2, '0')}', style: TextStyle( fontSize: 16, fontWeight: FontWeight .bold, color: Theme.of( context) .colorScheme .onSurface, ), ), ], ), ), Icon( Icons.arrow_forward_ios, size: 16, color: Theme.of(context) .colorScheme .outline, ), ], ), ), ), // 반복 알림 스위치 (2일전, 3일전 선택 시에만 활성화) if (provider.reminderDays >= 2) Padding( padding: const EdgeInsets.only( top: 16.0), child: Container( padding: const EdgeInsets .symmetric( vertical: 4, horizontal: 4), decoration: BoxDecoration( color: Theme.of(context) .colorScheme .surfaceContainerHighest .withValues( alpha: 0.3), borderRadius: BorderRadius.circular( 8), ), child: SwitchListTile( contentPadding: const EdgeInsets .symmetric( horizontal: 12), title: Text( AppLocalizations.of( context) .dailyReminder), subtitle: Text( provider.isDailyReminderEnabled ? AppLocalizations .of(context) .dailyReminderEnabled : AppLocalizations .of(context) .dailyReminderDisabledWithDays( provider .reminderDays), style: TextStyle( color: Theme.of( context) .colorScheme .onSurfaceVariant), ), value: provider .isDailyReminderEnabled, onChanged: (value) { provider .setDailyReminderEnabled( value); }, ), ), ), if (kDebugMode) Padding( padding: const EdgeInsets.only( top: 16.0), child: SizedBox( width: double.infinity, child: OutlinedButton.icon( icon: const Icon(Icons .notifications_active), label: Text( loc.testNotification), onPressed: () { NotificationService .showTestPaymentNotification(); }, ), ), ), ], ), ), ), ) : const SizedBox.shrink(), ), // 디버그 전용: 결제 알림 테스트 버튼 (숨김) // 미사용 서비스 알림 기능 비활성화 // const Divider(), // SwitchListTile( // title: const Text('미사용 서비스 알림'), // subtitle: const Text('2개월 이상 미사용 시 알림'), // value: provider.isUnusedServiceNotificationEnabled, // onChanged: (value) { // provider.setUnusedServiceNotificationEnabled(value); // }, // ), ], ); }, ), ), ), // SMS 권한 설정 if (!kIsWeb && Platform.isAndroid) Card( margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), elevation: 1, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12)), child: FutureBuilder( future: permission.Permission.sms.status, builder: (context, snapshot) { final isLoading = snapshot.connectionState == ConnectionState.waiting; final status = snapshot.data; final hasPermission = status?.isGranted ?? false; final isPermanentlyDenied = status?.isPermanentlyDenied ?? false; return ListTile( contentPadding: const EdgeInsets.all(8), leading: Icon( Icons.sms, color: Theme.of(context).colorScheme.onSurfaceVariant, ), title: Text( AppLocalizations.of(context).smsPermissionLabel, style: TextStyle( color: Theme.of(context).colorScheme.onSurface), ), subtitle: !hasPermission ? Text( isPermanentlyDenied ? AppLocalizations.of(context) .permanentlyDeniedMessage : AppLocalizations.of(context) .smsPermissionRequired, style: TextStyle( color: Theme.of(context) .colorScheme .onSurfaceVariant), ) : null, trailing: isLoading ? const SizedBox( width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2), ) : hasPermission ? Padding( padding: const EdgeInsets.symmetric( horizontal: 8.0), child: Icon(Icons.check_circle, color: Theme.of(context) .colorScheme .success), ) : isPermanentlyDenied ? TextButton( onPressed: () async { await permission.openAppSettings(); }, child: Text( AppLocalizations.of(context) .openSettings), ) : ElevatedButton( onPressed: () async { final granted = await SMSService .requestSMSPermission(); if (!granted) { final newStatus = await permission .Permission.sms.status; if (newStatus .isPermanentlyDenied) { await permission .openAppSettings(); } } if (context.mounted) { (context as Element) .markNeedsBuild(); } }, child: Text( AppLocalizations.of(context) .requestPermission), ), ); }, ), ), // 앱 정보 Card( margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 16), elevation: 1, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), side: BorderSide( color: Theme.of(context) .colorScheme .outline .withValues(alpha: 0.5), ), ), child: ListTile( contentPadding: const EdgeInsets.all(8), title: Text( AppLocalizations.of(context).appInfo, style: TextStyle( color: Theme.of(context).colorScheme.onSurface), ), subtitle: Text( '${AppLocalizations.of(context).version} 1.0.0', style: TextStyle( color: Theme.of(context).colorScheme.onSurfaceVariant), ), leading: Icon(Icons.info, color: Theme.of(context).colorScheme.onSurfaceVariant), onTap: null, // onTap: () async { // // 항상 앱 내 About 다이얼로그를 우선 표시 (현재 미사용) // showAboutDialog( // context: context, // applicationName: AppLocalizations.of(context).appTitle, // applicationVersion: '1.0.0', // applicationIcon: const FlutterLogo(size: 50), // children: [ // Text(AppLocalizations.of(context).appDescription), // const SizedBox(height: 8), // Text( // '${AppLocalizations.of(context).developer}: Julian Sul'), // const SizedBox(height: 12), // Builder(builder: (ctx) { // return TextButton.icon( // icon: const Icon(Icons.open_in_new), // label: Text(AppLocalizations.of(ctx).openStore), // onPressed: () async { // try { // if (Platform.isAndroid) { // // 우선 Play 스토어 앱 시도 // const pkg = // 'com.naturebridgeai.digitalrentmanager'; // final marketUri = // Uri.parse('market://details?id=$pkg'); // final webUri = Uri.parse( // 'https://play.google.com/store/apps/details?id=$pkg'); // final ok = await launchUrl(marketUri, // mode: LaunchMode.externalApplication); // if (!ok) { // await launchUrl(webUri, // mode: LaunchMode.externalApplication); // } // } else if (Platform.isIOS) { // final uri = Uri.parse( // 'https://apps.apple.com/app/id123456789'); // await launchUrl(uri, // mode: LaunchMode.externalApplication); // } // } catch (e) { // if (ctx.mounted) { // AppSnackBar.showError( // context: ctx, // message: AppLocalizations.of(ctx) // .cannotOpenStore, // ); // } // } // }, // ); // }), // ], // ); // }, ), ), // FloatingNavigationBar를 위한 충분한 하단 여백 SizedBox( height: 120 + MediaQuery.of(context).padding.bottom, ), ], ), ), ), ], ); } }