From 632da54311417ff4c5062081f2f85a9e0913a663 Mon Sep 17 00:00:00 2001 From: marco Date: Thu, 22 Jan 2026 16:19:20 +0100 Subject: [PATCH 01/14] added error texts --- lib/l10n/app_de.arb | 7 ++++++- lib/l10n/app_en.arb | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 8a05425..673f391 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -25,5 +25,10 @@ "url": "Url", "description": "Beschreibung", "settings": "Einstellungen", - "activateJsonExport": "Json-Export aktivieren" + "activateJsonExport": "Json-Export aktivieren", + + "@@comment": "Errors", + "errorStoragePermisson": "Zugriff auf Speicher verwehrt", + "errorCouldNotLaunchUrl": "Konnte Url nicht öffnen", + "errorInvalidUrl": "Fehlerhafte Url" } \ No newline at end of file diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index f751235..18aa2d1 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -25,5 +25,10 @@ "url": "Url", "description": "Description", "settings": "Settings", - "activateJsonExport": "Activate json export" + "activateJsonExport": "Activate json export", + + "@@comment": "Errors", + "errorStoragePermisson": "Storage permissions denied", + "errorCouldNotLaunchUrl": "Could not launch Url", + "errorInvalidUrl": "Invalid Url" } \ No newline at end of file -- 2.49.1 From ea961da678017bd42c622d217a30d22a9c418b7d Mon Sep 17 00:00:00 2001 From: marco Date: Thu, 22 Jan 2026 16:19:33 +0100 Subject: [PATCH 02/14] added permisson_handler package --- pubspec.lock | 148 ++++++++++++++++++++++++++++++++++++++++++++++++++- pubspec.yaml | 2 + 2 files changed, 148 insertions(+), 2 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index a69ba4a..577a83b 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -41,6 +41,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.2" + code_assets: + dependency: transitive + description: + name: code_assets + sha256: "83ccdaa064c980b5596c35dd64a8d3ecc68620174ab9b90b6343b753aa721687" + url: "https://pub.dev" + source: hosted + version: "1.0.0" collection: dependency: transitive description: @@ -49,6 +57,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.19.1" + crypto: + dependency: transitive + description: + name: crypto + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf + url: "https://pub.dev" + source: hosted + version: "3.0.7" csslib: dependency: transitive description: @@ -117,6 +133,22 @@ packages: description: flutter source: sdk version: "0.0.0" + glob: + dependency: transitive + description: + name: glob + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de + url: "https://pub.dev" + source: hosted + version: "2.1.3" + hooks: + dependency: transitive + description: + name: hooks + sha256: "5d309c86e7ce34cd8e37aa71cb30cb652d3829b900ab145e4d9da564b31d59f7" + url: "https://pub.dev" + source: hosted + version: "1.0.0" html: dependency: transitive description: @@ -181,6 +213,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.0.0" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" matcher: dependency: transitive description: @@ -213,6 +253,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.4.2" + native_toolchain_c: + dependency: transitive + description: + name: native_toolchain_c + sha256: "89e83885ba09da5fdf2cdacc8002a712ca238c28b7f717910b34bcd27b0d03ac" + url: "https://pub.dev" + source: hosted + version: "0.17.4" nested: dependency: transitive description: @@ -221,6 +269,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" + objective_c: + dependency: transitive + description: + name: objective_c + sha256: "9922a1ad59ac5afb154cc948aa6ded01987a75003651d0a2866afc23f4da624e" + url: "https://pub.dev" + source: hosted + version: "9.2.3" path: dependency: transitive description: @@ -229,6 +285,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.1" + path_provider: + dependency: "direct main" + description: + name: path_provider + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: f2c65e21139ce2c3dad46922be8272bb5963516045659e71bb16e151c93b580e + url: "https://pub.dev" + source: hosted + version: "2.2.22" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699" + url: "https://pub.dev" + source: hosted + version: "2.6.0" path_provider_linux: dependency: transitive description: @@ -253,6 +333,54 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.0" + permission_handler: + dependency: "direct main" + description: + name: permission_handler + sha256: bc917da36261b00137bbc8896bf1482169cd76f866282368948f032c8c1caae1 + url: "https://pub.dev" + source: hosted + version: "12.0.1" + permission_handler_android: + dependency: transitive + description: + name: permission_handler_android + sha256: "1e3bc410ca1bf84662104b100eb126e066cb55791b7451307f9708d4007350e6" + url: "https://pub.dev" + source: hosted + version: "13.0.1" + permission_handler_apple: + dependency: transitive + description: + name: permission_handler_apple + sha256: f000131e755c54cf4d84a5d8bd6e4149e262cc31c5a8b1d698de1ac85fa41023 + url: "https://pub.dev" + source: hosted + version: "9.4.7" + permission_handler_html: + dependency: transitive + description: + name: permission_handler_html + sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24" + url: "https://pub.dev" + source: hosted + version: "0.1.3+5" + permission_handler_platform_interface: + dependency: transitive + description: + name: permission_handler_platform_interface + sha256: eb99b295153abce5d683cac8c02e22faab63e50679b937fa1bf67d58bb282878 + url: "https://pub.dev" + source: hosted + version: "4.3.0" + permission_handler_windows: + dependency: transitive + description: + name: permission_handler_windows + sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e" + url: "https://pub.dev" + source: hosted + version: "0.2.1" platform: dependency: transitive description: @@ -277,6 +405,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.1.5+1" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" + url: "https://pub.dev" + source: hosted + version: "2.2.0" shared_preferences: dependency: "direct main" description: @@ -498,6 +634,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + yaml: + dependency: transitive + description: + name: yaml + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce + url: "https://pub.dev" + source: hosted + version: "3.1.3" sdks: - dart: ">=3.9.2 <4.0.0" - flutter: ">=3.35.0" + dart: ">=3.10.3 <4.0.0" + flutter: ">=3.38.4" diff --git a/pubspec.yaml b/pubspec.yaml index fd4ae02..7a5536f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -21,6 +21,8 @@ dependencies: flutter_localizations: sdk: flutter intl: any + path_provider: ^2.1.5 + permission_handler: ^12.0.1 dev_dependencies: flutter_test: -- 2.49.1 From d02684bb84c69211abd1b8a59e988ad30a99be51 Mon Sep 17 00:00:00 2001 From: marco Date: Thu, 22 Jan 2026 16:19:46 +0100 Subject: [PATCH 03/14] requested storage permission --- android/app/src/main/AndroidManifest.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index a26e2fe..6c04936 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,5 +1,6 @@ + Date: Thu, 22 Jan 2026 16:21:39 +0100 Subject: [PATCH 04/14] updated gitignore --- .gitignore | 154 +++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 137 insertions(+), 17 deletions(-) diff --git a/.gitignore b/.gitignore index 3820a95..feb91f2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,16 +1,22 @@ +# Do not remove or rename entries in this file, only add new ones +# See https://github.com/flutter/flutter/issues/128635 for more context. + # Miscellaneous *.class +*.lock *.log *.pyc *.swp .DS_Store .atom/ -.build/ .buildlog/ .history .svn/ -.swiftpm/ -migrate_working_dir/ + +# As packages are no longer pinned, we use a lockfile for testing locally. +# When unpinning packages, Using lockfiles ensures that failures in PRs are +# actually due to those PRs, not due to a package being updated. +!/pubspec.lock # IntelliJ related *.iml @@ -18,28 +24,142 @@ migrate_working_dir/ *.iws .idea/ -# The .vscode folder contains launch configuration and tasks you configure in -# VS Code which you may wish to be included in version control, so this line -# is commented out by default. -#.vscode/ +# Visual Studio Code related +.classpath +.project +.settings/ +.vscode/* +.ccls-cache + +# This file, on the master branch, should never exist or be checked-in. +# +# On a *final* release branch, that is, what will ship to stable or beta, the +# file can be force added (git add --force) and checked-in in order to effectively +# "pin" the engine artifact version so the flutter tool does not need to use git +# to determine the engine artifacts. +# +# See https://github.com/flutter/flutter/blob/main/docs/tool/Engine-artifacts.md. +/bin/internal/engine.version + +# Flutter repo-specific +/bin/cache/ +/bin/internal/bootstrap.bat +/bin/internal/bootstrap.sh +/bin/internal/engine.realm +/bin/mingit/ +/dev/benchmarks/mega_gallery/ +/dev/bots/.recipe_deps +/dev/bots/android_tools/ +/dev/devicelab/ABresults*.json +/dev/docs/doc/ +/dev/docs/api_docs.zip +/dev/docs/flutter.docs.zip +/dev/docs/lib/ +/dev/docs/pubspec.yaml +/dev/integration_tests/**/xcuserdata +/dev/integration_tests/**/Pods +/packages/flutter/coverage/ +version +analysis_benchmark.json + +# packages file containing multi-root paths +.packages.generated # Flutter/Dart/Pub related **/doc/api/ -**/ios/Flutter/.last_build_id .dart_tool/ .flutter-plugins-dependencies +**/generated_plugin_registrant.dart +.packages +.pub-preload-cache/ .pub-cache/ .pub/ -/build/ -/coverage/ +build/ +flutter_*.png +linked_*.ds +unlinked.ds +unlinked_spec.ds -# Symbolication related +# Android related +**/android/**/gradle-wrapper.jar +.gradle/ +**/android/captures/ +**/android/gradlew +**/android/gradlew.bat +**/android/**/GeneratedPluginRegistrant.java +**/android/key.properties +*.jks +local.properties +**/.cxx/ + +# iOS/XCode related +**/ios/**/*.mode1v3 +**/ios/**/*.mode2v3 +**/ios/**/*.moved-aside +**/ios/**/*.pbxuser +**/ios/**/*.perspectivev3 +**/ios/**/*sync/ +**/ios/**/.sconsign.dblite +**/ios/**/.tags* +**/ios/**/.vagrant/ +**/ios/**/DerivedData/ +**/ios/**/Icon? +**/ios/**/Pods/ +**/ios/**/.symlinks/ +**/ios/**/profile +**/ios/**/xcuserdata +**/ios/.generated/ +**/ios/Flutter/.last_build_id +**/ios/Flutter/App.framework +**/ios/Flutter/Flutter.framework +**/ios/Flutter/Flutter.podspec +**/ios/Flutter/Generated.xcconfig +**/ios/Flutter/ephemeral +**/ios/Flutter/app.flx +**/ios/Flutter/app.zip +**/ios/Flutter/flutter_assets/ +**/ios/Flutter/flutter_export_environment.sh +**/ios/ServiceDefinitions.json +**/ios/Runner/GeneratedPluginRegistrant.* + +# macOS +**/Flutter/ephemeral/ +**/Pods/ +**/macos/Flutter/GeneratedPluginRegistrant.swift +**/macos/Flutter/ephemeral +**/xcuserdata/ + +# Windows +**/windows/flutter/ephemeral/ +**/windows/flutter/generated_plugin_registrant.cc +**/windows/flutter/generated_plugin_registrant.h +**/windows/flutter/generated_plugins.cmake + +# Linux +**/linux/flutter/ephemeral/ +**/linux/flutter/generated_plugin_registrant.cc +**/linux/flutter/generated_plugin_registrant.h +**/linux/flutter/generated_plugins.cmake + +# Coverage +coverage/ + +# Symbols app.*.symbols -# Obfuscation related -app.*.map.json +# Exceptions to above rules. +!**/ios/**/default.mode1v3 +!**/ios/**/default.mode2v3 +!**/ios/**/default.pbxuser +!**/ios/**/default.perspectivev3 +!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages +!/dev/ci/**/Gemfile.lock +!.vscode/settings.json -# Android Studio will place build artifacts here -/android/app/debug -/android/app/profile -/android/app/release +# Monorepo +.cipd +.gclient +.gclient_entries +.python-version +.gclient_previous_custom_vars +.gclient_previous_sync_commits \ No newline at end of file -- 2.49.1 From 8687b7788b99605e24452aed92b672277c8cd598 Mon Sep 17 00:00:00 2001 From: marco Date: Thu, 22 Jan 2026 16:27:02 +0100 Subject: [PATCH 05/14] updated gitignore to ignore generated localization files --- .gitignore | 1 + lib/l10n/app_localizations.dart | 248 ----------------------------- lib/l10n/app_localizations_de.dart | 69 -------- lib/l10n/app_localizations_en.dart | 70 -------- 4 files changed, 1 insertion(+), 387 deletions(-) delete mode 100644 lib/l10n/app_localizations.dart delete mode 100644 lib/l10n/app_localizations_de.dart delete mode 100644 lib/l10n/app_localizations_en.dart diff --git a/.gitignore b/.gitignore index feb91f2..f0c4dec 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ .buildlog/ .history .svn/ +lib/l10n/app_localizations* # As packages are no longer pinned, we use a lockfile for testing locally. # When unpinning packages, Using lockfiles ensures that failures in PRs are diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart deleted file mode 100644 index 860f8ce..0000000 --- a/lib/l10n/app_localizations.dart +++ /dev/null @@ -1,248 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/foundation.dart'; -import 'package:flutter/widgets.dart'; -import 'package:flutter_localizations/flutter_localizations.dart'; -import 'package:intl/intl.dart' as intl; - -import 'app_localizations_de.dart'; -import 'app_localizations_en.dart'; - -// ignore_for_file: type=lint - -/// Callers can lookup localized strings with an instance of AppLocalizations -/// returned by `AppLocalizations.of(context)`. -/// -/// Applications need to include `AppLocalizations.delegate()` in their app's -/// `localizationDelegates` list, and the locales they support in the app's -/// `supportedLocales` list. For example: -/// -/// ```dart -/// import 'l10n/app_localizations.dart'; -/// -/// return MaterialApp( -/// localizationsDelegates: AppLocalizations.localizationsDelegates, -/// supportedLocales: AppLocalizations.supportedLocales, -/// home: MyApplicationHome(), -/// ); -/// ``` -/// -/// ## Update pubspec.yaml -/// -/// Please make sure to update your pubspec.yaml to include the following -/// packages: -/// -/// ```yaml -/// dependencies: -/// # Internationalization support. -/// flutter_localizations: -/// sdk: flutter -/// intl: any # Use the pinned version from flutter_localizations -/// -/// # Rest of dependencies -/// ``` -/// -/// ## iOS Applications -/// -/// iOS applications define key application metadata, including supported -/// locales, in an Info.plist file that is built into the application bundle. -/// To configure the locales supported by your app, you’ll need to edit this -/// file. -/// -/// First, open your project’s ios/Runner.xcworkspace Xcode workspace file. -/// Then, in the Project Navigator, open the Info.plist file under the Runner -/// project’s Runner folder. -/// -/// Next, select the Information Property List item, select Add Item from the -/// Editor menu, then select Localizations from the pop-up menu. -/// -/// Select and expand the newly-created Localizations item then, for each -/// locale your application supports, add a new item and select the locale -/// you wish to add from the pop-up menu in the Value field. This list should -/// be consistent with the languages listed in the AppLocalizations.supportedLocales -/// property. -abstract class AppLocalizations { - AppLocalizations(String locale) - : localeName = intl.Intl.canonicalizedLocale(locale.toString()); - - final String localeName; - - static AppLocalizations? of(BuildContext context) { - return Localizations.of(context, AppLocalizations); - } - - static const LocalizationsDelegate delegate = - _AppLocalizationsDelegate(); - - /// A list of this localizations delegate along with the default localizations - /// delegates. - /// - /// Returns a list of localizations delegates containing this delegate along with - /// GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate, - /// and GlobalWidgetsLocalizations.delegate. - /// - /// Additional delegates can be added by appending to this list in - /// MaterialApp. This list does not have to be used at all if a custom list - /// of delegates is preferred or required. - static const List> localizationsDelegates = - >[ - delegate, - GlobalMaterialLocalizations.delegate, - GlobalCupertinoLocalizations.delegate, - GlobalWidgetsLocalizations.delegate, - ]; - - /// A list of this localizations delegate's supported locales. - static const List supportedLocales = [ - Locale('de'), - Locale('en'), - ]; - - /// No description provided for @addToCollection. - /// - /// In en, this message translates to: - /// **'Add to {collection_name}'** - String addToCollection(String collection_name); - - /// No description provided for @cancel. - /// - /// In en, this message translates to: - /// **'Cancel'** - String get cancel; - - /// No description provided for @chooseCollection. - /// - /// In en, this message translates to: - /// **'Choose Collection'** - String get chooseCollection; - - /// No description provided for @collections. - /// - /// In en, this message translates to: - /// **'Collections'** - String get collections; - - /// No description provided for @tipCreateCollections. - /// - /// In en, this message translates to: - /// **'Create your first Collection to get started!'** - String get tipCreateCollections; - - /// No description provided for @search. - /// - /// In en, this message translates to: - /// **'Search'** - String get search; - - /// No description provided for @createBookmark. - /// - /// In en, this message translates to: - /// **'Create Bookmark'** - String get createBookmark; - - /// No description provided for @createCollection. - /// - /// In en, this message translates to: - /// **'Create Collection'** - String get createCollection; - - /// No description provided for @create. - /// - /// In en, this message translates to: - /// **'Create'** - String get create; - - /// No description provided for @delete. - /// - /// In en, this message translates to: - /// **'Delete'** - String get delete; - - /// No description provided for @add. - /// - /// In en, this message translates to: - /// **'Add'** - String get add; - - /// No description provided for @startSearching. - /// - /// In en, this message translates to: - /// **'Start searching'** - String get startSearching; - - /// No description provided for @tipNoResults. - /// - /// In en, this message translates to: - /// **'There are no results that match your search'** - String get tipNoResults; - - /// No description provided for @collectionName. - /// - /// In en, this message translates to: - /// **'Collection Name'** - String get collectionName; - - /// No description provided for @bookmarkTitle. - /// - /// In en, this message translates to: - /// **'Bookmark Title'** - String get bookmarkTitle; - - /// No description provided for @url. - /// - /// In en, this message translates to: - /// **'Url'** - String get url; - - /// No description provided for @description. - /// - /// In en, this message translates to: - /// **'Description'** - String get description; - - /// No description provided for @settings. - /// - /// In en, this message translates to: - /// **'Settings'** - String get settings; - - /// No description provided for @activateJsonExport. - /// - /// In en, this message translates to: - /// **'Activate json export'** - String get activateJsonExport; -} - -class _AppLocalizationsDelegate - extends LocalizationsDelegate { - const _AppLocalizationsDelegate(); - - @override - Future load(Locale locale) { - return SynchronousFuture(lookupAppLocalizations(locale)); - } - - @override - bool isSupported(Locale locale) => - ['de', 'en'].contains(locale.languageCode); - - @override - bool shouldReload(_AppLocalizationsDelegate old) => false; -} - -AppLocalizations lookupAppLocalizations(Locale locale) { - // Lookup logic when only language code is specified. - switch (locale.languageCode) { - case 'de': - return AppLocalizationsDe(); - case 'en': - return AppLocalizationsEn(); - } - - throw FlutterError( - 'AppLocalizations.delegate failed to load unsupported locale "$locale". This is likely ' - 'an issue with the localizations generation tool. Please file an issue ' - 'on GitHub with a reproducible sample app and the gen-l10n configuration ' - 'that was used.', - ); -} diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart deleted file mode 100644 index 5979058..0000000 --- a/lib/l10n/app_localizations_de.dart +++ /dev/null @@ -1,69 +0,0 @@ -// ignore: unused_import -import 'package:intl/intl.dart' as intl; -import 'app_localizations.dart'; - -// ignore_for_file: type=lint - -/// The translations for German (`de`). -class AppLocalizationsDe extends AppLocalizations { - AppLocalizationsDe([String locale = 'de']) : super(locale); - - @override - String addToCollection(String collection_name) { - return 'Speichern in $collection_name'; - } - - @override - String get cancel => 'Abbrechen'; - - @override - String get chooseCollection => 'Sammlung auswählen'; - - @override - String get collections => 'Sammlungen'; - - @override - String get tipCreateCollections => 'Erstelle deine erste Sammlung!'; - - @override - String get search => 'Suche'; - - @override - String get createBookmark => 'Lesezeichen erstellen'; - - @override - String get createCollection => 'Sammlung erstellen'; - - @override - String get create => 'Erstellen'; - - @override - String get delete => 'Löschen'; - - @override - String get add => 'Hinzufügen'; - - @override - String get startSearching => 'Suche etwas'; - - @override - String get tipNoResults => 'Keine Suchergebnisse gefunden'; - - @override - String get collectionName => 'Name der Sammlung'; - - @override - String get bookmarkTitle => 'Titel des Lesezeichens'; - - @override - String get url => 'Url'; - - @override - String get description => 'Beschreibung'; - - @override - String get settings => 'Einstellungen'; - - @override - String get activateJsonExport => 'Json-Export aktivieren'; -} diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart deleted file mode 100644 index 1ad6476..0000000 --- a/lib/l10n/app_localizations_en.dart +++ /dev/null @@ -1,70 +0,0 @@ -// ignore: unused_import -import 'package:intl/intl.dart' as intl; -import 'app_localizations.dart'; - -// ignore_for_file: type=lint - -/// The translations for English (`en`). -class AppLocalizationsEn extends AppLocalizations { - AppLocalizationsEn([String locale = 'en']) : super(locale); - - @override - String addToCollection(String collection_name) { - return 'Add to $collection_name'; - } - - @override - String get cancel => 'Cancel'; - - @override - String get chooseCollection => 'Choose Collection'; - - @override - String get collections => 'Collections'; - - @override - String get tipCreateCollections => - 'Create your first Collection to get started!'; - - @override - String get search => 'Search'; - - @override - String get createBookmark => 'Create Bookmark'; - - @override - String get createCollection => 'Create Collection'; - - @override - String get create => 'Create'; - - @override - String get delete => 'Delete'; - - @override - String get add => 'Add'; - - @override - String get startSearching => 'Start searching'; - - @override - String get tipNoResults => 'There are no results that match your search'; - - @override - String get collectionName => 'Collection Name'; - - @override - String get bookmarkTitle => 'Bookmark Title'; - - @override - String get url => 'Url'; - - @override - String get description => 'Description'; - - @override - String get settings => 'Settings'; - - @override - String get activateJsonExport => 'Activate json export'; -} -- 2.49.1 From 56daf1b9402847da85c034bfc565be76c386b977 Mon Sep 17 00:00:00 2001 From: marco Date: Thu, 22 Jan 2026 16:27:41 +0100 Subject: [PATCH 06/14] added localization and permission error snackbar --- lib/service/notifying.dart | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/service/notifying.dart b/lib/service/notifying.dart index bb8838a..8e12162 100644 --- a/lib/service/notifying.dart +++ b/lib/service/notifying.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; +import '../l10n/app_localizations.dart'; import 'url_launcher.dart' show UrlLaunchErrorCode; class Notifying { @@ -46,10 +47,18 @@ class Notifying { if (errorCode == UrlLaunchErrorCode.none) { return; } else if (errorCode == UrlLaunchErrorCode.couldNotLaunch) { - errorText = 'Could not launch Url'; + errorText = AppLocalizations.of(context)!.errorCouldNotLaunchUrl; } else { - errorText = 'Invalid Url'; + errorText = AppLocalizations.of(context)!.errorInvalidUrl; } showSnackbar(context, text: errorText, isError: true); } + + static void showStoragePermissionErrorSnackbar(BuildContext context) { + showSnackbar( + context, + text: AppLocalizations.of(context)!.errorStoragePermisson, + isError: true, + ); + } } -- 2.49.1 From b0eebb5ee8db8311c6c1066dcae5c584fd865b7a Mon Sep 17 00:00:00 2001 From: marco Date: Thu, 22 Jan 2026 16:28:05 +0100 Subject: [PATCH 07/14] added permission service --- lib/service/permission_service.dart | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 lib/service/permission_service.dart diff --git a/lib/service/permission_service.dart b/lib/service/permission_service.dart new file mode 100644 index 0000000..726ff54 --- /dev/null +++ b/lib/service/permission_service.dart @@ -0,0 +1,9 @@ +import 'package:permission_handler/permission_handler.dart'; + +class PermissionService { + static Future get storagePermissionStatus => + Permission.manageExternalStorage.status; + + static Future get requestStoragePermission => + Permission.manageExternalStorage.request(); +} -- 2.49.1 From 893a1b558f57f22159c9b2c49f4f5be784a8e8c3 Mon Sep 17 00:00:00 2001 From: marco Date: Thu, 22 Jan 2026 16:44:43 +0100 Subject: [PATCH 08/14] added file picker --- pubspec.lock | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++ pubspec.yaml | 1 + 2 files changed, 65 insertions(+) diff --git a/pubspec.lock b/pubspec.lock index 577a83b..0aca1e2 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -9,6 +9,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.0.0" + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" async: dependency: transitive description: @@ -57,6 +65,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.19.1" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "701dcfc06da0882883a2657c445103380e53e647060ad8d9dfb710c100996608" + url: "https://pub.dev" + source: hosted + version: "0.3.5+1" crypto: dependency: transitive description: @@ -81,6 +97,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.8" + dbus: + dependency: transitive + description: + name: dbus + sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c" + url: "https://pub.dev" + source: hosted + version: "0.7.11" fake_async: dependency: transitive description: @@ -105,6 +129,14 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.1" + file_picker: + dependency: "direct main" + description: + name: file_picker + sha256: d974b6ba2606371ac71dd94254beefb6fa81185bde0b59bdc1df09885da85fde + url: "https://pub.dev" + source: hosted + version: "10.3.8" flutter: dependency: "direct main" description: flutter @@ -123,6 +155,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + sha256: ee8068e0e1cd16c4a82714119918efdeed33b3ba7772c54b5d094ab53f9b7fd1 + url: "https://pub.dev" + source: hosted + version: "2.0.33" flutter_test: dependency: "direct dev" description: flutter @@ -381,6 +421,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.2.1" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: "1a97266a94f7350d30ae522c0af07890c70b8e62c71e8e3920d1db4d23c057d1" + url: "https://pub.dev" + source: hosted + version: "7.0.1" platform: dependency: transitive description: @@ -626,6 +674,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.1" + win32: + dependency: transitive + description: + name: win32 + sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e + url: "https://pub.dev" + source: hosted + version: "5.15.0" xdg_directories: dependency: transitive description: @@ -634,6 +690,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + xml: + dependency: transitive + description: + name: xml + sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" + url: "https://pub.dev" + source: hosted + version: "6.6.1" yaml: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 7a5536f..6fbc8da 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -23,6 +23,7 @@ dependencies: intl: any path_provider: ^2.1.5 permission_handler: ^12.0.1 + file_picker: ^10.3.8 dev_dependencies: flutter_test: -- 2.49.1 From c4fe32e4b1e9b65db5a1d6b2e03049aaf65a6533 Mon Sep 17 00:00:00 2001 From: marco Date: Thu, 22 Jan 2026 16:58:33 +0100 Subject: [PATCH 09/14] replaced file_picker with file_selector --- pubspec.lock | 112 +++++++++++++++++++++++++++------------------------ pubspec.yaml | 2 +- 2 files changed, 61 insertions(+), 53 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 0aca1e2..c4f3755 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -9,14 +9,6 @@ packages: url: "https://pub.dev" source: hosted version: "6.0.0" - args: - dependency: transitive - description: - name: args - sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 - url: "https://pub.dev" - source: hosted - version: "2.7.0" async: dependency: transitive description: @@ -97,14 +89,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.8" - dbus: - dependency: transitive - description: - name: dbus - sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c" - url: "https://pub.dev" - source: hosted - version: "0.7.11" fake_async: dependency: transitive description: @@ -129,14 +113,70 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.1" - file_picker: + file_selector: dependency: "direct main" description: - name: file_picker - sha256: d974b6ba2606371ac71dd94254beefb6fa81185bde0b59bdc1df09885da85fde + name: file_selector + sha256: bd15e43e9268db636b53eeaca9f56324d1622af30e5c34d6e267649758c84d9a url: "https://pub.dev" source: hosted - version: "10.3.8" + version: "1.1.0" + file_selector_android: + dependency: transitive + description: + name: file_selector_android + sha256: "51e8fd0446de75e4b62c065b76db2210c704562d072339d333bd89c57a7f8a7c" + url: "https://pub.dev" + source: hosted + version: "0.5.2+4" + file_selector_ios: + dependency: transitive + description: + name: file_selector_ios + sha256: e2ecf2885c121691ce13b60db3508f53c01f869fb6e8dc5c1cfa771e4c46aeca + url: "https://pub.dev" + source: hosted + version: "0.5.3+5" + file_selector_linux: + dependency: transitive + description: + name: file_selector_linux + sha256: "2567f398e06ac72dcf2e98a0c95df2a9edd03c2c2e0cacd4780f20cdf56263a0" + url: "https://pub.dev" + source: hosted + version: "0.9.4" + file_selector_macos: + dependency: transitive + description: + name: file_selector_macos + sha256: "5e0bbe9c312416f1787a68259ea1505b52f258c587f12920422671807c4d618a" + url: "https://pub.dev" + source: hosted + version: "0.9.5" + file_selector_platform_interface: + dependency: transitive + description: + name: file_selector_platform_interface + sha256: "35e0bd61ebcdb91a3505813b055b09b79dfdc7d0aee9c09a7ba59ae4bb13dc85" + url: "https://pub.dev" + source: hosted + version: "2.7.0" + file_selector_web: + dependency: transitive + description: + name: file_selector_web + sha256: c4c0ea4224d97a60a7067eca0c8fd419e708ff830e0c83b11a48faf566cec3e7 + url: "https://pub.dev" + source: hosted + version: "0.9.4+2" + file_selector_windows: + dependency: transitive + description: + name: file_selector_windows + sha256: "62197474ae75893a62df75939c777763d39c2bc5f73ce5b88497208bc269abfd" + url: "https://pub.dev" + source: hosted + version: "0.9.3+5" flutter: dependency: "direct main" description: flutter @@ -155,14 +195,6 @@ packages: description: flutter source: sdk version: "0.0.0" - flutter_plugin_android_lifecycle: - dependency: transitive - description: - name: flutter_plugin_android_lifecycle - sha256: ee8068e0e1cd16c4a82714119918efdeed33b3ba7772c54b5d094ab53f9b7fd1 - url: "https://pub.dev" - source: hosted - version: "2.0.33" flutter_test: dependency: "direct dev" description: flutter @@ -421,14 +453,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.2.1" - petitparser: - dependency: transitive - description: - name: petitparser - sha256: "1a97266a94f7350d30ae522c0af07890c70b8e62c71e8e3920d1db4d23c057d1" - url: "https://pub.dev" - source: hosted - version: "7.0.1" platform: dependency: transitive description: @@ -674,14 +698,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.1" - win32: - dependency: transitive - description: - name: win32 - sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e - url: "https://pub.dev" - source: hosted - version: "5.15.0" xdg_directories: dependency: transitive description: @@ -690,14 +706,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" - xml: - dependency: transitive - description: - name: xml - sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" - url: "https://pub.dev" - source: hosted - version: "6.6.1" yaml: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 6fbc8da..27f22da 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -23,7 +23,7 @@ dependencies: intl: any path_provider: ^2.1.5 permission_handler: ^12.0.1 - file_picker: ^10.3.8 + file_selector: ^1.1.0 dev_dependencies: flutter_test: -- 2.49.1 From cef23a1c83a73c3d838fd0209018f067d58050ed Mon Sep 17 00:00:00 2001 From: marco Date: Thu, 22 Jan 2026 17:00:23 +0100 Subject: [PATCH 10/14] added constant global values --- lib/assets/constants.dart | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 lib/assets/constants.dart diff --git a/lib/assets/constants.dart b/lib/assets/constants.dart new file mode 100644 index 0000000..76825ad --- /dev/null +++ b/lib/assets/constants.dart @@ -0,0 +1,3 @@ +const String appName = 'Maps Bookmarks'; +const String jsonFileName = 'MapsBookmarksData.json'; +const String defaultAndroidExportDirectory = '/storage/emulated/0/Documents'; -- 2.49.1 From 1029bad20f7303b278025fd006d4ee41c9159897 Mon Sep 17 00:00:00 2001 From: marco Date: Thu, 22 Jan 2026 17:16:37 +0100 Subject: [PATCH 11/14] added localization for settings --- lib/l10n/app_de.arb | 5 ++++- lib/l10n/app_en.arb | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 673f391..ccebefc 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -25,7 +25,10 @@ "url": "Url", "description": "Beschreibung", "settings": "Einstellungen", - "activateJsonExport": "Json-Export aktivieren", + "appData": "App-Daten", + "export": "Exportieren", + "import": "Importieren", + "activateJsonExport": "Immer als JSON speichern", "@@comment": "Errors", "errorStoragePermisson": "Zugriff auf Speicher verwehrt", diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 18aa2d1..f5f64c3 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -25,7 +25,10 @@ "url": "Url", "description": "Description", "settings": "Settings", - "activateJsonExport": "Activate json export", + "appData": "App data", + "export": "Export", + "import": "Import", + "activateJsonExport": "Always save to JSON", "@@comment": "Errors", "errorStoragePermisson": "Storage permissions denied", -- 2.49.1 From debf960d70a5b569a06060313b1ca038f94964b4 Mon Sep 17 00:00:00 2001 From: marco Date: Thu, 22 Jan 2026 17:51:50 +0100 Subject: [PATCH 12/14] simple working json import and export --- lib/pages/settings_page.dart | 49 ++++++++++++++++++++- lib/service/json_file_service.dart | 69 ++++++++++++++++++++++++++++++ lib/service/storage.dart | 17 ++++++++ 3 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 lib/service/json_file_service.dart diff --git a/lib/pages/settings_page.dart b/lib/pages/settings_page.dart index 98cfc8d..606c56c 100644 --- a/lib/pages/settings_page.dart +++ b/lib/pages/settings_page.dart @@ -1,15 +1,62 @@ import 'package:flutter/material.dart'; +import 'package:permission_handler/permission_handler.dart'; import '../l10n/app_localizations.dart'; +import '../service/notifying.dart'; +import '../service/permission_service.dart'; +import '../service/storage.dart'; -class SettingsPage extends StatelessWidget { +class SettingsPage extends StatefulWidget { static const routeName = '/settings'; const SettingsPage({super.key}); + @override + State createState() => _SettingsPageState(); +} + +class _SettingsPageState extends State { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text(AppLocalizations.of(context)!.settings)), + body: SizedBox( + width: MediaQuery.of(context).size.width * 0.9, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + AppLocalizations.of(context)!.appData, + style: Theme.of(context).textTheme.titleLarge, + ), + ElevatedButton( + onPressed: () => onActivateJsonImportPressed(), + child: Text(AppLocalizations.of(context)!.import), + ), + ElevatedButton( + onPressed: () => onActivateJsonExportPressed(), + child: Text(AppLocalizations.of(context)!.export), + ), + ], + ), + ), ); } + + void onActivateJsonExportPressed() async { + if (await checkStoragePermission) Storage.exportToJsonFile(); + } + + void onActivateJsonImportPressed() async { + if (await checkStoragePermission) Storage.importFromJsonFile(); + } + + Future get checkStoragePermission async { + if (!(await PermissionService.requestStoragePermission).isGranted) { + if (mounted) { + Notifying.showStoragePermissionErrorSnackbar(context); + return false; + } + } + return true; + } } diff --git a/lib/service/json_file_service.dart b/lib/service/json_file_service.dart new file mode 100644 index 0000000..56c2159 --- /dev/null +++ b/lib/service/json_file_service.dart @@ -0,0 +1,69 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:file_selector/file_selector.dart'; +import 'package:flutter/services.dart'; + +import '../model/bookmark.dart'; +import '../model/collection.dart'; +import '../assets/constants.dart' as constants; + +class JsonFileService { + static Future exportToJson({ + required List collections, + required List bookmarks, + }) async { + final dir = await _directoryPath; + + 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}'); + + return false; + } + + static Future<({List collections, List bookmarks})> + importFromJson() async { + const typeGroup = XTypeGroup(label: 'json', extensions: ['json']); + final XFile? file = await openFile( + acceptedTypeGroups: [typeGroup], + ); + + if (file == null) { + return (collections: [], bookmarks: []); + } + + final jsonString = await file.readAsString(); + + final data = jsonDecode(jsonString) as Map; + + final collections = (data['collections'] as List? ?? []) + .map((json) => Collection.fromJson(json as Map)) + .toList(); + + final bookmarks = (data['bookmarks'] as List? ?? []) + .map((json) => Bookmark.fromJson(json as Map)) + .toList(); + + return (collections: collections, bookmarks: bookmarks); + } + + static Future get _directoryPath async { + if (Platform.isAndroid) { + return await getDirectoryPath( + initialDirectory: constants.defaultAndroidExportDirectory, + ) ?? + ''; + } + return await getDirectoryPath() ?? ''; + } +} diff --git a/lib/service/storage.dart b/lib/service/storage.dart index ef91ac2..8ae8bac 100644 --- a/lib/service/storage.dart +++ b/lib/service/storage.dart @@ -4,6 +4,7 @@ import 'package:shared_preferences/shared_preferences.dart'; import '../model/bookmark.dart'; import '../model/collection.dart'; +import 'json_file_service.dart'; class Storage { static const String _bookmarksKey = 'bookmarks'; @@ -163,4 +164,20 @@ class Storage { } return _prefsWithCache!; } + + static Future exportToJsonFile() => JsonFileService.exportToJson( + collections: loadCollections(), + bookmarks: loadBookmarks(), + ); + + static Future importFromJsonFile() async { + final import = await JsonFileService.importFromJson(); + + if (import.bookmarks.isNotEmpty || import.collections.isNotEmpty) { + saveBookmarks(import.bookmarks); + saveCollections(import.collections); + return true; + } + return false; + } } -- 2.49.1 From 27c3804b1e222731ba8e55d8a4b41a847dbabd65 Mon Sep 17 00:00:00 2001 From: marco Date: Thu, 22 Jan 2026 17:52:18 +0100 Subject: [PATCH 13/14] removed path provider --- pubspec.lock | 100 ++------------------------------------------------- pubspec.yaml | 1 - 2 files changed, 2 insertions(+), 99 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index c4f3755..d5c5720 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -41,14 +41,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.2" - code_assets: - dependency: transitive - description: - name: code_assets - sha256: "83ccdaa064c980b5596c35dd64a8d3ecc68620174ab9b90b6343b753aa721687" - url: "https://pub.dev" - source: hosted - version: "1.0.0" collection: dependency: transitive description: @@ -65,14 +57,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.3.5+1" - crypto: - dependency: transitive - description: - name: crypto - sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf - url: "https://pub.dev" - source: hosted - version: "3.0.7" csslib: dependency: transitive description: @@ -205,22 +189,6 @@ packages: description: flutter source: sdk version: "0.0.0" - glob: - dependency: transitive - description: - name: glob - sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de - url: "https://pub.dev" - source: hosted - version: "2.1.3" - hooks: - dependency: transitive - description: - name: hooks - sha256: "5d309c86e7ce34cd8e37aa71cb30cb652d3829b900ab145e4d9da564b31d59f7" - url: "https://pub.dev" - source: hosted - version: "1.0.0" html: dependency: transitive description: @@ -285,14 +253,6 @@ packages: url: "https://pub.dev" source: hosted version: "6.0.0" - logging: - dependency: transitive - description: - name: logging - sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 - url: "https://pub.dev" - source: hosted - version: "1.3.0" matcher: dependency: transitive description: @@ -325,14 +285,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.4.2" - native_toolchain_c: - dependency: transitive - description: - name: native_toolchain_c - sha256: "89e83885ba09da5fdf2cdacc8002a712ca238c28b7f717910b34bcd27b0d03ac" - url: "https://pub.dev" - source: hosted - version: "0.17.4" nested: dependency: transitive description: @@ -341,14 +293,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" - objective_c: - dependency: transitive - description: - name: objective_c - sha256: "9922a1ad59ac5afb154cc948aa6ded01987a75003651d0a2866afc23f4da624e" - url: "https://pub.dev" - source: hosted - version: "9.2.3" path: dependency: transitive description: @@ -357,30 +301,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.1" - path_provider: - dependency: "direct main" - description: - name: path_provider - sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" - url: "https://pub.dev" - source: hosted - version: "2.1.5" - path_provider_android: - dependency: transitive - description: - name: path_provider_android - sha256: f2c65e21139ce2c3dad46922be8272bb5963516045659e71bb16e151c93b580e - url: "https://pub.dev" - source: hosted - version: "2.2.22" - path_provider_foundation: - dependency: transitive - description: - name: path_provider_foundation - sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699" - url: "https://pub.dev" - source: hosted - version: "2.6.0" path_provider_linux: dependency: transitive description: @@ -477,14 +397,6 @@ packages: url: "https://pub.dev" source: hosted version: "6.1.5+1" - pub_semver: - dependency: transitive - description: - name: pub_semver - sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" - url: "https://pub.dev" - source: hosted - version: "2.2.0" shared_preferences: dependency: "direct main" description: @@ -706,14 +618,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" - yaml: - dependency: transitive - description: - name: yaml - sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce - url: "https://pub.dev" - source: hosted - version: "3.1.3" sdks: - dart: ">=3.10.3 <4.0.0" - flutter: ">=3.38.4" + dart: ">=3.10.0 <4.0.0" + flutter: ">=3.38.0" diff --git a/pubspec.yaml b/pubspec.yaml index 27f22da..7f935b7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -21,7 +21,6 @@ dependencies: flutter_localizations: sdk: flutter intl: any - path_provider: ^2.1.5 permission_handler: ^12.0.1 file_selector: ^1.1.0 -- 2.49.1 From 06c5ca9910310677922b55838bf0fe5508e284bc Mon Sep 17 00:00:00 2001 From: marco Date: Thu, 22 Jan 2026 18:09:34 +0100 Subject: [PATCH 14/14] added minimal error handling and user feedback --- lib/l10n/app_de.arb | 8 +++- lib/l10n/app_en.arb | 9 +++- lib/pages/settings_page.dart | 30 +++++++++++-- lib/service/json_file_service.dart | 72 +++++++++++++++++------------- lib/service/notifying.dart | 12 ++--- 5 files changed, 88 insertions(+), 43 deletions(-) diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index ccebefc..3e2acdf 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -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" } \ No newline at end of file diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index f5f64c3..41652c7 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -29,9 +29,16 @@ "export": "Export", "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" } \ No newline at end of file diff --git a/lib/pages/settings_page.dart b/lib/pages/settings_page.dart index 606c56c..babf159 100644 --- a/lib/pages/settings_page.dart +++ b/lib/pages/settings_page.dart @@ -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 { } 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 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, + ); } diff --git a/lib/service/json_file_service.dart b/lib/service/json_file_service.dart index 56c2159..95d1162 100644 --- a/lib/service/json_file_service.dart +++ b/lib/service/json_file_service.dart @@ -13,48 +13,56 @@ class JsonFileService { required List collections, required List 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 collections, List bookmarks})> importFromJson() async { - const typeGroup = XTypeGroup(label: 'json', extensions: ['json']); - final XFile? file = await openFile( - acceptedTypeGroups: [typeGroup], - ); + try { + const typeGroup = XTypeGroup(label: 'json', extensions: ['json']); + final XFile? file = await openFile( + acceptedTypeGroups: [typeGroup], + ); - if (file == null) { + if (file == null) { + return (collections: [], bookmarks: []); + } + + final jsonString = await file.readAsString(); + + final data = jsonDecode(jsonString) as Map; + + final collections = (data['collections'] as List? ?? []) + .map((json) => Collection.fromJson(json as Map)) + .toList(); + + final bookmarks = (data['bookmarks'] as List? ?? []) + .map((json) => Bookmark.fromJson(json as Map)) + .toList(); + + return (collections: collections, bookmarks: bookmarks); + } catch (e) { return (collections: [], bookmarks: []); } - - final jsonString = await file.readAsString(); - - final data = jsonDecode(jsonString) as Map; - - final collections = (data['collections'] as List? ?? []) - .map((json) => Collection.fromJson(json as Map)) - .toList(); - - final bookmarks = (data['bookmarks'] as List? ?? []) - .map((json) => Bookmark.fromJson(json as Map)) - .toList(); - - return (collections: collections, bookmarks: bookmarks); } static Future get _directoryPath async { diff --git a/lib/service/notifying.dart b/lib/service/notifying.dart index 8e12162..4870c2b 100644 --- a/lib/service/notifying.dart +++ b/lib/service/notifying.dart @@ -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); } } -- 2.49.1