Implementada la compresión y limpiado un poco el codigo de action_button

parent 498b1565
...@@ -6,4 +6,5 @@ export 'elemento_seleccionable.dart'; ...@@ -6,4 +6,5 @@ export 'elemento_seleccionable.dart';
export 'enlace.dart'; export 'enlace.dart';
export 'formato.dart'; export 'formato.dart';
export 'lista_seleccionables.dart'; export 'lista_seleccionables.dart';
export 'perfil.dart'; export 'perfil.dart';
\ No newline at end of file export 'provider_ajustes.dart';
\ No newline at end of file
import 'dart:io'; import 'dart:io';
import 'package:archive/archive_io.dart';
import 'package:ffmpeg_kit_flutter_new/return_code.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:file_picker/file_picker.dart'; import 'package:file_picker/file_picker.dart';
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
import 'package:prueba_multimedia/modelo/modelo.dart'; import 'package:prueba_multimedia/modelo/modelo.dart';
import 'package:prueba_multimedia/modelo/provider_ajustes.dart';
class ActionButton extends StatelessWidget { class ActionButton extends StatelessWidget {
final ActionButtonTypes tipoBoton; final ActionButtonTypes tipoBoton;
...@@ -23,24 +24,12 @@ class ActionButton extends StatelessWidget { ...@@ -23,24 +24,12 @@ class ActionButton extends StatelessWidget {
required this.providerAjustes required this.providerAjustes
}); });
void Function()? getCallback() {
if(disabled) return null;
return switch(tipoBoton) {
ActionButtonTypes.archivo => archivoAction,
ActionButtonTypes.carpeta => carpetaAction,
ActionButtonTypes.enlace => enlaceAction,
ActionButtonTypes.copiar => copiarAction,
ActionButtonTypes.comprimir => comprimirAction,
ActionButtonTypes.reemplazar => reemplazarAction,
};
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Directionality( return Directionality(
textDirection: TextDirection.rtl, textDirection: TextDirection.rtl,
child: FilledButton.icon( child: FilledButton.icon(
onPressed: getCallback(), onPressed: _getCallback(),
label: Text( label: Text(
tipoBoton.label, tipoBoton.label,
textScaler: const TextScaler.linear(1.2)), textScaler: const TextScaler.linear(1.2)),
...@@ -49,86 +38,69 @@ class ActionButton extends StatelessWidget { ...@@ -49,86 +38,69 @@ class ActionButton extends StatelessWidget {
); );
} }
// ------------------------ ACCIONES ------------------------ //
/// Añade un archivo para convertir
void archivoAction() async { void archivoAction() async {
FilePickerResult? result = await FilePicker.platform.pickFiles(); FilePickerResult? result = await FilePicker.platform.pickFiles();
if (result != null) { if (result == null) {
File file = File(result.files.first.path!); _mostrarSnackBar(context, 'No se ha seleccionado ningún archivo');
final res = manager.addArchivo(file); return;
}
if(res){ File file = File(result.files.first.path!);
if(context.mounted && onSuccess != null){ final added = manager.addArchivo(file);
onSuccess!(); if (!added) {
} _mostrarSnackBar(context, 'El archivo seleccionado no es de un formato multimedia conocido');
} return;
else{ }
if (context.mounted) {
_mostrarSnackBar('El archivo seleccionado no es de un formato multimedia conocido'); if(onSuccess != null && context.mounted){
} onSuccess!();
}
} // Mensaje indicando que se seleccione un archivo
else {
// Comprobar que el widget no ha sido destruido por ser asíncrono
if (context.mounted) {
_mostrarSnackBar('No se ha seleccionado ningún archivo');
}
} }
} }
/// Añade una carpeta para convertir
void carpetaAction() async { void carpetaAction() async {
if(await comprobacionPermisoArchivos(context)){ if(await comprobacionPermisoArchivos(context)){
FilePicker.platform.getDirectoryPath().then( (path) { FilePicker.platform.getDirectoryPath().then((path) {
if (path != null) { if (path == null) {
bool inc = (!providerAjustes.cargando && providerAjustes.incluirSubcarpetas == 0)? true : false; _mostrarSnackBar(context, 'No se ha seleccionado ninguna carpeta');
var directory = Directory(path); return;
final res = manager.addCarpeta(directory, inc); }
if(res){
if(context.mounted && onSuccess != null){ bool inc = (!providerAjustes.cargando &&
onSuccess!(); providerAjustes.incluirSubcarpetas == 0) ? true : false;
} var directory = Directory(path);
} final added = manager.addCarpeta(directory, inc);
else{ if (!added) {
if (context.mounted) { _mostrarSnackBar(context, 'La carpeta seleccionada no contiene archivos de formatos multimedia conoocidos');
_mostrarSnackBar('La carpeta seleccionada no contiene archivos de formatos multimedia conoocidos'); return;
} }
}
} // Mensaje indicando que se seleccione una carpeta if (onSuccess != null && context.mounted) {
else { onSuccess!();
// Comprobar que el widget no ha sido destruido por ser asíncrono
if (context.mounted) {
_mostrarSnackBar('No se ha seleccionado ninguna carpeta');
}
} }
}); });
} }
} }
void enlaceAction() { // TODO: Implementar descarga de archivos
void enlaceAction() {}
}
/// Coloca el resultado de la conversión en la carpeta de salida
void copiarAction() async { void copiarAction() async {
if(await _comprobacionesPreviasConversion(context)){ if(await _comprobacionesPreviasConversion(context)) {
// Averiguamos donde colocar los archivos de salida // Averiguamos donde colocar los archivos de salida
String? directorioSalida; String? directorioSalida = providerAjustes.carpetaSalida.isNotEmpty
if(providerAjustes.carpetaSalida.isNotEmpty){ ? providerAjustes.carpetaSalida
directorioSalida = providerAjustes.carpetaSalida; : await FilePicker.platform.getDirectoryPath();
}
else{
directorioSalida = await FilePicker.platform.getDirectoryPath();
if(directorioSalida != null){
providerAjustes.setCarpetaSalidaTemporal(directorioSalida);
}
}
if(directorioSalida != null){ if(directorioSalida != null){
actualizadorProgreso(); _actualizadorProgreso();
// Convertimos los archivos como tal
List<Archivo> archivos = manager.seleccionables.whereType<Archivo>().toList();
while(manager.seleccionables.isNotEmpty){ while(manager.seleccionables.isNotEmpty){
ElementoSeleccionable sel = manager.seleccionables.first; ElementoSeleccionable sel = manager.seleccionables.first;
if(sel is Carpeta){ if(sel is Carpeta){
for (var archivo in sel.elementosSeleccionados) { for (var archivo in sel.elementosSeleccionados) {
Directory d = await Directory("$directorioSalida/${sel.nombre}").create(); Directory d = await Directory("$directorioSalida/${sel.nombre}").create();
...@@ -145,70 +117,135 @@ class ActionButton extends StatelessWidget { ...@@ -145,70 +117,135 @@ class ActionButton extends StatelessWidget {
} }
} }
/// Igual que copiar pero guarda el resultado en un .zip
void comprimirAction() async { void comprimirAction() async {
if(await _comprobacionesPreviasConversion(context)){ if(await _comprobacionesPreviasConversion(context)) {
// actualizadorProgreso(); // Averiguamos donde colocar los archivos de salida
String? directorioSalida = providerAjustes.carpetaSalida.isNotEmpty
? providerAjustes.carpetaSalida
: await FilePicker.platform.getDirectoryPath();
// TODO: Código de compresión if(directorioSalida != null) {
} final now = DateTime.now();
} final nombreZip = "Convertex_"
"${now.day}-${now.month}-${now.year}_"
"${now.hour}-${now.minute}-${now.second}";
final zipFileEncoder = ZipFileEncoder();
zipFileEncoder.create("$directorioSalida/$nombreZip.zip");
void reemplazarAction() async { _actualizadorProgreso();
if(await _comprobacionesPreviasConversion(context)){
actualizadorProgreso();
// Convertimos los archivos como tal final resultsZip = <Future<void>>[];
List<Archivo> archivos = manager.seleccionables.whereType<Archivo>().toList(); while(manager.seleccionables.isNotEmpty){
for (var archivo in archivos) { ElementoSeleccionable sel = manager.seleccionables.first;
Conversor.getMetadatos(archivo); // Conversion de carpetas
if(sel is Carpeta){
Directory d = await Directory("$directorioSalida/${sel.nombre}").create();
final resultsConversion = <Future<ReturnCode?>>[];
for (var archivo in sel.elementosSeleccionados) {
resultsConversion.add(Conversor.convertir(archivo, d.path));
}
// Esperamos a la conversión y añadimos a zip
for (final result in resultsConversion) {
await result;
}
resultsZip.add(
zipFileEncoder.addDirectory(d)..then((_) => d.delete(recursive: true))
);
}
// Conversion de archivos
else if (sel is Archivo){
// Esperamos a la conversión y añadimos a zip
await Conversor.convertir(sel, directorioSalida);
File tempFile = await File(
"$directorioSalida/${sel.nombre}.${sel.formatoDestino?.name}"
).create();
resultsZip.add(
zipFileEncoder.addFile(tempFile)..then((_) => tempFile.delete())
);
}
// Esperamos a que la compresión se termine y se borren los
// archivos temporales
if (manager.seleccionables.length == 1) {
for (final result in resultsZip) {
await result;
}
zipFileEncoder.closeSync();
}
// Esto hace que la barra de progreso suba
manager.borraSeleccionable(0);
}
} }
} }
} }
void actualizadorProgreso() async { // No se si podremos implementarlo...
void reemplazarAction() async {}
// ------------------------ UTILIDADES ------------------------ //
/// Actualiza la barra de progreso de la conversión de forma asíncrona
void _actualizadorProgreso() async {
manager.iniciarConversion(); manager.iniciarConversion();
int initialSize = manager.seleccionables.length; int initialSize = manager.seleccionables.length;
await Future.delayed(Duration(milliseconds: 200)); await Future.delayed(const Duration(milliseconds: 200));
while(manager.seleccionables.isNotEmpty){ while(manager.seleccionables.isNotEmpty){
manager.actualizarProgreso(initialSize); manager.actualizarProgreso(initialSize);
await Future.delayed(Duration(milliseconds: 200)); await Future.delayed(const Duration(milliseconds: 200));
} }
if(manager.progress < 100){ if(manager.progress < 100){
manager.actualizarProgreso(initialSize); manager.actualizarProgreso(initialSize);
await Future.delayed(Duration(milliseconds: 200)); await Future.delayed(const Duration(milliseconds: 200));
} }
manager.finalizarConversion(); manager.finalizarConversion();
if(this.onSuccess != null){ if(onSuccess != null){
this.onSuccess!(); onSuccess!();
} }
} }
/// Comprueba si tenemos permisos suficientes
// TODO: Añadir aquí una notificación para avisar al usuario de si quiere realmente convertir una carpeta muy grande
Future<bool> _comprobacionesPreviasConversion(BuildContext context) async { Future<bool> _comprobacionesPreviasConversion(BuildContext context) async {
if(!await comprobacionPermisoArchivos(context)){ if(!await comprobacionPermisoArchivos(context)){
return false; return false;
} }
// TODO: Me gustaría añadir aquí una notificación para avisar al usuario de si quiere realmente convertir una carpeta muy grande
return true; return true;
} }
/// Devuelve true si se han concedido los permisos. Si no se han concedido
/// se solicitan, y si se deniegan se devuelve false
static Future<bool> comprobacionPermisoArchivos(BuildContext context) async { static Future<bool> comprobacionPermisoArchivos(BuildContext context) async {
final granted = await Permission.manageExternalStorage.isGranted; final granted = await Permission.manageExternalStorage.isGranted;
if(!granted) { if(!granted) {
final permanently = await Permission.manageExternalStorage.isPermanentlyDenied; final permanently = await Permission.manageExternalStorage.isPermanentlyDenied;
if(permanently) { if(permanently) {
_mostrarNotificacion(context, 'El acceso al almacenamiento del dispositivo ha sido denegado permanentemente.\n' _mostrarNotificacion(context,
'¿Desea abrir la configuración para modificar el permiso?', true); 'El acceso al almacenamiento del dispositivo ha sido denegado permanentemente.\n'
'¿Desea abrir la configuración para modificar el permiso?',
true);
return false; return false;
} }
else { else {
await _mostrarNotificacion(context, 'ConVertex no tiene permiso para acceder a sus archivos. Por favor, para usar esta función, active el permiso a continuación.', false); await _mostrarNotificacion(context,
'ConVertex no tiene permiso para acceder a sus archivos. '
'Por favor, para usar esta función, active el permiso a continuación.',
false);
if(await Permission.manageExternalStorage.request().isDenied){ if(await Permission.manageExternalStorage.request().isDenied){
_mostrarNotificacion(context, 'Se ha denegado el permiso de acceso al almacenamiento. Sin dicho permiso, ConVertex no puede acceder a los archivos dentro de sus carpetas.\n' _mostrarNotificacion(context,
'Por favor, ceda el permiso para usar esta función.', false); 'Se ha denegado el permiso de acceso al almacenamiento. '
'Sin dicho permiso, ConVertex no puede acceder a los archivos dentro de sus carpetas.\n'
'Por favor, ceda el permiso para usar esta función.',
false);
return false; return false;
} }
} }
...@@ -216,60 +253,69 @@ class ActionButton extends StatelessWidget { ...@@ -216,60 +253,69 @@ class ActionButton extends StatelessWidget {
return true; return true;
} }
static Future<void> _mostrarNotificacion(BuildContext context, String text, bool opcion) async{ /// Muestra una notificación solo si el contexto está acoplado
await showDialog( static Future<void> _mostrarNotificacion(BuildContext context, String text, bool opcion) async {
context: context, if (context.mounted) {
builder: (context) { await showDialog(
return AlertDialog( context: context,
content: Text(text), builder: (context) {
actions: [ return AlertDialog(
if(opcion) TextButton( content: Text(text),
onPressed: () { actions: [
Navigator.of(context).pop(); if(opcion) TextButton(
openAppSettings(); onPressed: () {
}, Navigator.of(context).pop();
child: const Text('SÍ'), openAppSettings();
), },
TextButton( child: const Text('SÍ'),
onPressed: () => Navigator.of(context).pop(), ),
child: (opcion)? const Text('NO') : const Text('OK'), TextButton(
) onPressed: () => Navigator.of(context).pop(),
] child: (opcion)? const Text('NO') : const Text('OK'),
); )
} ]
); );
}
);
}
}
/// Muestra el mensaje en la SnackBar solo si el contexto está acoplado
void _mostrarSnackBar(BuildContext context, String text) {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(text))
);
}
} }
void _mostrarSnackBar(String text) { /// Asigna la función correspondiente según el tipo de botón
ScaffoldMessenger.of(context).showSnackBar(SnackBar( void Function()? _getCallback() {
content: Text( if(disabled) return null;
text return switch(tipoBoton) {
) ActionButtonTypes.archivo => archivoAction,
)); ActionButtonTypes.carpeta => carpetaAction,
ActionButtonTypes.enlace => enlaceAction,
ActionButtonTypes.copiar => copiarAction,
ActionButtonTypes.comprimir => comprimirAction,
ActionButtonTypes.reemplazar => reemplazarAction,
};
} }
} }
enum ActionButtonTypes { enum ActionButtonTypes {
archivo('Archivo', Icon(Icons.description_outlined)), archivo('Archivo', Icon(Icons.description_outlined), false),
carpeta('Carpeta', Icon(Icons.folder_outlined)), carpeta('Carpeta', Icon(Icons.folder_outlined), false),
enlace('Enlace', Icon(Icons.link_outlined)), enlace('Enlace', Icon(Icons.link_outlined), false),
copiar('Copiar', Icon(Icons.copy)), copiar('Copiar', Icon(Icons.copy), true),
comprimir('Comprimir', Icon(Icons.splitscreen_outlined)), comprimir('Comprimir', Icon(Icons.splitscreen_outlined), true),
reemplazar('Reemplazar', Icon(Icons.change_circle_outlined)); reemplazar('Reemplazar', Icon(Icons.change_circle_outlined), true);
final String label; final String label;
final Icon icon; final Icon icon;
const ActionButtonTypes(this.label, this.icon); final bool isConvertir;
const ActionButtonTypes(this.label, this.icon, this.isConvertir);
bool isConvertir() {
switch(this){
case ActionButtonTypes.copiar:
case ActionButtonTypes.comprimir:
case ActionButtonTypes.reemplazar:
return true;
default:
return false;
}
}
} }
...@@ -83,7 +83,7 @@ class _ConVertexFabBarState extends State<ConVertexFabBar> { ...@@ -83,7 +83,7 @@ class _ConVertexFabBarState extends State<ConVertexFabBar> {
tipoBoton: type, tipoBoton: type,
manager: manager, manager: manager,
context: context, context: context,
disabled: !(widget.allowConversion) && type.isConvertir(), disabled: !(widget.allowConversion) && type.isConvertir,
onSuccess: widget.onConvertSuccess, onSuccess: widget.onConvertSuccess,
providerAjustes: widget.providerAjustes, providerAjustes: widget.providerAjustes,
); );
......
...@@ -42,6 +42,7 @@ dependencies: ...@@ -42,6 +42,7 @@ dependencies:
open_file: ^3.5.10 open_file: ^3.5.10
ffmpeg_kit_flutter_new: ^1.6.1 ffmpeg_kit_flutter_new: ^1.6.1
shared_preferences: ^2.5.3 shared_preferences: ^2.5.3
archive: ^4.0.7
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment