Saba-dart/lib/widgets/app_lock_overlay.dart
2026-04-13 23:41:27 +03:30

307 lines
8.5 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../utils/app_lock_service.dart';
import '../utils/app_theme.dart';
class AppLockOverlay extends StatefulWidget {
const AppLockOverlay({
super.key,
required this.child,
});
final Widget child;
@override
State<AppLockOverlay> createState() => _AppLockOverlayState();
}
class _AppLockOverlayState extends State<AppLockOverlay>
with WidgetsBindingObserver {
bool _shouldRelockOnResume = false;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
final service = AppLockService.instance;
if (!service.isEnabled) return;
if (state == AppLifecycleState.inactive ||
state == AppLifecycleState.paused ||
state == AppLifecycleState.hidden) {
_shouldRelockOnResume = true;
return;
}
if (state == AppLifecycleState.resumed && _shouldRelockOnResume) {
_shouldRelockOnResume = false;
service.lock();
}
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: AppLockService.instance,
builder: (context, _) {
final service = AppLockService.instance;
return Stack(
fit: StackFit.expand,
children: [
widget.child,
if (service.initialized && service.isLocked)
const Positioned.fill(child: _AppLockBarrier()),
],
);
},
);
}
}
class _AppLockBarrier extends StatelessWidget {
const _AppLockBarrier();
@override
Widget build(BuildContext context) {
return PopScope(
canPop: false,
child: Material(
color: AppTheme.darkBg.withValues(alpha: 0.96),
child: Stack(
fit: StackFit.expand,
children: [
Positioned.fill(
child: CustomPaint(
painter: _LockBackgroundPainter(),
),
),
const SafeArea(
child: Center(
child: Padding(
padding: EdgeInsets.all(20),
child: _AppLockCard(),
),
),
),
],
),
),
);
}
}
class _AppLockCard extends StatefulWidget {
const _AppLockCard();
@override
State<_AppLockCard> createState() => _AppLockCardState();
}
class _AppLockCardState extends State<_AppLockCard> {
final TextEditingController _controller = TextEditingController();
bool _isVerifying = false;
String? _errorText;
@override
void dispose() {
_controller.dispose();
super.dispose();
}
Future<void> _unlock() async {
if (_isVerifying) return;
FocusScope.of(context).unfocus();
final passcode = _controller.text.trim();
if (passcode.isEmpty) {
setState(() => _errorText = 'رمز برنامه را وارد کنید.');
return;
}
setState(() {
_isVerifying = true;
_errorText = null;
});
final isValid = await AppLockService.instance.verifyPasscode(passcode);
if (!mounted) return;
setState(() {
_isVerifying = false;
_errorText = isValid ? null : 'رمز واردشده درست نیست.';
});
if (isValid) {
_controller.clear();
}
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 420),
child: AppTheme.glassWrapper(
radius: 28,
sigma: 14,
child: Padding(
padding: const EdgeInsets.fromLTRB(24, 28, 24, 24),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 76,
height: 76,
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: LinearGradient(
colors: [
theme.primaryColor.withValues(alpha: 0.95),
AppTheme.accentCyan.withValues(alpha: 0.95),
],
),
boxShadow: [
BoxShadow(
color: theme.primaryColor.withValues(alpha: 0.28),
blurRadius: 20,
spreadRadius: 1,
),
],
),
child: const Icon(
Icons.lock_rounded,
size: 38,
color: Colors.white,
),
),
const SizedBox(height: 18),
const Text(
'قفل برنامه فعال است',
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
color: Colors.white,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 10),
const Text(
'برای ورود به صبا، رمز برنامه را وارد کنید.',
style: TextStyle(
fontSize: 14,
height: 1.6,
color: Colors.white70,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 24),
TextField(
controller: _controller,
autofocus: true,
keyboardType: TextInputType.number,
textInputAction: TextInputAction.done,
obscureText: true,
obscuringCharacter: '',
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
LengthLimitingTextInputFormatter(8),
],
onSubmitted: (_) => _unlock(),
decoration: InputDecoration(
labelText: 'رمز برنامه',
hintText: '۴ تا ۸ رقم',
errorText: _errorText,
filled: true,
fillColor: Colors.white.withValues(alpha: 0.06),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
),
),
),
const SizedBox(height: 18),
SizedBox(
width: double.infinity,
child: ElevatedButton.icon(
onPressed: _isVerifying ? null : _unlock,
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 15),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
),
icon: _isVerifying
? const SizedBox(
width: 18,
height: 18,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Icon(Icons.lock_open_rounded),
label:
Text(_isVerifying ? 'در حال بررسی...' : 'ورود به برنامه'),
),
),
],
),
),
),
);
}
}
class _LockBackgroundPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final purplePaint = Paint()
..shader = RadialGradient(
colors: [
AppTheme.accentPurple.withValues(alpha: 0.22),
AppTheme.accentPurple.withValues(alpha: 0),
],
).createShader(
Rect.fromCircle(
center: Offset(size.width * 0.18, size.height * 0.2),
radius: size.width * 0.55,
),
);
final cyanPaint = Paint()
..shader = RadialGradient(
colors: [
AppTheme.accentCyan.withValues(alpha: 0.14),
AppTheme.accentCyan.withValues(alpha: 0),
],
).createShader(
Rect.fromCircle(
center: Offset(size.width * 0.82, size.height * 0.78),
radius: size.width * 0.6,
),
);
canvas.drawCircle(
Offset(size.width * 0.18, size.height * 0.2),
size.width * 0.55,
purplePaint,
);
canvas.drawCircle(
Offset(size.width * 0.82, size.height * 0.78),
size.width * 0.6,
cyanPaint,
);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}