804 lines
31 KiB
Dart
804 lines
31 KiB
Dart
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<SettingsScreen> createState() => _SettingsScreenState();
|
|
}
|
|
|
|
class _SettingsScreenState extends State<SettingsScreen> {
|
|
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<void> _checkDefaultStatus() async {
|
|
try {
|
|
final bool isDefault = await _platform.invokeMethod('isDefaultSmsApp');
|
|
if (mounted) setState(() => _isCurrentlyDefault = isDefault);
|
|
} catch (_) {}
|
|
}
|
|
|
|
Future<void> _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<void> _confirmReset() async {
|
|
final confirmed = await showDialog<bool>(
|
|
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<void> _loadAppLockStatus() async {
|
|
await AppLockService.instance.init();
|
|
if (!mounted) return;
|
|
setState(() {
|
|
_isAppLockEnabled = AppLockService.instance.isEnabled;
|
|
_isAppLockLoading = false;
|
|
});
|
|
}
|
|
|
|
Future<void> _showEnableAppLockDialog() async {
|
|
final enabled = await showDialog<bool>(
|
|
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<void> _showChangeAppLockDialog() async {
|
|
final changed = await showDialog<bool>(
|
|
context: context,
|
|
builder: (_) => const _AppLockDialog(mode: _AppLockDialogMode.change),
|
|
);
|
|
|
|
if (changed == true && mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(
|
|
content: Text('رمز برنامه با موفقیت تغییر کرد.'),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
Future<void> _showDisableAppLockDialog() async {
|
|
final disabled = await showDialog<bool>(
|
|
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<bool>(
|
|
future: _platform
|
|
.invokeMethod<bool>('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<bool>(
|
|
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<void> _close([bool result = false]) async {
|
|
FocusManager.instance.primaryFocus?.unfocus();
|
|
await Future<void>.delayed(const Duration(milliseconds: 40));
|
|
if (!mounted) return;
|
|
Navigator.of(context).pop(result);
|
|
}
|
|
|
|
Future<void> _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<String>? 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;
|
|
}
|