import 'dart:convert'; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:kulinar_app/constants.dart'; import 'package:kulinar_app/util/notifications.dart'; import 'package:kulinar_app/widgets/toastbar_widget.dart'; import 'package:kulinar_app/models/data/recipe_data_class.dart'; import 'package:kulinar_app/models/data/settings_data_class.dart'; import 'package:file_picker/file_picker.dart'; import 'package:external_path/external_path.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; enum ExportType { settings, recipes, recipe, full, } /// Handles all the things related to file serialization / deserialization and other file interaction. class FileHandler { /// Serializes the given `data` into a json style File. Sends a confirmation Toast to the given `context` afterwards. static Future serializeFile(BuildContext context, Map data) async { if (await Permission.storage.request().isGranted) { try { final directory = await ExternalPath.getExternalStoragePublicDirectory(ExternalPath.DIRECTORY_DOWNLOADS); final file = File("$directory/Export-${DateTime.now().toUtc().toString().split(" ")[0]}.kulinar"); await file.writeAsString(jsonEncode(await encodeDataAsMap(ExportType.full, data))); ToastBar.showToastBar(context, AppLocalizations.of(context)!.exportSuccess, actionLabel: ""); Notifications.notify(AppLocalizations.of(context)!.exportSuccess, AppLocalizations.of(context)!.tapHint, file.path); } catch (e) { debugPrint("$e"); ToastBar.showToastBar(context, AppLocalizations.of(context)!.exportError, actionLabel: ""); } } } /// Deserializes the given `file` into the returned Map. Sends a confirmation Toast to the given `context` afterwards. static Future deserializeFile(BuildContext context, File file) async { final dynamic content = jsonDecode(await file.readAsString()); SettingsData.decode(content["settings"]); RecipeData.decode(content["recipes"]); ToastBar.showToastBar(context, AppLocalizations.of(context)!.importSuccess, actionLabel: ""); } /// Parses a given possible deserializable `file` for useful information and returns a map of it. static Future> deserializeFileInformation(File file) async { final Map content = jsonDecode(await file.readAsString()); Map map = {}; map["version"] = content["version"]; map["type"] = content["type"].split(".")[1]; map["size"] = "${await file.length()} Bytes"; if (content["type"] == ExportType.full.toString()) { int a = jsonDecode(content["recipes"]).length; int b = jsonDecode(content["settings"]).length; map["entries"] = (a + b).toString(); } return map; } /// Opens the native file picker and returns the picked `.kulinar` file. static Future pickDeserializableFile(BuildContext context) async { if (await Permission.storage.request().isGranted) { try { FilePickerResult? result = await FilePicker.platform.pickFiles(type: FileType.any); //, allowedExtensions: ["kulinar"]); if (result != null) { return File(result.files.single.path!); } } catch (e) { debugPrint("$e"); ToastBar.showToastBar(context, AppLocalizations.of(context)!.importError, actionLabel: ""); } } return null; } /// Encodes the given `data` together with some additional information into a serializable map. static Future> encodeDataAsMap(ExportType type, Map data) async { Map map = {}; map["version"] = cVersion; map["type"] = type.toString(); // TODO: IMPLEMENT: Base64 Images and image count on export map.addAll(data as Map); return map; } /// Decodes the given `map` into /// TODO: IMPLEMENT: decodeExportMap // static Future> decodeMapToData(ExportType type) async {} }