Spaces:
Running
Running
| import 'dart:convert'; | |
| import 'package:flutter/services.dart'; | |
| import 'package:sqflite/sqflite.dart'; | |
| import 'package:path/path.dart'; | |
| class StorageService { | |
| static final StorageService _instance = StorageService._internal(); | |
| factory StorageService() => _instance; | |
| StorageService._internal(); | |
| Database? _database; | |
| Future<Database> get database async { | |
| if (_database != null) return _database!; | |
| _database = await _initDB(); | |
| return _database!; | |
| } | |
| Future<Database> _initDB() async { | |
| String path = join(await getDatabasesPath(), 'premithius.db'); | |
| return await openDatabase( | |
| path, | |
| version: 3, | |
| onCreate: _createTables, | |
| onUpgrade: _onUpgrade, | |
| ); | |
| } | |
| Future<void> _createTables(Database db, int version) async { | |
| await db.execute(''' | |
| CREATE TABLE keyboard_history ( | |
| id INTEGER PRIMARY KEY AUTOINCREMENT, | |
| app_context TEXT, | |
| original_text TEXT, | |
| action TEXT, | |
| result_text TEXT, | |
| accepted INTEGER DEFAULT 0, | |
| created_at INTEGER | |
| ) | |
| '''); | |
| await db.execute(''' | |
| CREATE TABLE scam_log ( | |
| id INTEGER PRIMARY KEY AUTOINCREMENT, | |
| sender TEXT, | |
| message_preview TEXT, | |
| category TEXT, | |
| confidence REAL, | |
| threat_level TEXT, | |
| user_feedback TEXT, | |
| created_at INTEGER | |
| ) | |
| '''); | |
| await db.execute(''' | |
| CREATE TABLE scam_patterns ( | |
| id INTEGER PRIMARY KEY AUTOINCREMENT, | |
| pattern TEXT NOT NULL, | |
| category TEXT, | |
| hit_count INTEGER DEFAULT 1, | |
| user_confirmed INTEGER DEFAULT 0, | |
| created_at INTEGER | |
| ) | |
| '''); | |
| await db.execute(''' | |
| CREATE TABLE whitelist ( | |
| id INTEGER PRIMARY KEY AUTOINCREMENT, | |
| sender TEXT UNIQUE, | |
| created_at INTEGER | |
| ) | |
| '''); | |
| await db.execute(''' | |
| CREATE TABLE upi_events ( | |
| id INTEGER PRIMARY KEY AUTOINCREMENT, | |
| vpa TEXT, | |
| amount REAL, | |
| fraud_type TEXT, | |
| confidence REAL, | |
| created_at INTEGER | |
| ) | |
| '''); | |
| await db.execute(''' | |
| CREATE TABLE shield_warnings ( | |
| id INTEGER PRIMARY KEY AUTOINCREMENT, | |
| warning_type TEXT, | |
| app_context TEXT, | |
| user_action TEXT, | |
| created_at INTEGER | |
| ) | |
| '''); | |
| await db.execute(''' | |
| CREATE TABLE permission_anomalies ( | |
| id INTEGER PRIMARY KEY AUTOINCREMENT, | |
| app_package TEXT, | |
| permission TEXT, | |
| access_count INTEGER, | |
| time_window TEXT, | |
| severity TEXT, | |
| created_at INTEGER | |
| ) | |
| '''); | |
| await db.execute(''' | |
| CREATE TABLE festival_calendar ( | |
| id INTEGER PRIMARY KEY, | |
| name TEXT, | |
| start_date TEXT, | |
| end_date TEXT, | |
| sensitivity_multiplier REAL | |
| ) | |
| '''); | |
| await _createAppLockTables(db); | |
| await _createVpnTables(db); | |
| await _seedScamDomains(db); | |
| } | |
| Future<void> _onUpgrade(Database db, int oldVersion, int newVersion) async { | |
| if (oldVersion < 2) { | |
| await _createAppLockTables(db); | |
| } | |
| if (oldVersion < 3) { | |
| await _createVpnTables(db); | |
| await _seedScamDomains(db); | |
| } | |
| } | |
| Future<void> _createAppLockTables(Database db) async { | |
| await db.execute(''' | |
| CREATE TABLE IF NOT EXISTS locked_apps ( | |
| id INTEGER PRIMARY KEY AUTOINCREMENT, | |
| package_name TEXT UNIQUE NOT NULL, | |
| app_name TEXT NOT NULL, | |
| auth_method TEXT DEFAULT 'biometric', | |
| locked_at INTEGER NOT NULL, | |
| access_attempts INTEGER DEFAULT 0, | |
| temporary_unlock_until INTEGER DEFAULT 0 | |
| ) | |
| '''); | |
| await db.execute(''' | |
| CREATE TABLE IF NOT EXISTS access_log ( | |
| id INTEGER PRIMARY KEY AUTOINCREMENT, | |
| package_name TEXT NOT NULL, | |
| app_name TEXT, | |
| was_allowed INTEGER DEFAULT 0, | |
| attempted_at INTEGER NOT NULL | |
| ) | |
| '''); | |
| } | |
| // --- Helpers for Scam Logging --- | |
| Future<int> insertScamLog(Map<String, dynamic> row) async { | |
| Database db = await database; | |
| return await db.insert('scam_log', row); | |
| } | |
| Future<List<Map<String, dynamic>>> getScamLogs() async { | |
| Database db = await database; | |
| return await db.query('scam_log', orderBy: 'created_at DESC'); | |
| } | |
| // --- Helpers for Pattern Learning --- | |
| Future<int> insertKeyboardLog(Map<String, dynamic> row) async { | |
| Database db = await database; | |
| return await db.insert('keyboard_history', row); | |
| } | |
| Future<Map<String, dynamic>?> checkLocalPattern(String text) async { | |
| Database db = await database; | |
| // VERY simplified exact phrase matching for hackathon scale. | |
| // Real implementation would do tokenized LIKE matching. | |
| final List<Map<String, dynamic>> maps = await db.query('scam_patterns'); | |
| for (var row in maps) { | |
| if (text.toLowerCase().contains((row['pattern'] as String).toLowerCase())) { | |
| return row; | |
| } | |
| } | |
| return null; | |
| } | |
| // --- App Lock Helpers --- | |
| Future<void> insertLockedApp({ | |
| required String packageName, | |
| required String appName, | |
| String authMethod = 'biometric', | |
| }) async { | |
| final db = await database; | |
| await db.insert( | |
| 'locked_apps', | |
| { | |
| 'package_name': packageName, | |
| 'app_name': appName, | |
| 'auth_method': authMethod, | |
| 'locked_at': DateTime.now().millisecondsSinceEpoch, | |
| 'access_attempts': 0, | |
| 'temporary_unlock_until': 0, | |
| }, | |
| conflictAlgorithm: ConflictAlgorithm.replace, | |
| ); | |
| } | |
| Future<void> removeLockedApp(String packageName) async { | |
| final db = await database; | |
| await db.delete('locked_apps', where: 'package_name = ?', whereArgs: [packageName]); | |
| } | |
| Future<List<Map<String, dynamic>>> getLockedApps() async { | |
| final db = await database; | |
| return db.query('locked_apps', orderBy: 'locked_at DESC'); | |
| } | |
| Future<bool> isAppLocked(String packageName) async { | |
| final db = await database; | |
| final rows = await db.query( | |
| 'locked_apps', | |
| where: 'package_name = ?', | |
| whereArgs: [packageName], | |
| ); | |
| if (rows.isEmpty) return false; | |
| final tempUntil = rows.first['temporary_unlock_until'] as int? ?? 0; | |
| return tempUntil == 0 || DateTime.now().millisecondsSinceEpoch > tempUntil; | |
| } | |
| Future<void> setTemporaryUnlock(String packageName, {required DateTime expires}) async { | |
| final db = await database; | |
| await db.update( | |
| 'locked_apps', | |
| {'temporary_unlock_until': expires.millisecondsSinceEpoch}, | |
| where: 'package_name = ?', | |
| whereArgs: [packageName], | |
| ); | |
| } | |
| Future<void> logAccessAttempt({ | |
| required String packageName, | |
| required bool wasAllowed, | |
| String? appName, | |
| }) async { | |
| final db = await database; | |
| await db.insert('access_log', { | |
| 'package_name': packageName, | |
| 'app_name': appName, | |
| 'was_allowed': wasAllowed ? 1 : 0, | |
| 'attempted_at': DateTime.now().millisecondsSinceEpoch, | |
| }); | |
| } | |
| Future<int> getAccessAttemptsTodayCount(String packageName) async { | |
| final db = await database; | |
| final todayStart = DateTime.now() | |
| .subtract(const Duration(hours: 24)) | |
| .millisecondsSinceEpoch; | |
| final result = await db.rawQuery( | |
| 'SELECT COUNT(*) as cnt FROM access_log WHERE package_name = ? AND attempted_at > ? AND was_allowed = 0', | |
| [packageName, todayStart], | |
| ); | |
| return result.first['cnt'] as int? ?? 0; | |
| } | |
| Future<void> _createVpnTables(Database db) async { | |
| await db.execute(''' | |
| CREATE TABLE IF NOT EXISTS scam_domains ( | |
| id INTEGER PRIMARY KEY AUTOINCREMENT, | |
| domain TEXT UNIQUE NOT NULL, | |
| category TEXT NOT NULL, | |
| source TEXT NOT NULL, | |
| hit_count INTEGER DEFAULT 0, | |
| last_hit INTEGER, | |
| created_at INTEGER NOT NULL | |
| ) | |
| '''); | |
| await db.execute(''' | |
| CREATE TABLE IF NOT EXISTS vpn_events ( | |
| id INTEGER PRIMARY KEY AUTOINCREMENT, | |
| event_type TEXT NOT NULL, | |
| domain TEXT, | |
| category TEXT, | |
| app_package TEXT, | |
| created_at INTEGER NOT NULL | |
| ) | |
| '''); | |
| } | |
| Future<void> _seedScamDomains(Database db) async { | |
| try { | |
| final String contents = await rootBundle.loadString('assets/blocklist/india_scam_domains.txt'); | |
| final lines = const LineSplitter().convert(contents); | |
| Batch batch = db.batch(); | |
| String currentCategory = 'Known Scam Domain'; | |
| for (var line in lines) { | |
| final clean = line.trim().toLowerCase(); | |
| if (clean.isEmpty) continue; | |
| if (clean.startsWith('#')) { | |
| currentCategory = clean.substring(1).trim(); | |
| continue; | |
| } | |
| batch.insert('scam_domains', { | |
| 'domain': clean, | |
| 'category': currentCategory, | |
| 'source': 'seed', | |
| 'hit_count': 0, | |
| 'created_at': DateTime.now().millisecondsSinceEpoch, | |
| }, conflictAlgorithm: ConflictAlgorithm.ignore); | |
| } | |
| await batch.commit(noResult: true); | |
| } catch (e) { | |
| print('Error seeding scam domains: $e'); | |
| } | |
| } | |
| } | |