import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../main.dart'; import '../utils/app_lock_service.dart'; import '../utils/contact_helper.dart'; import '../utils/secure_messaging_service.dart'; import '../utils/app_theme.dart'; import 'splash_screen.dart'; enum _AppLockDialogMode { enable, change, disable } String? _validateAppLockPasscode(String value) { final normalized = value.trim(); if (normalized.isEmpty) { return 'رمز را وارد کنید.'; } if (!RegExp(r'^\d{4,8}$').hasMatch(normalized)) { return 'رمز باید ۴ تا ۸ رقم باشد.'; } return null; } class SettingsScreen extends StatefulWidget { const SettingsScreen({super.key}); @override State createState() => _SettingsScreenState(); } class _SettingsScreenState extends State { bool _isResetting = false; static const _platform = MethodChannel('com.example.saba_secure_sms/sms_role'); bool _isCurrentlyDefault = false; bool _isAppLockEnabled = false; bool _isAppLockLoading = true; @override void initState() { super.initState(); _checkDefaultStatus(); _loadAppLockStatus(); } Future _checkDefaultStatus() async { try { final bool isDefault = await _platform.invokeMethod('isDefaultSmsApp'); if (mounted) setState(() => _isCurrentlyDefault = isDefault); } catch (_) {} } Future _changeDefaultApp() async { try { if (_isCurrentlyDefault) { // If already default, open settings to allow unsetting await _platform.invokeMethod('openDefaultAppsSettings'); } else { // If not default, request it await _platform.invokeMethod('requestDefaultSmsApp'); } // Re-check after returning _checkDefaultStatus(); } catch (_) {} } Future _confirmReset() async { final confirmed = await showDialog( context: context, builder: (ctx) => AlertDialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), title: const Text('ریست کامل برنامه'), content: const Text( 'با این کار تمام کلیدها، گروه‌ها، پیام‌های ذخیره‌شده، کش بازگشایی و تنظیمات امنیتی حذف می‌شوند و برنامه مثل نصب تازه می‌شود. ادامه می‌دهید؟', ), actions: [ TextButton( onPressed: () => Navigator.pop(ctx, false), child: const Text('لغو'), ), ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: Colors.red, foregroundColor: Colors.white, ), onPressed: () => Navigator.pop(ctx, true), child: const Text('ریست کامل'), ), ], ), ); if (confirmed != true || !mounted) return; setState(() => _isResetting = true); await SecureMessagingService.instance.resetForFreshInstall(); await AppLockService.instance.clear(); ContactHelper.clearCache(); if (!mounted) return; Navigator.of(context).pushAndRemoveUntil( MaterialPageRoute(builder: (_) => const SplashScreen()), (route) => false, ); } Future _loadAppLockStatus() async { await AppLockService.instance.init(); if (!mounted) return; setState(() { _isAppLockEnabled = AppLockService.instance.isEnabled; _isAppLockLoading = false; }); } Future _showEnableAppLockDialog() async { final enabled = await showDialog( context: context, builder: (_) => const _AppLockDialog(mode: _AppLockDialogMode.enable), ); if (enabled == true) { await _loadAppLockStatus(); if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('رمز برنامه با موفقیت فعال شد.'), ), ); } } Future _showChangeAppLockDialog() async { final changed = await showDialog( context: context, builder: (_) => const _AppLockDialog(mode: _AppLockDialogMode.change), ); if (changed == true && mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('رمز برنامه با موفقیت تغییر کرد.'), ), ); } } Future _showDisableAppLockDialog() async { final disabled = await showDialog( context: context, builder: (_) => const _AppLockDialog(mode: _AppLockDialogMode.disable), ); if (disabled == true) { await _loadAppLockStatus(); if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('رمز برنامه غیرفعال شد.'), ), ); } } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: AppTheme.darkBg, appBar: AppBar( title: const Text('تنظیمات'), backgroundColor: Colors.transparent, elevation: 0, ), body: Stack( children: [ Positioned.fill(child: CustomPaint(painter: MeshBackgroundPainter())), SingleChildScrollView( child: Padding( padding: const EdgeInsets.all(16), child: Column( children: [ _buildSettingSection( icon: Icons.sms_rounded, iconColor: Colors.blueAccent, title: 'برنامه پیش‌فرض پیامک', content: _isCurrentlyDefault ? 'صبا در حال حاضر برنامه پیش‌فرض پیامک گوشی شماست.' : 'صبا برنامه پیش‌فرض نیست. برای استفاده از تمام امکانات امنیتی، صبا را به عنوان گزینه پیش‌فرض انتخاب کنید.', action: SizedBox( width: double.infinity, child: OutlinedButton.icon( onPressed: _changeDefaultApp, style: OutlinedButton.styleFrom( foregroundColor: _isCurrentlyDefault ? Colors.white70 : Colors.blueAccent, side: BorderSide( color: _isCurrentlyDefault ? Colors.white24 : Colors.blueAccent), padding: const EdgeInsets.symmetric(vertical: 14), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(14)), ), icon: Icon(_isCurrentlyDefault ? Icons.settings_applications : Icons.check_circle_outline), label: Text(_isCurrentlyDefault ? 'تغییر در تنظیمات سیستمی' : 'انتخاب به عنوان پیش‌فرض'), ), ), ), const SizedBox(height: 16), _buildSettingSection( icon: Icons.palette_rounded, iconColor: Colors.purpleAccent, title: 'رنگ‌بندی برنامه', content: 'رنگ مورد علاقه‌ی خود را برای محیط برنامه انتخاب کنید:', action: SizedBox( height: 55, child: ListView( scrollDirection: Axis.horizontal, children: [ _buildColorItem(const Color(0xFF7000FF), 'بنفش'), _buildColorItem(const Color(0xFF00D2FF), 'آبی'), _buildColorItem(const Color(0xFF00FFD1), 'فیروزه‌ای'), _buildColorItem(const Color(0xFF4CAF50), 'سبز'), _buildColorItem(const Color(0xFFFF9800), 'نارنجی'), _buildColorItem(const Color(0xFFE91E63), 'صورتی'), _buildColorItem(const Color(0xFFF44336), 'قرمز'), ], ), ), ), const SizedBox(height: 16), _buildSettingSection( icon: Icons.lock_outline_rounded, iconColor: Colors.amberAccent, title: 'قفل برنامه', content: _isAppLockEnabled ? 'رمز برنامه فعال است. از این به بعد، هر بار ورود یا بازگشت به اپ نیاز به وارد کردن رمز خواهد داشت.' : 'اگر این گزینه را فعال کنید، بدون وارد کردن رمز هیچ‌کس نمی‌تواند وارد برنامه شود.', action: _isAppLockLoading ? const Center(child: CircularProgressIndicator()) : Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.05), borderRadius: BorderRadius.circular(14), border: Border.all( color: _isAppLockEnabled ? Colors.greenAccent .withValues(alpha: 0.25) : Colors.white12, ), ), child: Row( children: [ Icon( _isAppLockEnabled ? Icons.verified_user_rounded : Icons.lock_open_rounded, color: _isAppLockEnabled ? Colors.greenAccent : Colors.white60, ), const SizedBox(width: 12), Expanded( child: Text( _isAppLockEnabled ? 'رمز برنامه فعال است' : 'رمز برنامه غیرفعال است', style: const TextStyle( color: Colors.white, fontWeight: FontWeight.w600, ), ), ), ], ), ), const SizedBox(height: 14), SizedBox( width: double.infinity, child: ElevatedButton.icon( onPressed: _isAppLockEnabled ? _showChangeAppLockDialog : _showEnableAppLockDialog, style: ElevatedButton.styleFrom( padding: const EdgeInsets.symmetric( vertical: 14, ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(14), ), ), icon: Icon( _isAppLockEnabled ? Icons.password_rounded : Icons.lock_rounded, ), label: Text( _isAppLockEnabled ? 'تغییر رمز برنامه' : 'فعال‌سازی رمز برنامه', ), ), ), if (_isAppLockEnabled) ...[ const SizedBox(height: 10), SizedBox( width: double.infinity, child: OutlinedButton.icon( onPressed: _showDisableAppLockDialog, style: OutlinedButton.styleFrom( foregroundColor: Colors.redAccent, side: const BorderSide( color: Colors.redAccent, ), padding: const EdgeInsets.symmetric( vertical: 14, ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(14), ), ), icon: const Icon(Icons.lock_reset_rounded), label: const Text('غیرفعال‌سازی رمز برنامه'), ), ), ], ], ), ), const SizedBox(height: 16), _buildSettingSection( icon: Icons.security_rounded, iconColor: Colors.cyanAccent, title: 'حریم خصوصی و استتار', content: 'با فعال‌سازی این گزینه، آیکون و نام برنامه در لیست برنامه‌های گوشی به "ماشین حساب" تغییر می‌کند تا امنیت شما حفظ شود.', action: FutureBuilder( future: _platform .invokeMethod('getStealthMode') .then((v) => v ?? false), builder: (context, snapshot) { final isStealth = snapshot.data ?? false; return Container( decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.05), borderRadius: BorderRadius.circular(12), ), child: SwitchListTile( title: const Text('حالت استتار (ماشین حساب)', style: TextStyle( fontSize: 14, color: Colors.white)), subtitle: Text( isStealth ? 'فعال (برنامه مخفی است)' : 'غیرفعال', style: const TextStyle( fontSize: 12, color: Colors.white60)), value: isStealth, contentPadding: const EdgeInsets.symmetric(horizontal: 12), secondary: const Icon(Icons.calculate_outlined, color: Colors.white70), activeThumbColor: Colors.cyanAccent, onChanged: (bool value) async { final confirmed = await showDialog( context: context, builder: (ctx) => AlertDialog( backgroundColor: AppTheme.darkCard, title: Text( value ? 'فعال‌سازی حالت مخفی' : 'خروج از حالت مخفی', style: const TextStyle(color: Colors.white)), content: Text( value ? 'با تایید این گزینه، برنامه بسته شده و آیکون آن به ماشین حساب تغییر می‌کند.' : 'با تایید این گزینه، آیکون اصلی صبا باز می‌گردد.', style: const TextStyle( color: Colors.white70)), actions: [ TextButton( onPressed: () => Navigator.pop(ctx, false), child: const Text('لغو')), ElevatedButton( onPressed: () => Navigator.pop(ctx, true), child: const Text('تایید')), ], ), ); if (confirmed == true) { await _platform.invokeMethod( 'setStealthMode', {'enabled': value}); } }, ), ); }, ), ), const SizedBox(height: 32), Center( child: TextButton.icon( onPressed: _isResetting ? null : _confirmReset, style: TextButton.styleFrom( foregroundColor: Colors.redAccent.withValues(alpha: 0.8)), icon: const Icon(Icons.dangerous_outlined), label: const Text('ریست کامل و حذف تمام داده‌ها', style: TextStyle(fontWeight: FontWeight.bold)), ), ), const SizedBox(height: 40), ], ), ), ), ], ), ); } Widget _buildSettingSection({ required IconData icon, required Color iconColor, required String title, required String content, required Widget action, }) { return AppTheme.glassWrapper( child: Padding( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon(icon, color: iconColor), const SizedBox(width: 12), Text( title, style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: Colors.white), ), ], ), const SizedBox(height: 12), Text( content, style: const TextStyle( height: 1.5, color: Colors.white70, fontSize: 13), ), const SizedBox(height: 20), action, ], ), ), ); } Widget _buildColorItem(Color color, String label) { final bool isSelected = themeNotifier.value.toARGB32() == color.toARGB32(); return GestureDetector( onTap: () async { themeNotifier.value = color; final prefs = await SharedPreferences.getInstance(); await prefs.setInt('primary_color_value', color.toARGB32()); setState(() {}); // Refresh local UI for selection check }, child: Container( margin: const EdgeInsets.symmetric(horizontal: 8), padding: const EdgeInsets.all(3), decoration: BoxDecoration( shape: BoxShape.circle, border: Border.all( color: isSelected ? color : Colors.transparent, width: 2, ), ), child: Container( width: 38, height: 38, decoration: BoxDecoration( color: color, shape: BoxShape.circle, boxShadow: [ BoxShadow( color: color.withValues(alpha: 0.4), blurRadius: 4, offset: const Offset(0, 2), ), ], ), child: isSelected ? const Icon(Icons.check, color: Colors.white, size: 20) : null, ), ), ); } } class _AppLockDialog extends StatefulWidget { const _AppLockDialog({required this.mode}); final _AppLockDialogMode mode; @override State<_AppLockDialog> createState() => _AppLockDialogState(); } class _AppLockDialogState extends State<_AppLockDialog> { final TextEditingController _currentController = TextEditingController(); final TextEditingController _nextController = TextEditingController(); final TextEditingController _confirmController = TextEditingController(); bool _isSubmitting = false; String? _errorText; bool get _isEnableMode => widget.mode == _AppLockDialogMode.enable; bool get _isChangeMode => widget.mode == _AppLockDialogMode.change; bool get _isDisableMode => widget.mode == _AppLockDialogMode.disable; @override void dispose() { _currentController.dispose(); _nextController.dispose(); _confirmController.dispose(); super.dispose(); } Future _close([bool result = false]) async { FocusManager.instance.primaryFocus?.unfocus(); await Future.delayed(const Duration(milliseconds: 40)); if (!mounted) return; Navigator.of(context).pop(result); } Future _submit() async { if (_isSubmitting) return; if (_isEnableMode) { final nextError = _validateAppLockPasscode(_nextController.text); if (nextError != null) { setState(() => _errorText = nextError); return; } if (_nextController.text.trim() != _confirmController.text.trim()) { setState(() => _errorText = 'تکرار رمز با رمز اصلی یکسان نیست.'); return; } } if (_isChangeMode) { final currentError = _validateAppLockPasscode(_currentController.text); final nextError = _validateAppLockPasscode(_nextController.text); if (currentError != null) { setState(() => _errorText = 'رمز فعلی معتبر نیست.'); return; } if (nextError != null) { setState(() => _errorText = nextError); return; } if (_nextController.text.trim() != _confirmController.text.trim()) { setState(() => _errorText = 'تکرار رمز جدید با رمز جدید یکسان نیست.'); return; } } if (_isDisableMode) { final currentError = _validateAppLockPasscode(_currentController.text); if (currentError != null) { setState(() => _errorText = 'رمز فعلی را درست وارد کنید.'); return; } } setState(() { _isSubmitting = true; _errorText = null; }); if (_isEnableMode) { await AppLockService.instance.setPasscode(_nextController.text.trim()); await _close(true); return; } if (_isChangeMode) { final updated = await AppLockService.instance.changePasscode( currentPasscode: _currentController.text.trim(), newPasscode: _nextController.text.trim(), ); if (!mounted) return; if (!updated) { setState(() { _isSubmitting = false; _errorText = 'رمز فعلی درست نیست.'; }); return; } await _close(true); return; } final removed = await AppLockService.instance .disablePasscode(_currentController.text.trim()); if (!mounted) return; if (!removed) { setState(() { _isSubmitting = false; _errorText = 'رمز فعلی درست نیست.'; }); return; } await _close(true); } String get _title { switch (widget.mode) { case _AppLockDialogMode.enable: return 'فعال‌سازی رمز برنامه'; case _AppLockDialogMode.change: return 'تغییر رمز برنامه'; case _AppLockDialogMode.disable: return 'غیرفعال‌سازی رمز برنامه'; } } String get _primaryActionLabel { switch (widget.mode) { case _AppLockDialogMode.enable: return _isSubmitting ? 'در حال ذخیره...' : 'فعال‌سازی'; case _AppLockDialogMode.change: return _isSubmitting ? 'در حال ذخیره...' : 'ذخیره تغییرات'; case _AppLockDialogMode.disable: return _isSubmitting ? 'در حال بررسی...' : 'حذف رمز'; } } Widget _buildPasscodeField({ required TextEditingController controller, required String label, ValueChanged? onSubmitted, }) { return TextField( controller: controller, keyboardType: TextInputType.number, obscureText: true, obscuringCharacter: '•', textInputAction: TextInputAction.done, inputFormatters: [ FilteringTextInputFormatter.digitsOnly, LengthLimitingTextInputFormatter(8), ], onSubmitted: onSubmitted, decoration: InputDecoration( labelText: label, hintText: '۴ تا ۸ رقم', filled: true, fillColor: Colors.white.withValues(alpha: 0.06), border: OutlineInputBorder( borderRadius: BorderRadius.circular(14), ), ), ); } @override Widget build(BuildContext context) { return AlertDialog( backgroundColor: AppTheme.darkCard, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), ), title: Text( _title, style: const TextStyle(color: Colors.white), ), content: Column( mainAxisSize: MainAxisSize.min, children: [ if (_isEnableMode) const Text( 'یک رمز ۴ تا ۸ رقمی انتخاب کنید. از این به بعد، برای ورود یا بازگشت به برنامه باید همین رمز وارد شود.', style: TextStyle(color: Colors.white70, height: 1.6), ), if (_isDisableMode) const Text( 'برای حذف قفل برنامه، رمز فعلی را وارد کنید.', style: TextStyle(color: Colors.white70, height: 1.6), ), if (_isEnableMode || _isDisableMode) const SizedBox(height: 16), if (_isChangeMode) ...[ _buildPasscodeField( controller: _currentController, label: 'رمز فعلی', ), const SizedBox(height: 12), _buildPasscodeField( controller: _nextController, label: 'رمز جدید', ), const SizedBox(height: 12), _buildPasscodeField( controller: _confirmController, label: 'تکرار رمز جدید', onSubmitted: (_) => _submit(), ), ], if (_isEnableMode) ...[ _buildPasscodeField( controller: _nextController, label: 'رمز جدید', ), const SizedBox(height: 12), _buildPasscodeField( controller: _confirmController, label: 'تکرار رمز', onSubmitted: (_) => _submit(), ), ], if (_isDisableMode) _buildPasscodeField( controller: _currentController, label: 'رمز فعلی', onSubmitted: (_) => _submit(), ), if (_errorText != null) ...[ const SizedBox(height: 12), Text( _errorText!, style: const TextStyle(color: Colors.redAccent), ), ], ], ), actions: [ TextButton( onPressed: _isSubmitting ? null : _close, child: const Text('لغو'), ), ElevatedButton( style: _isDisableMode ? ElevatedButton.styleFrom( backgroundColor: Colors.redAccent, foregroundColor: Colors.white, ) : null, onPressed: _isSubmitting ? null : _submit, child: Text(_primaryActionLabel), ), ], ); } } class MeshBackgroundPainter extends CustomPainter { @override void paint(Canvas canvas, Size size) { final Paint paint1 = Paint() ..shader = RadialGradient( colors: [ const Color(0xFF7000FF).withValues(alpha: 0.12), const Color(0xFF7000FF).withValues(alpha: 0.0), ], ).createShader(Rect.fromCircle( center: Offset(size.width * 0.2, size.height * 0.15), radius: 300)); canvas.drawCircle( Offset(size.width * 0.2, size.height * 0.15), 300, paint1); final Paint paint2 = Paint() ..shader = RadialGradient( colors: [ const Color(0xFF00D2FF).withValues(alpha: 0.1), const Color(0xFF00D2FF).withValues(alpha: 0.0), ], ).createShader(Rect.fromCircle( center: Offset(size.width * 0.8, size.height * 0.8), radius: 400)); canvas.drawCircle(Offset(size.width * 0.8, size.height * 0.8), 400, paint2); } @override bool shouldRepaint(covariant CustomPainter oldDelegate) => false; }