added persistence using shared preferences

This commit is contained in:
2025-09-19 20:43:38 +02:00
parent 12459bb4cb
commit d0feca1ba8
9 changed files with 301 additions and 15 deletions

2
android/app/proguard-rules.pro vendored Normal file
View File

@@ -0,0 +1,2 @@
-keep class io.flutter.plugins.sharedpreferences.** { *; }
-keep class io.flutter.plugins.sharedpreferences.LegacySharedPreferencesPlugin { *; }

View File

@@ -5,6 +5,7 @@ import android.os.Bundle
import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel
import io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin
class MainActivity: FlutterActivity() { class MainActivity: FlutterActivity() {
private var sharedText: String? = null private var sharedText: String? = null
@@ -31,6 +32,8 @@ class MainActivity: FlutterActivity() {
} }
override fun configureFlutterEngine(flutterEngine: FlutterEngine) { override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
flutterEngine.plugins.add(SharedPreferencesPlugin())
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL) MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
.setMethodCallHandler { call, result -> .setMethodCallHandler { call, result ->
if (call.method == "getSharedText") { if (call.method == "getSharedText") {

3
devtools_options.yaml Normal file
View File

@@ -0,0 +1,3 @@
description: This file stores settings for Dart & Flutter DevTools.
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
extensions:

View File

@@ -1,7 +1,10 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'pages/collections_page.dart'; import 'pages/collections_page.dart';
import 'service/storage.dart';
void main() { void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Storage.initialize();
runApp(const MapsBookmarks()); runApp(const MapsBookmarks());
} }

View File

@@ -1,11 +1,35 @@
class Bookmark { class Bookmark {
Bookmark({required this.name, required this.link}); Bookmark({
required this.collectionId,
required this.name,
required this.link,
required this.description,
int? createdAt,
}) : createdAt = createdAt ?? DateTime.now().millisecondsSinceEpoch;
factory Bookmark.fromJson(Map<String, dynamic> json) => factory Bookmark.fromJson(Map<String, dynamic> json) => Bookmark(
Bookmark(name: json['name'] as String, link: json['link'] as String); collectionId: json['collectionId'] as int,
name: json['name'] as String,
link: json['link'] as String,
description: json['description'] as String,
createdAt: json['createdAt'] as int,
);
int collectionId;
String link; String link;
String name; String name;
String description;
int createdAt;
Map<String, dynamic> toJson() => {'name': name, 'link': link}; int get id => createdAt;
DateTime get createdDate => DateTime.fromMillisecondsSinceEpoch(createdAt);
Map<String, dynamic> toJson() => {
'collectionId': collectionId,
'name': name,
'link': link,
'description': description,
'createdAt': createdAt,
};
} }

View File

@@ -1,10 +1,18 @@
class Collection { class Collection {
Collection({required this.name}); Collection({required this.name, int? createdAt})
: createdAt = createdAt ?? DateTime.now().millisecondsSinceEpoch;
factory Collection.fromJson(Map<String, dynamic> json) => factory Collection.fromJson(Map<String, dynamic> json) => Collection(
Collection(name: json['name'] as String); name: json['name'] as String,
createdAt: json['createdAt'] as int,
);
String name; String name;
int createdAt; // used as Id with millisecondsSinceEpoch
Map<String, dynamic> toJson() => {'name': name}; int get id => createdAt;
DateTime get createdDate => DateTime.fromMillisecondsSinceEpoch(createdAt);
Map<String, dynamic> toJson() => {'name': name, 'createdAt': createdAt};
} }

117
lib/service/storage.dart Normal file
View File

@@ -0,0 +1,117 @@
import 'dart:convert' show jsonDecode, jsonEncode;
import 'package:shared_preferences/shared_preferences.dart';
import '../model/bookmark.dart';
import '../model/collection.dart';
class Storage {
static const String _collectionsKey = 'collections';
static const String _bookmarksKey = 'bookmarks';
static const String _statsKey = 'stats';
static SharedPreferencesWithCache? _prefsWithCache;
static Future<void> initialize() async {
_prefsWithCache = await SharedPreferencesWithCache.create(
cacheOptions: const SharedPreferencesWithCacheOptions(
allowList: <String>{_collectionsKey, _bookmarksKey, _statsKey},
),
);
}
static SharedPreferencesWithCache get _prefs {
if (_prefsWithCache == null) {
throw StateError(
'BookmarkStorage not initialized. Call initialize() first.',
);
}
return _prefsWithCache!;
}
static List<Collection> loadCollections() {
final jsonString = _prefs.getString(_collectionsKey) ?? '[]';
final jsonList = jsonDecode(jsonString) as List;
return jsonList
.map((json) => Collection.fromJson(json as Map<String, dynamic>))
.toList();
}
static Future<void> saveCollections(List<Collection> collections) async {
final jsonList = collections.map((c) => c.toJson()).toList();
await _prefs.setString(_collectionsKey, jsonEncode(jsonList));
}
static List<Bookmark> loadAllBookmarks() {
final jsonString = _prefs.getString(_bookmarksKey) ?? '[]';
final jsonList = jsonDecode(jsonString) as List;
return jsonList
.map((json) => Bookmark.fromJson(json as Map<String, dynamic>))
.toList();
}
static Future<void> saveAllBookmarks(List<Bookmark> bookmarks) async {
final jsonList = bookmarks.map((b) => b.toJson()).toList();
await _prefs.setString(_bookmarksKey, jsonEncode(jsonList));
}
static List<Bookmark> loadBookmarksForCollection(int collectionId) {
final allBookmarks = loadAllBookmarks();
return allBookmarks.where((b) => b.collectionId == collectionId).toList();
}
static Future<void> addBookmark(Bookmark bookmark) async {
final bookmarks = loadAllBookmarks();
bookmarks.add(bookmark);
await saveAllBookmarks(bookmarks);
}
static Future<void> deleteBookmarkById(int bookmarkId) async {
final bookmarks = loadAllBookmarks();
bookmarks.removeWhere((b) => b.id == bookmarkId);
await saveAllBookmarks(bookmarks);
}
static Future<void> deleteBookmarksForCollection(int collectionId) async {
final bookmarks = loadAllBookmarks();
bookmarks.removeWhere((b) => b.collectionId == collectionId);
await saveAllBookmarks(bookmarks);
}
static Future<void> updateBookmarkById(
int bookmarkId, {
String? name,
String? description,
}) async {
final bookmarks = loadAllBookmarks();
final index = bookmarks.indexWhere((b) => b.id == bookmarkId);
if (index == -1) return;
if (name != null) bookmarks[index].name = name;
if (description != null) bookmarks[index].description = description;
await saveAllBookmarks(bookmarks);
}
static Map<String, int> getStats() {
final statsJson = _prefs.getString(_statsKey) ?? '{}';
final stats = jsonDecode(statsJson) as Map<String, dynamic>;
return {
'totalCollections': stats['totalCollections'] ?? 0,
'totalBookmarks': stats['totalBookmarks'] ?? 0,
};
}
static Future<void> updateStats() async {
final collections = loadCollections();
final bookmarks = loadAllBookmarks();
final stats = {
'totalCollections': collections.length,
'totalBookmarks': bookmarks.length,
'lastUpdated': DateTime.now().millisecondsSinceEpoch,
};
await _prefs.setString(_statsKey, jsonEncode(stats));
}
}

View File

@@ -65,6 +65,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.3" version: "1.3.3"
ffi:
dependency: transitive
description:
name: ffi
sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418"
url: "https://pub.dev"
source: hosted
version: "2.1.4"
file:
dependency: transitive
description:
name: file
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
url: "https://pub.dev"
source: hosted
version: "7.0.1"
flutter: flutter:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
@@ -74,15 +90,20 @@ packages:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: flutter_lints name: flutter_lints
sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.0.0" version: "6.0.0"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
flutter_web_plugins:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
leak_tracker: leak_tracker:
dependency: transitive dependency: transitive
description: description:
@@ -111,10 +132,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: lints name: lints
sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 sha256: a5e2b223cb7c9c8efdc663ef484fdd95bb243bff242ef5b13e26883547fce9a0
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.1.1" version: "6.0.0"
matcher: matcher:
dependency: transitive dependency: transitive
description: description:
@@ -147,6 +168,30 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.9.1" version: "1.9.1"
path_provider_linux:
dependency: transitive
description:
name: path_provider_linux
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
url: "https://pub.dev"
source: hosted
version: "2.2.1"
path_provider_platform_interface:
dependency: transitive
description:
name: path_provider_platform_interface
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
url: "https://pub.dev"
source: hosted
version: "2.3.0"
platform: platform:
dependency: transitive dependency: transitive
description: description:
@@ -155,6 +200,70 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.6" version: "3.1.6"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
url: "https://pub.dev"
source: hosted
version: "2.1.8"
shared_preferences:
dependency: "direct main"
description:
name: shared_preferences
sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5"
url: "https://pub.dev"
source: hosted
version: "2.5.3"
shared_preferences_android:
dependency: transitive
description:
name: shared_preferences_android
sha256: a2608114b1ffdcbc9c120eb71a0e207c71da56202852d4aab8a5e30a82269e74
url: "https://pub.dev"
source: hosted
version: "2.4.12"
shared_preferences_foundation:
dependency: transitive
description:
name: shared_preferences_foundation
sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03"
url: "https://pub.dev"
source: hosted
version: "2.5.4"
shared_preferences_linux:
dependency: transitive
description:
name: shared_preferences_linux
sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
shared_preferences_platform_interface:
dependency: transitive
description:
name: shared_preferences_platform_interface
sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
shared_preferences_web:
dependency: transitive
description:
name: shared_preferences_web
sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019
url: "https://pub.dev"
source: hosted
version: "2.4.3"
shared_preferences_windows:
dependency: transitive
description:
name: shared_preferences_windows
sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
sky_engine: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter
@@ -224,6 +333,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "15.0.2" version: "15.0.2"
web:
dependency: transitive
description:
name: web
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
url: "https://pub.dev"
source: hosted
version: "1.1.1"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
sdks: sdks:
dart: ">=3.9.2 <4.0.0" dart: ">=3.9.2 <4.0.0"
flutter: ">=3.18.0-18.0.pre.54" flutter: ">=3.29.0"

View File

@@ -14,12 +14,13 @@ dependencies:
cupertino_icons: ^1.0.8 cupertino_icons: ^1.0.8
android_intent_plus: ^6.0.0 android_intent_plus: ^6.0.0
shared_preferences: ^2.3.2
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
flutter_lints: ^5.0.0 flutter_lints: ^6.0.0
flutter: flutter: