146 lines
4.9 KiB
Dart
146 lines
4.9 KiB
Dart
import 'dart:io';
|
|
|
|
import 'package:file_picker/file_picker.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:path_provider/path_provider.dart';
|
|
import 'package:provider/provider.dart';
|
|
import 'package:share_plus/share_plus.dart' show Share, XFile;
|
|
|
|
import '../../estado/estado_radio.dart';
|
|
import '../../l10n/gen/app_localizations.dart';
|
|
import '../../widgets/pluri_glass_surface.dart';
|
|
import '../../widgets/pluri_layout.dart';
|
|
import '../../widgets/pluri_push_scaffold.dart';
|
|
|
|
/// APLICACIÓN group · "Copia de seguridad" (design ADR-3). Body moved
|
|
/// verbatim from the former `_SeccionBackup` in `pantalla_ajustes.dart` —
|
|
/// only the panel header's icon and title were removed (the pushed screen's
|
|
/// title now carries them); every method below is unchanged.
|
|
class PantallaAjustesBackup extends StatelessWidget {
|
|
const PantallaAjustesBackup({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final l10n = AppLocalizations.of(context);
|
|
return PluriPushScaffold(
|
|
title: l10n.backupSectionTitle,
|
|
body: ListView(
|
|
padding: PluriLayout.pageContentPadding,
|
|
children: const [_CuerpoBackup()],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _CuerpoBackup extends StatelessWidget {
|
|
const _CuerpoBackup();
|
|
|
|
Future<void> _exportar(BuildContext context) async {
|
|
final l10n = AppLocalizations.of(context);
|
|
try {
|
|
final estado = context.read<EstadoRadio>();
|
|
// JSON serialization is owned by ServicioExportImport (S4-R4).
|
|
final json = await estado.exportarConfigJson();
|
|
|
|
final dir = await getTemporaryDirectory();
|
|
final file = File('${dir.path}/pluriwave-backup.json');
|
|
await file.writeAsString(json);
|
|
|
|
await Share.shareXFiles(
|
|
[XFile(file.path)],
|
|
subject: l10n.backupShareSubject,
|
|
text: l10n.backupShareText(DateTime.now().toLocal()),
|
|
);
|
|
} catch (e) {
|
|
if (context.mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(content: Text(l10n.backupExportError(e.toString()))),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
Future<void> _importar(BuildContext context) async {
|
|
final l10n = AppLocalizations.of(context);
|
|
try {
|
|
final result = await FilePicker.platform.pickFiles(
|
|
type: FileType.custom,
|
|
allowedExtensions: ['json'],
|
|
);
|
|
if (result == null || result.files.single.path == null) return;
|
|
|
|
final file = File(result.files.single.path!);
|
|
final contenido = await file.readAsString();
|
|
if (!context.mounted) return;
|
|
// Parsing is owned by ServicioExportImport (S4-R4): null = malformed.
|
|
final json = context.read<EstadoRadio>().parsearConfigJson(contenido);
|
|
if (json == null) {
|
|
throw const FormatException('invalid backup file');
|
|
}
|
|
|
|
if (context.mounted) {
|
|
final confirmar = await showDialog<bool>(
|
|
context: context,
|
|
builder:
|
|
(ctx) => AlertDialog(
|
|
title: Text(AppLocalizations.of(ctx).backupImportTitle),
|
|
content: Text(
|
|
AppLocalizations.of(ctx).backupImportConfirmMessage,
|
|
),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(ctx, false),
|
|
child: Text(AppLocalizations.of(ctx).cancelAction),
|
|
),
|
|
FilledButton(
|
|
onPressed: () => Navigator.pop(ctx, true),
|
|
child: Text(AppLocalizations.of(ctx).backupImportTitle),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
if (confirmar != true) return;
|
|
if (context.mounted) {
|
|
final estado = context.read<EstadoRadio>();
|
|
final messenger = ScaffoldMessenger.of(context);
|
|
await estado.importarConfig(json);
|
|
messenger.showSnackBar(
|
|
SnackBar(content: Text(l10n.backupImportSuccess)),
|
|
);
|
|
}
|
|
}
|
|
} catch (e) {
|
|
if (context.mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(content: Text(l10n.backupImportError(e.toString()))),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return PluriGlassSurface(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
ListTile(
|
|
contentPadding: EdgeInsets.zero,
|
|
leading: const Icon(Icons.upload_outlined),
|
|
title: Text(AppLocalizations.of(context).backupExportTitle),
|
|
subtitle: Text(AppLocalizations.of(context).backupExportSubtitle),
|
|
onTap: () => _exportar(context),
|
|
),
|
|
ListTile(
|
|
contentPadding: EdgeInsets.zero,
|
|
leading: const Icon(Icons.download_outlined),
|
|
title: Text(AppLocalizations.of(context).backupImportTitle),
|
|
subtitle: Text(AppLocalizations.of(context).backupImportSubtitle),
|
|
onTap: () => _importar(context),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|