stopou_blocker 0.6.1
stopou_blocker: ^0.6.1 copied to clipboard
Plugin do Stopou para bloqueio por VPN local (preparado para estratégias futuras).
example/lib/main.dart
import 'package:flutter/material.dart';
import 'package:stopou_blocker/stopou_blocker.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Stopou Blocker Demo',
theme: ThemeData(useMaterial3: true),
home: const DemoPage(),
);
}
}
class DemoPage extends StatefulWidget {
const DemoPage({super.key});
@override
State<DemoPage> createState() => _DemoPageState();
}
class _DemoPageState extends State<DemoPage> {
final _log = <String>[];
bool _vpnRunning = false;
bool _keywordBlockerRunning = false;
bool _hasVpnPermission = false;
bool _hasAccessibilityPermission = false;
bool _hasNotificationPermission = false;
@override
void initState() {
super.initState();
// ✅ Assina o stream de eventos do plugin.
StopouBlocker.events.listen((e) {
setState(() {
_log.add(
"${e.ts.toIso8601String()} ${e.protocol.padRight(5)} ${e.host} ${e.appPackage ?? ''}",
);
});
});
// ✅ Verifica status inicial
_updateStatus();
}
Future<void> _updateStatus() async {
final vpnRunning = await StopouBlocker.isVpnRunning();
final keywordRunning = await StopouBlocker.isKeywordBlockerRunning();
final hasVpn = await StopouBlocker.hasVpnPermission();
final hasAccessibility = await StopouBlocker.hasAccessibilityPermission();
final hasNotification = await StopouBlocker.hasNotificationPermission();
setState(() {
_vpnRunning = vpnRunning;
_keywordBlockerRunning = keywordRunning;
_hasVpnPermission = hasVpn;
_hasAccessibilityPermission = hasAccessibility;
_hasNotificationPermission = hasNotification;
});
}
/// 📝 Adiciona uma mensagem ao log
void _addLog(String message) {
final timestamp = DateTime.now().toString().substring(11, 19);
setState(() {
_log.add('[$timestamp] $message');
});
// Também exibe no console para debugging
print('[$timestamp] $message');
}
/// 📱 Testa a funcionalidade de obter aplicativos instalados
Future<void> _testInstalledApps() async {
_addLog('📱 [TEST] Iniciando teste de aplicativos instalados...');
try {
final stopwatch = Stopwatch()..start();
// Obtém lista de aplicativos instalados (formato JSON)
final apps = await StopouBlocker.getInstalledApps();
stopwatch.stop();
final duration = stopwatch.elapsedMilliseconds;
_addLog('📱 [TEST] ✅ Aplicativos obtidos em ${duration}ms');
_addLog('📱 [TEST] Total de aplicativos: ${apps.length}');
_addLog('📱 [TEST] 📋 Formato JSON limpo: {"label": "...", "packageName": "...", "iconBase64": "..." ou null}');
_addLog('📱 [TEST] ========== PRIMEIROS 10 APLICATIVOS ==========');
// Exibe os primeiros 10 aplicativos para validar
final appsToShow = apps.take(10);
for (int i = 0; i < appsToShow.length; i++) {
final app = appsToShow.elementAt(i);
final label = app['label'] as String? ?? '';
final packageName = app['packageName'] as String? ?? '';
final iconBase64 = app['iconBase64'] as String?;
final hasIcon = iconBase64 != null ? '🎨' : '🚫';
_addLog('📱 [TEST] ${i + 1}. $hasIcon $label');
_addLog('📱 [TEST] 📦 $packageName');
// Validar dados
if (label.isEmpty) {
_addLog('📱 [TEST] ⚠️ App sem label: $packageName');
}
if (packageName.isEmpty) {
_addLog('📱 [TEST] ❌ App sem packageName!');
}
}
_addLog('📱 [TEST] ========================================');
// Estatísticas
final appsWithIcons = apps.where((app) => app['iconBase64'] != null).length;
final appsWithoutIcons = apps.length - appsWithIcons;
_addLog('📱 [TEST] 📊 Com ícones: $appsWithIcons');
_addLog('📱 [TEST] 📊 Sem ícones: $appsWithoutIcons');
// Validar dados críticos
final appsWithEmptyLabel = apps.where((app) => (app['label'] as String? ?? '').isEmpty).length;
final appsWithEmptyPackage = apps.where((app) => (app['packageName'] as String? ?? '').isEmpty).length;
if (appsWithEmptyLabel > 0) {
_addLog('📱 [TEST] ⚠️ $appsWithEmptyLabel apps com label vazio');
}
if (appsWithEmptyPackage > 0) {
_addLog('📱 [TEST] ❌ $appsWithEmptyPackage apps com packageName vazio');
}
_addLog('📱 [TEST] ✅ Teste concluído com sucesso!');
// Exibir diálogo com resumo
if (mounted) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('📱 Aplicativos Instalados'),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Total: ${apps.length} aplicativos'),
Text('Tempo: ${duration}ms'),
Text('Com ícones: $appsWithIcons'),
Text('Sem ícones: $appsWithoutIcons'),
const SizedBox(height: 8),
const Text('✅ Formato JSON compatível com FlutterFlow',
style: TextStyle(color: Colors.green, fontWeight: FontWeight.bold)),
if (appsWithEmptyLabel > 0)
Text('⚠️ $appsWithEmptyLabel sem label',
style: const TextStyle(color: Colors.orange)),
if (appsWithEmptyPackage > 0)
Text('❌ $appsWithEmptyPackage sem package',
style: const TextStyle(color: Colors.red)),
],
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('OK'),
),
],
),
);
}
} catch (e, stackTrace) {
_addLog('📱 [TEST] ❌ Erro ao obter aplicativos: $e');
_addLog('📱 [TEST] Stack trace: $stackTrace');
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('❌ Erro: $e'),
backgroundColor: Colors.red,
),
);
}
}
}
/// 🧪 Testa a funcionalidade completa de bloqueio de aplicativos
Future<void> _testAppBlocker() async {
_addLog('🧪 [APP_BLOCKER] ========== TESTE DE BLOQUEIO DE APPS ==========');
try {
// 1. Verificar permissão de acessibilidade
_addLog('🧪 [APP_BLOCKER] 1. Verificando permissões...');
final hasAccessibility = await StopouBlocker.hasAccessibilityPermission();
_addLog('🧪 [APP_BLOCKER] Acessibilidade: ${hasAccessibility ? "✅" : "❌"}');
if (!hasAccessibility) {
_addLog('🧪 [APP_BLOCKER] ⚠️ Permissão de acessibilidade necessária');
_addLog('🧪 [APP_BLOCKER] 💡 Use o botão "Solicitar Permissões" primeiro');
return;
}
// 2. Obter lista de apps para teste
_addLog('🧪 [APP_BLOCKER] 2. Obtendo apps instalados...');
final apps = await StopouBlocker.getInstalledApps();
_addLog('🧪 [APP_BLOCKER] Encontrados: ${apps.length} apps');
// 3. Selecionar app de teste (Chrome preferencialmente)
String? testPackage;
final preferredPackages = [
'com.android.chrome',
'com.google.android.youtube',
'com.whatsapp'
];
for (final pkg in preferredPackages) {
if (apps.any((app) => app['packageName'] == pkg)) {
testPackage = pkg;
break;
}
}
if (testPackage == null && apps.isNotEmpty) {
testPackage = apps.first['packageName'] as String;
}
if (testPackage == null) {
_addLog('🧪 [APP_BLOCKER] ❌ Nenhum app encontrado para teste');
return;
}
final testAppName = apps.firstWhere(
(app) => app['packageName'] == testPackage,
orElse: () => {'label': 'App Desconhecido'}
)['label'] as String;
_addLog('🧪 [APP_BLOCKER] 3. App selecionado: $testAppName ($testPackage)');
// 4. Testar início do bloqueio
_addLog('🧪 [APP_BLOCKER] 4. Iniciando bloqueio...');
final startResult = await StopouBlocker.startAppBlocker([testPackage]);
_addLog('🧪 [APP_BLOCKER] Resultado início: ${startResult ? "✅" : "❌"}');
if (!startResult) {
_addLog('🧪 [APP_BLOCKER] ❌ Falha ao iniciar bloqueio');
return;
}
// 5. Aguardar um pouco para testar
_addLog('🧪 [APP_BLOCKER] 5. Aguardando 3 segundos para teste...');
_addLog('🧪 [APP_BLOCKER] 🎯 AGORA! Tente abrir o app: $testAppName');
_addLog('🧪 [APP_BLOCKER] 💡 O app deve fechar automaticamente!');
await Future.delayed(const Duration(seconds: 3));
// 6. Parar bloqueio
_addLog('🧪 [APP_BLOCKER] 6. Parando bloqueio...');
final stopResult = await StopouBlocker.stopAppBlocker();
_addLog('🧪 [APP_BLOCKER] Resultado parada: ${stopResult ? "✅" : "❌"}');
// 7. Resultado final
if (startResult && stopResult) {
_addLog('🧪 [APP_BLOCKER] ✅ TESTE CONCLUÍDO COM SUCESSO!');
_addLog('🧪 [APP_BLOCKER] 💡 Sistema funcionando corretamente');
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('✅ Teste de bloqueio concluído com sucesso!'),
backgroundColor: Colors.green,
),
);
}
} else {
_addLog('🧪 [APP_BLOCKER] ❌ TESTE FALHOU');
_addLog('🧪 [APP_BLOCKER] Início: $startResult, Parada: $stopResult');
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('❌ Teste de bloqueio falhou'),
backgroundColor: Colors.red,
),
);
}
}
} catch (e, stackTrace) {
_addLog('🧪 [APP_BLOCKER] ❌ Erro no teste: $e');
_addLog('🧪 [APP_BLOCKER] Stack trace: $stackTrace');
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('❌ Erro no teste: $e'),
backgroundColor: Colors.red,
),
);
}
}
_addLog('🧪 [APP_BLOCKER] ==========================================');
}
/// 🚫 Demo: Inicia bloqueio do Chrome
Future<void> _startAppBlockerDemo() async {
_addLog('🚫 [DEMO] Iniciando bloqueio do Chrome...');
try {
// Verificar permissão
final hasPermission = await StopouBlocker.hasAccessibilityPermission();
if (!hasPermission) {
_addLog('🚫 [DEMO] ❌ Permissão de acessibilidade necessária');
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('❌ Permissão de acessibilidade necessária'),
backgroundColor: Colors.red,
),
);
}
return;
}
// Iniciar bloqueio do Chrome
const chromePackage = 'com.android.chrome';
final success = await StopouBlocker.startAppBlocker([chromePackage]);
if (success) {
_addLog('🚫 [DEMO] ✅ Chrome bloqueado!');
_addLog('🚫 [DEMO] 🎯 Tente abrir o Chrome - ele deve fechar automaticamente');
_addLog('🚫 [DEMO] 💡 Use "Demo: Parar Bloqueio" para desbloquear');
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('🚫 Chrome bloqueado! Tente abrí-lo para testar'),
backgroundColor: Colors.orange,
duration: Duration(seconds: 4),
),
);
}
} else {
_addLog('🚫 [DEMO] ❌ Falha ao bloquear Chrome');
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('❌ Falha ao bloquear Chrome'),
backgroundColor: Colors.red,
),
);
}
}
} catch (e) {
_addLog('🚫 [DEMO] ❌ Erro: $e');
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('❌ Erro: $e'),
backgroundColor: Colors.red,
),
);
}
}
}
/// ✅ Demo: Para bloqueio de aplicativos
Future<void> _stopAppBlockerDemo() async {
_addLog('✅ [DEMO] Parando bloqueio de aplicativos...');
try {
final success = await StopouBlocker.stopAppBlocker();
if (success) {
_addLog('✅ [DEMO] ✅ Bloqueio parado!');
_addLog('✅ [DEMO] 🎯 Todos os apps podem ser abertos normalmente');
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('✅ Bloqueio parado! Apps liberados'),
backgroundColor: Colors.green,
),
);
}
} else {
_addLog('✅ [DEMO] ❌ Falha ao parar bloqueio');
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('❌ Falha ao parar bloqueio'),
backgroundColor: Colors.red,
),
);
}
}
} catch (e) {
_addLog('✅ [DEMO] ❌ Erro: $e');
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('❌ Erro: $e'),
backgroundColor: Colors.red,
),
);
}
}
}
void _showAccessibilityInstructions() {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('⚠️ Permissão de Acessibilidade'),
content: const SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Se o Android mostrar "permissão restrita", siga estes passos:',
style: TextStyle(fontWeight: FontWeight.bold),
),
SizedBox(height: 12),
Text('1. Nas configurações de Acessibilidade, encontre "Stopou"'),
SizedBox(height: 8),
Text('2. Toque em "Stopou" para abrir as configurações'),
SizedBox(height: 8),
Text(
'3. Se aparecer aviso de "permissão restrita", toque em "Aprenda a conceder acesso"',
),
SizedBox(height: 8),
Text('4. Ative o toggle para "Usar Stopou"'),
SizedBox(height: 8),
Text('5. Confirme tocando em "OK" quando perguntado'),
SizedBox(height: 12),
Text(
'ℹ️ O Stopou é seguro: apenas detecta palavras específicas e exibe alertas. Não coleta dados pessoais.',
style: TextStyle(fontStyle: FontStyle.italic),
),
],
),
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('Entendi'),
),
ElevatedButton(
onPressed: () async {
Navigator.of(context).pop();
await StopouBlocker.openAccessibilitySettings();
},
child: const Text('Abrir Configurações'),
),
],
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Stopou Blocker Demo'),
actions: [
IconButton(
icon: const Icon(Icons.refresh),
onPressed: _updateStatus,
tooltip: 'Atualizar Status',
),
],
),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
// Status Section
Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'📊 Status dos Serviços',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 12),
Row(
children: [
Icon(
_vpnRunning
? Icons.vpn_lock
: Icons.vpn_lock_outlined,
color: _vpnRunning ? Colors.green : Colors.grey,
),
const SizedBox(width: 8),
Text('VPN: ${_vpnRunning ? "Ativo" : "Inativo"}'),
],
),
const SizedBox(height: 8),
Row(
children: [
Icon(
_keywordBlockerRunning
? Icons.accessibility
: Icons.accessibility_outlined,
color: _keywordBlockerRunning
? Colors.green
: Colors.grey,
),
const SizedBox(width: 8),
Text(
'Bloqueador Keywords: ${_keywordBlockerRunning ? "Ativo" : "Inativo"}',
),
],
),
],
),
),
),
const SizedBox(height: 16),
// Permissions Section
Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'🔑 Permissões',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 12),
ElevatedButton.icon(
onPressed: () async {
final ok = await StopouBlocker.requestPermission();
await _updateStatus();
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Permissão VPN: $ok')),
);
}
},
icon: Icon(
_hasVpnPermission ? Icons.check : Icons.vpn_key,
),
label: Text('VPN ${_hasVpnPermission ? "✓" : "✗"}'),
),
const SizedBox(height: 8),
ElevatedButton.icon(
onPressed: () async {
final ok =
await StopouBlocker.requestAccessibilityPermission();
await _updateStatus();
if (mounted) {
if (!ok) {
_showAccessibilityInstructions();
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Permissão Acessibilidade: ✓'),
),
);
}
}
},
icon: Icon(
_hasAccessibilityPermission
? Icons.check
: Icons.accessibility,
),
label: Text(
'Acessibilidade ${_hasAccessibilityPermission ? "✓" : "✗"}',
),
),
const SizedBox(height: 8),
ElevatedButton.icon(
onPressed: () async {
final ok =
await StopouBlocker.requestNotificationPermission();
await _updateStatus();
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Permissão Notificação: $ok'),
),
);
}
},
icon: Icon(
_hasNotificationPermission
? Icons.check
: Icons.notifications,
),
label: Text(
'Notificações ${_hasNotificationPermission ? "✓" : "✗"}',
),
),
],
),
),
),
const SizedBox(height: 16),
// Controls Section
Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'🎛️ Controles',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 12),
ElevatedButton.icon(
onPressed: _vpnRunning
? null
: () async {
await StopouBlocker.start(
blocklist: const ['.bet.br', 'exemplo.com'],
logAttempts: true,
dnsServers: const ['1.1.1.1', '8.8.8.8'],
strategies: const [BlockStrategies.vpn],
);
await _updateStatus();
},
icon: const Icon(Icons.play_arrow),
label: const Text('Iniciar VPN'),
),
const SizedBox(height: 8),
ElevatedButton.icon(
onPressed: _keywordBlockerRunning
? null
: () async {
await StopouBlocker.startKeywordBlocker([
'bet',
'casino',
'apostas',
]);
await _updateStatus();
},
icon: const Icon(Icons.block),
label: const Text('Iniciar Bloqueador Keywords'),
),
const SizedBox(height: 8),
ElevatedButton.icon(
onPressed: (!_vpnRunning && !_keywordBlockerRunning)
? null
: () async {
await StopouBlocker.stop();
await StopouBlocker.stopKeywordBlocker();
await _updateStatus();
},
icon: const Icon(Icons.stop),
label: const Text('Parar Tudo'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red[100],
),
),
const SizedBox(height: 16),
// Seção de teste de aplicativos instalados
const Divider(),
const Text('🧪 Testes', style: TextStyle(fontWeight: FontWeight.bold)),
const SizedBox(height: 8),
ElevatedButton.icon(
onPressed: _testInstalledApps,
icon: const Icon(Icons.apps),
label: const Text('Testar Apps Instalados'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.purple,
foregroundColor: Colors.white,
),
),
const SizedBox(height: 8),
ElevatedButton.icon(
onPressed: _testAppBlocker,
icon: const Icon(Icons.block),
label: const Text('Testar Bloqueio Apps'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.orange,
foregroundColor: Colors.white,
),
),
const SizedBox(height: 8),
ElevatedButton.icon(
onPressed: _startAppBlockerDemo,
icon: const Icon(Icons.security),
label: const Text('Demo: Bloquear Chrome'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
foregroundColor: Colors.white,
),
),
const SizedBox(height: 8),
ElevatedButton.icon(
onPressed: _stopAppBlockerDemo,
icon: const Icon(Icons.lock_open),
label: const Text('Demo: Parar Bloqueio'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.green,
foregroundColor: Colors.white,
),
),
],
),
),
),
const SizedBox(height: 16),
// Events Log
const Align(
alignment: Alignment.centerLeft,
child: Text(
'📝 Log de Eventos:',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
),
const SizedBox(height: 8),
Expanded(
child: Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.grey.shade300),
borderRadius: BorderRadius.circular(8),
),
child: _log.isEmpty
? const Center(
child: Text('Nenhum evento registrado ainda...'),
)
: ListView.builder(
itemCount: _log.length,
itemBuilder: (_, i) => Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 4,
),
child: Text(
_log[_log.length - 1 - i], // Mais recentes primeiro
style: const TextStyle(
fontFamily: 'monospace',
fontSize: 12,
),
),
),
),
),
),
],
),
),
);
}
}