prem / lib /screens /data_shield_screen.dart
Nitishkumar-ai's picture
Deploy source code to Hugging Face without binaries
c25dcd7
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:intl/intl.dart';
import '../services/vector_service.dart';
import '../services/storage_service.dart';
import '../services/telemetry_service.dart';
import 'app_lock_screen.dart';
import 'vpn_shield_screen.dart';
class DataShieldScreen extends StatefulWidget {
const DataShieldScreen({super.key});
@override
State<DataShieldScreen> createState() => _DataShieldScreenState();
}
class _DataShieldScreenState extends State<DataShieldScreen> {
int _embeddingCount = 0;
List<PrivacyStory> stories = [];
List<AppDataUsage> dataUsage = [];
List<SensorAccess> activeSensors = [];
bool isLoading = true;
bool hasPermission = false;
String selectedLanguage = 'english'; // english or hindi
DateTime lastScan = DateTime.now();
Timer? _sensorTimer;
@override
void initState() {
super.initState();
_loadEmbeddingCount();
_fetchTelemetry();
_sensorTimer = Timer.periodic(const Duration(seconds: 60), (_) {
_fetchActiveSensors();
});
}
@override
void dispose() {
_sensorTimer?.cancel();
super.dispose();
}
Future<void> _loadEmbeddingCount() async {
if (!VectorService.isReady) return;
final db = await StorageService().database;
final count = await VectorService().embeddingCount(db);
if (mounted) setState(() => _embeddingCount = count);
}
Future<void> _fetchTelemetry() async {
if (!mounted) return;
setState(() => isLoading = true);
final perm = await TelemetryService.hasUsagePermission();
final st = await TelemetryService.getPrivacyStories();
final dUsage = await TelemetryService.getDataUsage();
final sensors = await TelemetryService.getActiveSensors();
if (mounted) {
setState(() {
hasPermission = perm;
stories = st;
dataUsage = dUsage;
activeSensors = sensors;
lastScan = DateTime.now();
isLoading = false;
});
}
}
Future<void> _fetchActiveSensors() async {
final sensors = await TelemetryService.getActiveSensors();
if (mounted) {
setState(() {
activeSensors = sensors;
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFF0F1117),
appBar: AppBar(
title: const Text(
'Data Shield',
style: TextStyle(fontWeight: FontWeight.bold, color: Colors.white),
),
backgroundColor: const Color(0xFF1A1D27),
foregroundColor: Colors.white,
elevation: 0,
actions: [
// Language Toggle
Padding(
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 8),
child: Container(
decoration: BoxDecoration(
color: const Color(0xFF2D3145),
borderRadius: BorderRadius.circular(20),
),
child: Row(
children: [
_langButton('EN', 'english'),
_langButton('हिं', 'hindi'),
],
),
),
),
IconButton(
icon: const Icon(Icons.refresh, color: Colors.white70),
tooltip: 'Refresh',
onPressed: () {
_fetchTelemetry();
},
),
IconButton(
icon: const Icon(Icons.shield, color: Colors.white70),
tooltip: 'DNS Shield',
onPressed: () => Navigator.push(
context,
MaterialPageRoute(builder: (_) => const VpnShieldScreen()),
),
),
IconButton(
icon: const Icon(Icons.lock_outline, color: Colors.white70),
tooltip: 'App Lock',
onPressed: () => Navigator.push(
context,
MaterialPageRoute(builder: (_) => const AppLockScreen()),
),
),
],
),
floatingActionButton: FloatingActionButton.extended(
onPressed: isLoading ? null : _fetchTelemetry,
backgroundColor: Colors.blueAccent,
icon: isLoading
? const SizedBox(width: 20, height: 20, child: CircularProgressIndicator(color: Colors.white, strokeWidth: 2))
: const Icon(Icons.search, color: Colors.white),
label: const Text('Scan Now', style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold)),
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (!hasPermission && !isLoading) _buildPermissionBanner(),
const SizedBox(height: 16),
_buildLiveSensorStatus(),
const SizedBox(height: 24),
_sectionHeader('📊 Privacy Stories'),
const SizedBox(height: 8),
if (isLoading && stories.isEmpty)
const Center(child: Padding(padding: EdgeInsets.all(20), child: CircularProgressIndicator()))
else
...stories.map(_buildStoryCard).toList(),
const SizedBox(height: 24),
if (dataUsage.isNotEmpty) ...[
_sectionHeader('📤 Data Sent (last 24h)'),
const SizedBox(height: 8),
_buildDataUsageSection(),
const SizedBox(height: 24),
],
_sectionHeader('🛡️ System Status'),
const SizedBox(height: 8),
_buildSystemStatusCard(),
const SizedBox(height: 80), // Fab spacing
],
),
),
);
}
Widget _langButton(String label, String langVal) {
final active = selectedLanguage == langVal;
return GestureDetector(
onTap: () => setState(() => selectedLanguage = langVal),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
decoration: BoxDecoration(
color: active ? Colors.blueAccent : Colors.transparent,
borderRadius: BorderRadius.circular(20),
),
child: Text(
label,
style: TextStyle(
color: active ? Colors.white : Colors.white54,
fontWeight: active ? FontWeight.bold : FontWeight.normal,
fontSize: 13,
),
),
),
);
}
Widget _buildPermissionBanner() {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.amber.shade900.withValues(alpha: 0.2),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.amber.shade700),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(Icons.warning_amber_rounded, color: Colors.amber.shade300),
const SizedBox(width: 8),
const Expanded(
child: Text(
'Grant Usage Access for full insights',
style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 16),
),
),
],
),
const SizedBox(height: 8),
const Text(
'Premithius reads app usage locally. Nothing leaves your device.',
style: TextStyle(color: Colors.white70, fontSize: 13),
),
const SizedBox(height: 12),
ElevatedButton(
onPressed: () async {
const platform = MethodChannel('flutter/platform_channel');
try {
await platform.invokeMethod('openSettingsIntent', 'android.settings.USAGE_ACCESS_SETTINGS');
} catch (_) {}
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.amber.shade700,
foregroundColor: Colors.white,
),
child: const Text('Open Settings'),
),
],
),
);
}
Widget _buildLiveSensorStatus() {
final activeMic = activeSensors.where((s) => s.sensor == 'microphone').toList();
final activeCam = activeSensors.where((s) => s.sensor == 'camera').toList();
return Row(
children: [
Expanded(child: _buildSensorCard('Microphone', Icons.mic, activeMic)),
const SizedBox(width: 12),
Expanded(child: _buildSensorCard('Camera', Icons.videocam, activeCam)),
],
);
}
Widget _buildSensorCard(String title, IconData icon, List<SensorAccess> activeList) {
final bool isActive = activeList.isNotEmpty;
final Color bgColor = isActive ? Colors.red.shade900 : Colors.green.shade900.withValues(alpha: 0.5);
final Color borderColor = isActive ? Colors.red.shade400 : Colors.green.shade700;
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: bgColor,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: borderColor, width: 2),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(icon, color: Colors.white, size: 20),
const SizedBox(width: 8),
Text(title, style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold)),
if (isActive) ...[
const Spacer(),
Container(
width: 8, height: 8,
decoration: const BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
),
),
]
],
),
const SizedBox(height: 8),
Text(
isActive ? "LIVE — ${activeList.first.appName}" : "No apps listening",
style: const TextStyle(color: Colors.white70, fontSize: 13),
maxLines: 1, overflow: TextOverflow.ellipsis,
)
],
),
);
}
Widget _buildStoryCard(PrivacyStory story) {
Color badgeColor;
switch(story.severity) {
case 'CRITICAL': badgeColor = Colors.red; break;
case 'HIGH': badgeColor = Colors.orange; break;
case 'MEDIUM': badgeColor = Colors.amber; break;
case 'SAFE': badgeColor = Colors.green; break;
default: badgeColor = Colors.grey;
}
final text = selectedLanguage == 'hindi' ? story.hindi : story.english;
return Container(
margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: const Color(0xFF1A1D27),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: const Color(0xFF2D3145)),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.2),
blurRadius: 8,
offset: const Offset(0, 4),
),
],
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(story.emoji, style: const TextStyle(fontSize: 32)),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: badgeColor.withValues(alpha: 0.2),
borderRadius: BorderRadius.circular(4),
border: Border.all(color: badgeColor),
),
child: Text(story.severity, style: TextStyle(color: badgeColor, fontSize: 10, fontWeight: FontWeight.bold)),
),
const SizedBox(height: 8),
Text(
text,
style: const TextStyle(
color: Colors.white,
height: 1.4,
fontSize: 14,
),
),
if (story.packageName.isNotEmpty && story.severity != 'SAFE') ...[
const SizedBox(height: 12),
SizedBox(
height: 32,
child: OutlinedButton(
style: OutlinedButton.styleFrom(
foregroundColor: Colors.white70,
side: const BorderSide(color: Colors.white24),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
onPressed: () async {
const channel = MethodChannel('in.inmodel.premithius/accessibility');
try {
await channel.invokeMethod('launchApp', {'packageName': story.packageName});
} catch (_) {}
},
child: const Text('Open App', style: TextStyle(fontSize: 12)),
),
)
]
],
),
),
],
),
);
}
Widget _buildDataUsageSection() {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: const Color(0xFF1A1D27),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: const Color(0xFF2D3145)),
),
child: Column(
children: dataUsage.take(5).map((usage) {
final maxMb = dataUsage.first.mbSent;
final pct = maxMb > 0 ? (usage.mbSent / maxMb) : 0.0;
return Padding(
padding: const EdgeInsets.only(bottom: 12.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(usage.appName, style: const TextStyle(color: Colors.white, fontSize: 13, fontWeight: FontWeight.bold)),
Text("${usage.mbSent.toStringAsFixed(1)} MB", style: const TextStyle(color: Colors.white70, fontSize: 12)),
],
),
const SizedBox(height: 6),
LinearProgressIndicator(
value: pct,
backgroundColor: const Color(0xFF2D3145),
color: usage.mbSent > 100 ? Colors.redAccent : Colors.blueAccent,
minHeight: 6,
borderRadius: BorderRadius.circular(4),
),
],
),
);
}).toList(),
),
);
}
Widget _sectionHeader(String title) {
return Text(
title,
style: const TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.bold,
letterSpacing: 0.3,
),
);
}
Widget _buildSystemStatusCard() {
final bool vecReady = VectorService.isReady;
final String vecVersion = VectorService.vecVersion;
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: const Color(0xFF1A1D27),
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: const Color(0xFF2D3145),
width: 1,
),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.3),
blurRadius: 12,
offset: const Offset(0, 4),
),
],
),
child: Column(
children: [
_statusRow(
'Layer 1 — India Threat Pack',
true,
'ACTIVE',
),
const Divider(color: Color(0xFF2D3145), height: 16),
_statusRow(
'Layer 2 — Vector Search',
vecReady,
vecReady ? 'ACTIVE' : 'DISABLED',
),
const Divider(color: Color(0xFF2D3145), height: 16),
_statusRow(
'Layer 3 — Random Forest',
true,
'ACTIVE',
),
const Divider(color: Color(0xFF2D3145), height: 16),
_statusRow(
'Layer 4 — MobileBERT',
true,
'ACTIVE',
),
const Divider(color: Color(0xFF2D3145), height: 16),
_infoRow(
Icons.extension,
'sqlite-vec version',
vecReady ? vecVersion : 'not loaded',
vecReady ? Colors.blueAccent : Colors.grey,
),
const Divider(color: Color(0xFF2D3145), height: 16),
_infoRow(
Icons.storage,
'Embeddings in store',
vecReady ? '$_embeddingCount' : '—',
vecReady ? Colors.blueAccent : Colors.grey,
),
const Divider(color: Color(0xFF2D3145), height: 16),
_infoRow(
Icons.bar_chart,
'Usage Stats Permission',
hasPermission ? 'GRANTED' : 'NEEDED',
hasPermission ? Colors.green : Colors.amber,
),
const Divider(color: Color(0xFF2D3145), height: 16),
_infoRow(
Icons.article,
'Privacy Stories',
'${stories.length} generated',
Colors.blueAccent,
),
const Divider(color: Color(0xFF2D3145), height: 16),
_infoRow(
Icons.schedule,
'Last scan',
DateFormat('HH:mm:ss').format(lastScan),
Colors.white70,
),
],
),
);
}
Widget _statusRow(String label, bool isActive, String statusText) {
final Color statusColor =
isActive ? const Color(0xFF00E676) : Colors.grey;
final Color pillBg =
isActive ? const Color(0xFF00E676).withValues(alpha: 0.15) : Colors.grey.withValues(alpha: 0.15);
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
label,
style: const TextStyle(
color: Colors.white70,
fontSize: 14,
),
),
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: pillBg,
borderRadius: BorderRadius.circular(20),
border: Border.all(color: statusColor.withValues(alpha: 0.5)),
),
child: Text(
statusText,
style: TextStyle(
color: statusColor,
fontSize: 12,
fontWeight: FontWeight.bold,
letterSpacing: 0.5,
),
),
),
],
);
}
Widget _infoRow(IconData icon, String label, String value, Color valueColor) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Icon(icon, color: Colors.white54, size: 16),
const SizedBox(width: 8),
Text(
label,
style: const TextStyle(color: Colors.white70, fontSize: 14),
),
],
),
Text(
value,
style: TextStyle(
color: valueColor,
fontSize: 14,
fontWeight: FontWeight.w600,
fontFamily: 'monospace',
),
),
],
);
}
}