aboutsummaryrefslogtreecommitdiff
path: root/lib/util/file_handler.dart
blob: b230e35558d5f9fd6024f3df46c0986d39549b64 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
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<void> serializeFile(BuildContext context, Map<String, dynamic> 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<Map<String, String>> deserializeFileInformation(File file) async {
    final Map<dynamic, dynamic> content = jsonDecode(await file.readAsString());
    Map<String, String> 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<File?> 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<Map<String, String>> encodeDataAsMap(ExportType type, Map<String, dynamic> data) async {
    Map<String, String> map = {};

    map["version"] = cVersion;
    map["type"] = type.toString();
    // TODO: IMPLEMENT: Base64 Images and image count on export
    map.addAll(data as Map<String, String>);

    return map;
  }

  /// Decodes the given `map` into
  /// TODO: IMPLEMENT: decodeExportMap
  // static Future<Map<String, String>> decodeMapToData(ExportType type) async {}
}