import 'dart:io'; import 'dart:math' as math; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:flutter_svg/svg.dart'; import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart'; import 'package:search_engine/database.dart'; import 'package:search_engine/screens/landing.dart'; import 'package:path_provider/path_provider.dart'; import 'package:filesize/filesize.dart'; import 'package:search_engine/services/config_service.dart'; import 'package:drift/drift.dart' as drift; // Clase para almacenar información de categorías de almacenamiento class StorageCategory { final String name; final IconData icon; final Color color; int size; double percentage; StorageCategory({ required this.name, required this.icon, required this.color, this.size = 0, this.percentage = 0, }); } // Clase para pintar el gráfico circular class StorageChartPainter extends CustomPainter { final List categories; StorageChartPainter(this.categories); @override void paint(Canvas canvas, Size size) { final center = Offset(size.width / 2, size.height / 2); final radius = math.min(size.width, size.height) / 2; final rect = Rect.fromCircle(center: center, radius: radius); double startAngle = -math.pi / 2; for (var category in categories) { if (category.percentage > 0) { final sweepAngle = (category.percentage / 100) * 2 * math.pi; final paint = Paint() ..color = category.color ..style = PaintingStyle.fill; canvas.drawArc(rect, startAngle, sweepAngle, true, paint); startAngle += sweepAngle; } } // Dibujar círculo central canvas.drawCircle( center, radius * 0.6, Paint()..color = Colors.white, ); } @override bool shouldRepaint(covariant CustomPainter oldDelegate) => true; } class ConfigView extends StatefulWidget { const ConfigView({super.key}); @override State createState() => _ConfigViewState(); } class _ConfigViewState extends State { bool hdThumbnail = false; bool pdfDownload = false; bool isSavingHdThumbnail = false; bool isSavingPdfDownload = false; bool isCalculatingCache = false; bool isCleaningCache = false; String selectedLocale = 'es'; String cacheSize = '0 B'; late Future _databaseFuture; final String _version = dotenv.env['VERSION'] ?? '1.0'; // Lista de categorías de almacenamiento final List _categories = [ StorageCategory( name: 'thumbnails', icon: Icons.image, color: Colors.blue, ), StorageCategory( name: 'pdfs', icon: Icons.picture_as_pdf, color: Colors.green, ), StorageCategory( name: 'other', icon: Icons.folder, color: Colors.orange, ), ]; final Map locales = { 'es': 'Español', 'en': 'English', 'pt': 'Português', 'fr': 'Français', 'rw': 'Kinyarwanda', }; @override void initState() { super.initState(); _databaseFuture = Future.value(AppDatabase()); _getConfig(); _calculateCacheSize(); } @override void dispose() { _databaseFuture.then((database) => database.close()); super.dispose(); } Future _calculateCacheSize() async { setState(() { isCalculatingCache = true; }); try { final appDir = await getApplicationDocumentsDirectory(); final searchDir = Directory('${appDir.path}/LGCC_Search'); if (!await searchDir.exists()) { setState(() { cacheSize = '0 B'; isCalculatingCache = false; }); return; } int totalSize = 0; Map categorySizes = { 'thumbnails': 0, 'pdfs': 0, 'other': 0, }; await for (var entity in searchDir.list(recursive: true)) { if (entity is File && !entity.path.contains('/internal/')) { final size = await entity.length(); totalSize += size; if (entity.path.endsWith('.jpg')) { categorySizes['thumbnails'] = (categorySizes['thumbnails'] ?? 0) + size; } else if (entity.path.endsWith('.pdf')) { categorySizes['pdfs'] = (categorySizes['pdfs'] ?? 0) + size; } else { categorySizes['other'] = (categorySizes['other'] ?? 0) + size; } } } // Actualizar tamaños y porcentajes de categorías for (var category in _categories) { category.size = categorySizes[category.name] ?? 0; category.percentage = totalSize > 0 ? (category.size / totalSize) * 100 : 0; } setState(() { cacheSize = filesize(totalSize); isCalculatingCache = false; }); } catch (e) { setState(() { cacheSize = 'Error'; isCalculatingCache = false; }); } } Future _clearCache() async { setState(() { isCleaningCache = true; }); try { final appDir = await getApplicationDocumentsDirectory(); final searchDir = Directory('${appDir.path}/LGCC_Search'); if (await searchDir.exists()) { await for (var entity in searchDir.list(recursive: true)) { if (entity is File && !entity.path.contains('/internal/')) { await entity.delete(); } else if (entity is Directory && !entity.path.contains('/internal/')) { if (await entity.exists() && entity.listSync().isEmpty) { await entity.delete(); } } } } await _calculateCacheSize(); } catch (e) { // Handle error } finally { setState(() { isCleaningCache = false; }); } } // Widget para mostrar el gráfico circular Widget _buildStorageChart() { return SizedBox( height: 200, child: Stack( alignment: Alignment.center, children: [ CustomPaint( size: const Size(200, 200), painter: StorageChartPainter(_categories), ), Column( mainAxisSize: MainAxisSize.min, children: [ Text( cacheSize, style: const TextStyle( fontSize: 24, fontWeight: FontWeight.bold, ), ), Text( 'storage_used'.tr(), style: const TextStyle( fontSize: 14, color: Colors.grey, ), ), ], ), ], ), ); } // Widget para mostrar la lista de categorías Widget _buildCategoryList() { return Column( children: _categories.map((category) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: const EdgeInsets.symmetric(vertical: 8), child: Row( children: [ Container( width: 32, height: 32, decoration: BoxDecoration( color: category.color.withOpacity(0.1), borderRadius: BorderRadius.circular(8), ), child: Center( child: Icon( category.icon, size: 20, ), ), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( category.name.tr(), style: const TextStyle( fontWeight: FontWeight.w500, fontSize: 14, ), ), Text( filesize(category.size), style: TextStyle( color: Colors.grey[600], fontSize: 13, ), ), ], ), const SizedBox(height: 6), LinearProgressIndicator( value: category.percentage / 100, backgroundColor: category.color.withOpacity(0.1), valueColor: AlwaysStoppedAnimation(category.color), minHeight: 4, borderRadius: BorderRadius.circular(2), ), ], ), ), ], ), ), if (_categories.last != category) const SizedBox(height: 8), ], ); }).toList(), ); } Future _getConfig() async { final hd = await ConfigService.getHdThumbnails(); setState(() { hdThumbnail = hd; }); final pdf = await ConfigService.getPdfDownload(); setState(() { pdfDownload = pdf; }); final locale = await ConfigService.getLocale(); setState(() { selectedLocale = locale; }); } Future _updateConfig(String key, dynamic value) async { setState(() { if (key == 'hd_thumbnails') { isSavingHdThumbnail = true; } else if (key == 'pdf_download') { isSavingPdfDownload = true; } }); if (key == 'hd_thumbnails') { await ConfigService.setHdThumbnails(value); } else if (key == 'pdf_download') { await ConfigService.setPdfDownload(value); } else if (key == 'locale') { await ConfigService.setLocale(value); } setState(() { if (key == 'hd_thumbnails') { isSavingHdThumbnail = false; hdThumbnail = value; } else if (key == 'pdf_download') { isSavingPdfDownload = false; pdfDownload = value; } else if (key == 'locale') { selectedLocale = value; } }); } Future _confirmLocaleChange(String newValue) async { final currentContext = context; final bool? confirm = await showDialog( context: currentContext, builder: (BuildContext context) { return AlertDialog( title: Text('change_language'.tr()), content: Text('change_language_confirm'.tr()), actions: [ TextButton( child: Text('no'.tr()), onPressed: () => Navigator.of(context).pop(false), ), TextButton( child: Text('yes'.tr()), onPressed: () => Navigator.of(context).pop(true), ), ], ); }, ); if (confirm == true) { // Mostrar diálogo de progreso if (mounted) { showDialog( context: context, barrierDismissible: false, builder: (BuildContext context) { return AlertDialog( title: Text('updating_data'.tr()), content: Column( mainAxisSize: MainAxisSize.min, children: [ const CircularProgressIndicator(), const SizedBox(height: 16), Text('processing_data'.tr()), ], ), ); }, ); } try { // Actualizar el idioma await _updateConfig('locale', newValue); // Obtener la base de datos final database = await _databaseFuture; // Eliminar traducciones que no correspondan al nuevo idioma await database.customSelect( 'DELETE FROM translations WHERE languages_code != ?', variables: [drift.Variable.withString(newValue)], ).get(); // Reiniciar la fecha para el nuevo idioma await ConfigService.setLastDate(newValue, '0'); if (mounted) { // Cerrar el diálogo de progreso Navigator.of(context).pop(); // Reiniciar la aplicación currentContext.setLocale(Locale(newValue)).then((_) { pushReplacementWithoutNavBar(context, MaterialPageRoute(builder: (context) => const LandingPage())); }); } } catch (e) { if (mounted) { // Cerrar el diálogo de progreso Navigator.of(context).pop(); // Mostrar error ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('sync_error'.tr()), backgroundColor: Colors.red, ), ); } } } } void _confirmClearCache() { showDialog( context: context, builder: (context) => AlertDialog( title: Text('clear_cache'.tr()), content: Text('clear_cache_desc'.tr()), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: Text('no'.tr()), ), TextButton( onPressed: () { Navigator.of(context).pop(); _clearCache(); }, child: Text('yes'.tr()), ), ], ), ); } @override Widget build(BuildContext context) { return Container( decoration: const BoxDecoration( gradient: LinearGradient( colors: [Color(0xFFffffff), Color(0xFFe3ead6)], begin: Alignment.topRight, end: Alignment.bottomLeft, ), ), child: Scaffold( backgroundColor: Colors.transparent, appBar: AppBar( backgroundColor: Colors.transparent, elevation: 0, title: Text( 'config'.tr(), style: const TextStyle( fontSize: 24, fontFamily: 'Outfit', fontWeight: FontWeight.w700, ), ), iconTheme: const IconThemeData(color: Colors.black, size: 20), ), body: SafeArea( child: SingleChildScrollView( padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'storage_usage'.tr(), style: const TextStyle( fontSize: 20, fontWeight: FontWeight.bold, ), ), Text( cacheSize, style: const TextStyle( fontSize: 20, fontWeight: FontWeight.bold, color: Color(0XFF6b8b66), ), ), ], ), const SizedBox(height: 8), Text( 'storage_usage_desc'.tr(), style: const TextStyle( fontSize: 14, color: Colors.grey, ), ), const SizedBox(height: 24), if (isCalculatingCache) const LinearProgressIndicator( backgroundColor: Color(0xFFdce2ca), valueColor: AlwaysStoppedAnimation(Color(0XFF6b8b66)), ) else ...[ _buildCategoryList(), const SizedBox(height: 24), Row( children: [ Expanded( child: Row( children: [ Icon( Icons.high_quality_outlined, size: 20, color: Colors.grey[600], ), const SizedBox(width: 8), Text( 'hd_thumbnails'.tr(), style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w500, ), ), ], ), ), Switch( value: hdThumbnail, onChanged: isSavingHdThumbnail ? null : (val) => _updateConfig('hd_thumbnails', val), activeColor: const Color(0XFF6b8b66), activeTrackColor: const Color(0XFFdce2ca), inactiveThumbColor: Colors.grey, inactiveTrackColor: Colors.grey.shade100, ), ], ), Text( 'hd_thumbnails_desc'.tr(), style: TextStyle( fontSize: 13, color: Colors.grey[600], ), ), const SizedBox(height: 24), SizedBox( width: double.infinity, child: TextButton.icon( onPressed: isCleaningCache ? null : _confirmClearCache, icon: const Icon(Icons.delete_outline), label: Text('clear_cache'.tr()), style: TextButton.styleFrom( foregroundColor: Colors.red, padding: const EdgeInsets.symmetric(vertical: 12), ), ), ), ], const SizedBox(height: 32), DropdownButtonFormField( value: selectedLocale, icon: const Icon(Icons.language, color: Colors.grey), borderRadius: BorderRadius.circular(8), dropdownColor: const Color(0xFFf1f4ea), onChanged: (newValue) { if (newValue != null) { _confirmLocaleChange(newValue); } }, items: locales.entries.map((entry) { return DropdownMenuItem( value: entry.key, child: Text(entry.value), ); }).toList(), decoration: InputDecoration( labelText: 'locale'.tr(), border: const OutlineInputBorder(), ), ), const SizedBox(height: 40), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "title".tr(), style: const TextStyle( fontSize: 52, fontWeight: FontWeight.bold, height: 1, ), ), Text( '${'version'.tr()} $_version', style: const TextStyle( fontSize: 20, fontWeight: FontWeight.w200, ), ), const SizedBox(height: 20), Center( child: SvgPicture.asset( 'assets/svg/logo.svg', height: 30, width: 30, fit: BoxFit.cover, placeholderBuilder: (context) => const CircularProgressIndicator(), semanticsLabel: 'Logo LGCC', ), ), ], ), ], ), ), ), ), ); } }