mm_device_security 0.0.1
mm_device_security: ^0.0.1 copied to clipboard
Detecta root, jailbreak, emulador/simulador y modo desarrollador en Android e iOS.
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:mm_device_security/mm_device_security.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'MM Device Security',
theme: ThemeData(
useMaterial3: true,
brightness: Brightness.light,
scaffoldBackgroundColor: const Color(0xFFF5F7FA),
colorScheme: ColorScheme.fromSeed(
seedColor: const Color(0xFF2563EB),
),
),
home: const SecurityPage(),
);
}
}
class SecurityPage extends StatefulWidget {
const SecurityPage({super.key});
@override
State<SecurityPage> createState() => _SecurityPageState();
}
class _SecurityPageState extends State<SecurityPage> {
final _plugin = MmDeviceSecurity();
DeviceSecurityResult? _resultado;
String? _error;
bool _cargando = false;
@override
void initState() {
super.initState();
_consultar();
}
Future<void> _consultar() async {
setState(() {
_cargando = true;
_error = null;
});
try {
final resultado = await _plugin.getSecuritySummary();
debugPrint('Resultado: $resultado');
if (!mounted) return;
setState(() {
_resultado = resultado;
});
} on MissingPluginException catch (error) {
if (!mounted) return;
setState(() {
_resultado = const DeviceSecurityResult();
_error = 'Canal no registrado: $error';
});
} on PlatformException catch (error) {
if (!mounted) return;
setState(() {
_error = 'Error nativo: $error';
});
} catch (error) {
if (!mounted) return;
setState(() {
_error = 'Error inesperado: $error';
});
} finally {
if (mounted) {
setState(() {
_cargando = false;
});
}
}
}
@override
Widget build(BuildContext context) {
final resultado = _resultado;
final bloqueado = resultado?.isBlocked ?? false;
final seguro = resultado?.isSecure ?? false;
return Scaffold(
appBar: AppBar(
title: const Text(
'MM Device Security',
style: TextStyle(fontWeight: FontWeight.w700),
),
centerTitle: false,
),
body: RefreshIndicator(
onRefresh: _consultar,
child: ListView(
physics: const AlwaysScrollableScrollPhysics(),
padding: const EdgeInsets.fromLTRB(20, 16, 20, 32),
children: [
_SecurityHeader(
resultado: resultado,
seguro: seguro,
bloqueado: bloqueado,
),
const SizedBox(height: 24),
if (_error != null)
_ErrorCard(
message: _error!,
onRetry: _consultar,
),
if (resultado != null) ...[
const _SectionTitle(
title: 'Diagnóstico',
subtitle: 'Estado de seguridad del dispositivo',
),
const SizedBox(height: 12),
_SecurityCard(
icon: Icons.security_rounded,
title: 'Root / Jailbreak',
description:
'Comprueba si el dispositivo presenta privilegios elevados.',
value: resultado.isRooted,
),
_SecurityCard(
icon: Icons.devices_rounded,
title: 'Emulador / Simulador',
description:
'Detecta si la aplicación está ejecutándose en un entorno virtual.',
value: resultado.isEmulator,
),
_SecurityCard(
icon: Icons.developer_mode_rounded,
title: 'Modo desarrollador',
description:
'Comprueba si el modo desarrollador está habilitado.',
value: resultado.isDeveloperMode,
),
_SecurityCard(
icon: Icons.science_rounded,
title: 'Firebase Test Lab',
description:
'Detecta ejecución dentro de Firebase Test Lab.',
value: resultado.isTestLab,
),
_SecurityCard(
icon: Icons.apple_rounded,
title: 'TestFlight',
description:
'Indica si la aplicación está instalada mediante TestFlight.',
value: resultado.isTestFlight,
),
const SizedBox(height: 20),
const _SectionTitle(
title: 'Resultado',
subtitle: 'Evaluación general del dispositivo',
),
const SizedBox(height: 12),
_ResultCard(
blocked: bloqueado,
secure: seguro,
),
] else if (_cargando)
const Padding(
padding: EdgeInsets.symmetric(vertical: 80),
child: Center(
child: CircularProgressIndicator(),
),
),
const SizedBox(height: 24),
FilledButton.icon(
onPressed: _cargando ? null : _consultar,
icon: _cargando
? const SizedBox(
width: 18,
height: 18,
child: CircularProgressIndicator(
strokeWidth: 2,
),
)
: const Icon(Icons.refresh_rounded),
label: Text(
_cargando ? 'Consultando...' : 'Volver a consultar',
),
),
],
),
),
);
}
}
class _SecurityHeader extends StatelessWidget {
const _SecurityHeader({
required this.resultado,
required this.seguro,
required this.bloqueado,
});
final DeviceSecurityResult? resultado;
final bool seguro;
final bool bloqueado;
@override
Widget build(BuildContext context) {
final color = bloqueado
? const Color(0xFFDC2626)
: seguro
? const Color(0xFF16A34A)
: const Color(0xFF64748B);
final icon = bloqueado
? Icons.warning_rounded
: seguro
? Icons.verified_user_rounded
: Icons.security_rounded;
final title = bloqueado
? 'Dispositivo no seguro'
: seguro
? 'Dispositivo seguro'
: 'Sin diagnóstico';
final subtitle = bloqueado
? 'Se detectó una condición que puede comprometer la seguridad.'
: seguro
? 'No se detectaron condiciones de riesgo.'
: 'Ejecuta el diagnóstico para comprobar el dispositivo.';
return Container(
padding: const EdgeInsets.all(22),
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(24),
boxShadow: [
BoxShadow(
color: color.withValues(alpha: 0.20),
blurRadius: 20,
offset: const Offset(0, 8),
),
],
),
child: Row(
children: [
Container(
width: 56,
height: 56,
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.18),
shape: BoxShape.circle,
),
child: Icon(
icon,
color: Colors.white,
size: 30,
),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: const TextStyle(
color: Colors.white,
fontSize: 20,
fontWeight: FontWeight.w800,
),
),
const SizedBox(height: 6),
Text(
subtitle,
style: TextStyle(
color: Colors.white.withValues(alpha: 0.88),
fontSize: 13,
height: 1.4,
),
),
],
),
),
],
),
);
}
}
class _SectionTitle extends StatelessWidget {
const _SectionTitle({
required this.title,
required this.subtitle,
});
final String title;
final String subtitle;
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.w800,
color: Color(0xFF172033),
),
),
const SizedBox(height: 4),
Text(
subtitle,
style: const TextStyle(
fontSize: 13,
color: Color(0xFF697386),
),
),
],
);
}
}
class _SecurityCard extends StatelessWidget {
const _SecurityCard({
required this.icon,
required this.title,
required this.description,
required this.value,
});
final IconData icon;
final String title;
final String description;
final bool value;
@override
Widget build(BuildContext context) {
final statusColor = value
? const Color(0xFFDC2626)
: const Color(0xFF16A34A);
return Container(
margin: const EdgeInsets.only(bottom: 10),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(18),
border: Border.all(
color: const Color(0xFFE5E7EB),
),
),
child: Row(
children: [
Container(
width: 44,
height: 44,
decoration: BoxDecoration(
color: const Color(0xFFF1F5F9),
borderRadius: BorderRadius.circular(14),
),
child: Icon(
icon,
color: statusColor,
),
),
const SizedBox(width: 14),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: const TextStyle(
fontWeight: FontWeight.w700,
fontSize: 15,
color: Color(0xFF172033),
),
),
const SizedBox(height: 4),
Text(
description,
style: const TextStyle(
fontSize: 12,
height: 1.35,
color: Color(0xFF697386),
),
),
],
),
),
const SizedBox(width: 12),
_StatusBadge(value: value),
],
),
);
}
}
class _StatusBadge extends StatelessWidget {
const _StatusBadge({
required this.value,
});
final bool value;
@override
Widget build(BuildContext context) {
final color = value
? const Color(0xFFDC2626)
: const Color(0xFF16A34A);
return Container(
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 6,
),
decoration: BoxDecoration(
color: color.withValues(alpha: 0.10),
borderRadius: BorderRadius.circular(20),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
value
? Icons.warning_rounded
: Icons.check_circle_rounded,
size: 15,
color: color,
),
const SizedBox(width: 5),
Text(
value ? 'SÍ' : 'NO',
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w800,
color: color,
),
),
],
),
);
}
}
class _ResultCard extends StatelessWidget {
const _ResultCard({
required this.blocked,
required this.secure,
});
final bool blocked;
final bool secure;
@override
Widget build(BuildContext context) {
final color = blocked
? const Color(0xFFDC2626)
: const Color(0xFF16A34A);
return Container(
padding: const EdgeInsets.all(18),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
border: Border.all(
color: color.withValues(alpha: 0.25),
),
),
child: Row(
children: [
Icon(
blocked
? Icons.block_rounded
: Icons.check_circle_rounded,
color: color,
size: 30,
),
const SizedBox(width: 14),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
blocked ? 'Acceso restringido' : 'Acceso permitido',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w800,
color: color,
),
),
const SizedBox(height: 4),
Text(
blocked
? 'El dispositivo cumple una condición de bloqueo.'
: 'El dispositivo cumple las condiciones de seguridad.',
style: const TextStyle(
fontSize: 12,
color: Color(0xFF697386),
),
),
],
),
),
],
),
);
}
}
class _ErrorCard extends StatelessWidget {
const _ErrorCard({
required this.message,
required this.onRetry,
});
final String message;
final VoidCallback onRetry;
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.only(bottom: 20),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: const Color(0xFFFFF7ED),
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: const Color(0xFFFED7AA),
),
),
child: Row(
children: [
const Icon(
Icons.error_outline_rounded,
color: Color(0xFFEA580C),
),
const SizedBox(width: 12),
Expanded(
child: Text(
message,
style: const TextStyle(
fontSize: 12,
color: Color(0xFF9A3412),
),
),
),
IconButton(
onPressed: onRetry,
icon: const Icon(Icons.refresh_rounded),
),
],
),
);
}
}