668 lines
20 KiB
Dart
668 lines
20 KiB
Dart
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<StorageCategory> 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<ConfigView> createState() => _ConfigViewState();
|
|
}
|
|
|
|
class _ConfigViewState extends State<ConfigView> {
|
|
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<AppDatabase> _databaseFuture;
|
|
final String _version = dotenv.env['VERSION'] ?? '1.0';
|
|
|
|
// Lista de categorías de almacenamiento
|
|
final List<StorageCategory> _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<String, String> 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<void> _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<String, int> 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<void> _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<Color>(category.color),
|
|
minHeight: 4,
|
|
borderRadius: BorderRadius.circular(2),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
if (_categories.last != category) const SizedBox(height: 8),
|
|
],
|
|
);
|
|
}).toList(),
|
|
);
|
|
}
|
|
|
|
Future<void> _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<void> _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<void> _confirmLocaleChange(String newValue) async {
|
|
final currentContext = context;
|
|
final bool? confirm = await showDialog<bool>(
|
|
context: currentContext,
|
|
builder: (BuildContext context) {
|
|
return AlertDialog(
|
|
title: Text('change_language'.tr()),
|
|
content: Text('change_language_confirm'.tr()),
|
|
actions: <Widget>[
|
|
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>(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<String>(
|
|
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<String>(
|
|
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',
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|