Implement husband-wife connection dialogue and theme support for learn articles

This commit is contained in:
2026-01-05 17:09:15 -06:00
parent 02d25d0cc7
commit 96655f9a74
36 changed files with 3849 additions and 819 deletions

View File

@@ -0,0 +1,134 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:local_auth/local_auth.dart';
import '../models/user_profile.dart';
class ProtectedContentWrapper extends StatefulWidget {
final Widget child;
final bool isProtected;
final UserProfile? userProfile;
final String title;
const ProtectedContentWrapper({
super.key,
required this.child,
required this.isProtected,
required this.userProfile,
required this.title,
});
@override
State<ProtectedContentWrapper> createState() => _ProtectedContentWrapperState();
}
class _ProtectedContentWrapperState extends State<ProtectedContentWrapper> {
bool _isUnlocked = false;
final LocalAuthentication auth = LocalAuthentication();
Future<void> _authenticate() async {
final user = widget.userProfile;
if (user == null || user.privacyPin == null) {
// Fallback or error if PIN is missing but protected? (Shouldn't happen with UI logic)
return;
}
bool authenticated = false;
// Try Biometrics if enabled
if (user.isBioProtected) {
try {
final bool canCheckBiometrics = await auth.canCheckBiometrics;
if (canCheckBiometrics) {
authenticated = await auth.authenticate(
localizedReason: 'Scan your fingerprint or face to unlock ${widget.title}',
);
}
} on PlatformException catch (e) {
debugPrint('Biometric Error: $e');
// Fallback to PIN
}
}
if (authenticated) {
setState(() {
_isUnlocked = true;
});
return;
}
if (!mounted) return;
// PIN Fallback
final controller = TextEditingController();
final pin = await showDialog<String>(
context: context,
builder: (context) => AlertDialog(
title: const Text('Enter PIN'),
content: TextField(
controller: controller,
keyboardType: TextInputType.number,
obscureText: true,
maxLength: 4,
style: const TextStyle(fontSize: 24, letterSpacing: 8),
textAlign: TextAlign.center,
decoration: const InputDecoration(
hintText: '....',
border: OutlineInputBorder(),
),
autofocus: true,
),
actions: [
TextButton(onPressed: () => Navigator.pop(context), child: const Text('Cancel')),
ElevatedButton(
onPressed: () => Navigator.pop(context, controller.text),
child: const Text('Unlock'),
),
],
),
);
if (pin == user.privacyPin) {
setState(() {
_isUnlocked = true;
});
} else if (pin != null) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Incorrect PIN')),
);
}
}
}
@override
Widget build(BuildContext context) {
// If not protected, or already unlocked, show content
if (!widget.isProtected || _isUnlocked) {
return widget.child;
}
// Otherwise show Lock Screen
return Scaffold(
appBar: AppBar(title: Text(widget.title)),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.lock_outline, size: 64, color: Colors.grey),
const SizedBox(height: 16),
Text(
'${widget.title} is Protected',
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 24),
ElevatedButton.icon(
onPressed: _authenticate,
icon: const Icon(Icons.key),
label: Text(widget.userProfile?.isBioProtected == true ? 'Unlock with FaceID / PIN' : 'Enter PIN to Unlock'),
),
],
),
),
);
}
}