added minimal error handling and user feedback

This commit is contained in:
2026-01-22 18:09:34 +01:00
parent 27c3804b1e
commit 06c5ca9910
5 changed files with 88 additions and 43 deletions

View File

@@ -30,8 +30,14 @@
"import": "Importieren",
"activateJsonExport": "Immer als JSON speichern",
"@@comment": "Info",
"exportSuccess": "Daten exportiert",
"importSuccess": "Daten importiert",
"@@comment": "Errors",
"errorStoragePermisson": "Zugriff auf Speicher verwehrt",
"errorCouldNotLaunchUrl": "Konnte Url nicht öffnen",
"errorInvalidUrl": "Fehlerhafte Url"
"errorInvalidUrl": "Fehlerhafte Url",
"exportFailed": "Export fehlgeschlagen",
"importFailed": "Import fehlgeschlagen"
}

View File

@@ -30,8 +30,15 @@
"import": "Import",
"activateJsonExport": "Always save to JSON",
"@@comment": "Info",
"exportSuccess": "Exported data",
"importSuccess": "Imported data",
"@@comment": "Errors",
"errorStoragePermisson": "Storage permissions denied",
"errorCouldNotLaunchUrl": "Could not launch Url",
"errorInvalidUrl": "Invalid Url"
"errorInvalidUrl": "Invalid Url",
"exportFailed": "Export failed",
"importFailed": "Import failed"
}

View File

@@ -1,3 +1,5 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';
@@ -43,20 +45,42 @@ class _SettingsPageState extends State<SettingsPage> {
}
void onActivateJsonExportPressed() async {
if (await checkStoragePermission) Storage.exportToJsonFile();
if (!await checkStoragePermission) return;
Storage.exportToJsonFile().then(showExportInfo);
}
void onActivateJsonImportPressed() async {
if (await checkStoragePermission) Storage.importFromJsonFile();
if (!await checkStoragePermission) return;
Storage.importFromJsonFile().then(showImportInfo);
}
Future<bool> get checkStoragePermission async {
if (!(await PermissionService.requestStoragePermission).isGranted) {
if (mounted) {
Notifying.showStoragePermissionErrorSnackbar(context);
Notifying.showErrorSnackbar(
context,
AppLocalizations.of(context)!.errorStoragePermisson,
);
return false;
}
}
return true;
}
void showExportInfo(bool success) => Notifying.showSnackbar(
context,
text: success
? AppLocalizations.of(context)!.exportSuccess
: AppLocalizations.of(context)!.exportFailed,
isError: success,
);
void showImportInfo(bool success) => Notifying.showSnackbar(
context,
text: success
? AppLocalizations.of(context)!.importSuccess
: AppLocalizations.of(context)!.importFailed,
isError: success,
);
}

View File

@@ -13,48 +13,56 @@ class JsonFileService {
required List<Collection> collections,
required List<Bookmark> bookmarks,
}) async {
final dir = await _directoryPath;
try {
final dir = await _directoryPath;
if (dir.isEmpty) return false;
final data = {
'collections': collections.map((c) => c.toJson()).toList(),
'bookmarks': bookmarks.map((b) => b.toJson()).toList(),
};
final json = jsonEncode(data).codeUnits;
final file = XFile.fromData(
Uint8List.fromList(json),
mimeType: 'application/json',
name: constants.jsonFileName,
);
file.saveTo('$dir/${constants.jsonFileName}');
final data = {
'collections': collections.map((c) => c.toJson()).toList(),
'bookmarks': bookmarks.map((b) => b.toJson()).toList(),
};
final json = jsonEncode(data).codeUnits;
final file = XFile.fromData(
Uint8List.fromList(json),
mimeType: 'application/json',
name: constants.jsonFileName,
);
file.saveTo('$dir/${constants.jsonFileName}');
} catch (e) {
return false;
}
return false;
}
static Future<({List<Collection> collections, List<Bookmark> bookmarks})>
importFromJson() async {
const typeGroup = XTypeGroup(label: 'json', extensions: <String>['json']);
final XFile? file = await openFile(
acceptedTypeGroups: <XTypeGroup>[typeGroup],
);
try {
const typeGroup = XTypeGroup(label: 'json', extensions: <String>['json']);
final XFile? file = await openFile(
acceptedTypeGroups: <XTypeGroup>[typeGroup],
);
if (file == null) {
if (file == null) {
return (collections: <Collection>[], bookmarks: <Bookmark>[]);
}
final jsonString = await file.readAsString();
final data = jsonDecode(jsonString) as Map<String, dynamic>;
final collections = (data['collections'] as List<dynamic>? ?? [])
.map((json) => Collection.fromJson(json as Map<String, dynamic>))
.toList();
final bookmarks = (data['bookmarks'] as List<dynamic>? ?? [])
.map((json) => Bookmark.fromJson(json as Map<String, dynamic>))
.toList();
return (collections: collections, bookmarks: bookmarks);
} catch (e) {
return (collections: <Collection>[], bookmarks: <Bookmark>[]);
}
final jsonString = await file.readAsString();
final data = jsonDecode(jsonString) as Map<String, dynamic>;
final collections = (data['collections'] as List<dynamic>? ?? [])
.map((json) => Collection.fromJson(json as Map<String, dynamic>))
.toList();
final bookmarks = (data['bookmarks'] as List<dynamic>? ?? [])
.map((json) => Bookmark.fromJson(json as Map<String, dynamic>))
.toList();
return (collections: collections, bookmarks: bookmarks);
}
static Future<String> get _directoryPath async {

View File

@@ -54,11 +54,11 @@ class Notifying {
showSnackbar(context, text: errorText, isError: true);
}
static void showStoragePermissionErrorSnackbar(BuildContext context) {
showSnackbar(
context,
text: AppLocalizations.of(context)!.errorStoragePermisson,
isError: true,
);
static void showErrorSnackbar(BuildContext context, String message) {
showSnackbar(context, text: message, isError: true);
}
static void showMessageSnackbar(BuildContext context, String message) {
showSnackbar(context, text: message, isError: false);
}
}