[local_storage] begin implementation

This commit is contained in:
Piotr Domański 2024-08-03 22:30:07 +02:00
parent ebd001e6ce
commit 1100114040
12 changed files with 254 additions and 52 deletions

View file

@ -9,6 +9,9 @@ PODS:
- FlutterMacOS
- permission_handler_apple (9.3.0):
- Flutter
- sqflite (0.0.3):
- Flutter
- FlutterMacOS
DEPENDENCIES:
- Flutter (from `Flutter`)
@ -16,6 +19,7 @@ DEPENDENCIES:
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
- sqflite (from `.symlinks/plugins/sqflite/darwin`)
EXTERNAL SOURCES:
Flutter:
@ -28,6 +32,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/path_provider_foundation/darwin"
permission_handler_apple:
:path: ".symlinks/plugins/permission_handler_apple/ios"
sqflite:
:path: ".symlinks/plugins/sqflite/darwin"
SPEC CHECKSUMS:
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
@ -35,6 +41,7 @@ SPEC CHECKSUMS:
flutter_secure_storage: d33dac7ae2ea08509be337e775f6b59f1ff45f12
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
PODFILE CHECKSUM: 819463e6a0290f5a72f145ba7cde16e8b6ef0796

View file

@ -6,16 +6,17 @@ class Product {
final double carb;
final double fat;
final double fiber;
final String? barcode;
Product({
required this.id,
Product(
{required this.id,
required this.name,
required this.calories,
required this.protein,
required this.carb,
required this.fat,
required this.fiber,
});
this.barcode});
Product.fromJson(Map<String, dynamic> map)
: id = map['id'] as int,
@ -24,5 +25,19 @@ class Product {
protein = map['protein'] as double,
carb = map['carb'] as double,
fat = map['fat'] as double,
fiber = map['fiber'] as double;
fiber = map['fiber'] as double,
barcode = map['barcode'] as String?;
Map<String, Object?> toMap() {
return {
'id': id,
'name': name,
'calories': calories,
'protein': protein,
'carb': carb,
'fat': fat,
'fiber': fiber,
'barcode': barcode,
};
}
}

View file

@ -9,6 +9,7 @@ import 'package:fooder/components/text.dart';
import 'package:fooder/components/dropdown.dart';
import 'package:fooder/components/floating_action_button.dart';
import 'package:fooder/screens/add_product.dart';
import 'package:fooder/storage/base.dart';
import 'package:simple_barcode_scanner/simple_barcode_scanner.dart';
class AddEntryScreen extends BasedScreen {
@ -50,12 +51,31 @@ class _AddEntryScreen extends BasedState<AddEntryScreen> {
}
Future<void> _getProducts() async {
if (storage != null) {
var storagePorducts = await storage!.product.list();
if (storagePorducts.length > 5) {
print("Using local storage");
setState(() {
products = storagePorducts;
});
return;
}
} else {
print("No local storage");
}
var productsMap =
await widget.apiClient.getProducts(productNameController.text);
setState(() {
products = (productsMap['products'] as List<dynamic>)
var parsedProducts = (productsMap['products'] as List<dynamic>)
.map((e) => Product.fromJson(e as Map<String, dynamic>))
.toList();
await storage!.product.bulkInsert(parsedProducts);
setState(() {
products = parsedProducts;
});
}

View file

@ -4,6 +4,7 @@ import 'package:fooder/components/app_bar.dart';
import 'package:fooder/components/navigation_bar.dart';
import 'package:fooder/screens/login.dart';
import 'package:fooder/screens/main.dart';
import 'package:fooder/storage/base.dart';
TextStyle logoStyle(context) {
return Theme.of(context).textTheme.labelLarge!.copyWith(
@ -13,11 +14,28 @@ TextStyle logoStyle(context) {
abstract class BasedScreen extends StatefulWidget {
final ApiClient apiClient;
final Storage? storage;
const BasedScreen({super.key, required this.apiClient});
const BasedScreen({super.key, required this.apiClient, this.storage});
}
abstract class BasedState<T extends BasedScreen> extends State<T> {
Storage? storage;
@override
void initState() {
super.initState();
_asyncInitState().then((value) => null);
}
Future<void> _asyncInitState() async {
if (widget.storage != null) {
this.storage = widget.storage;
} else {
this.storage = await Storage.create();
}
}
void _logout() async {
await widget.apiClient.logout();
Navigator.pushReplacement(

View file

@ -8,6 +8,7 @@ import 'package:fooder/widgets/meal.dart';
import 'package:fooder/components/sliver.dart';
import 'package:fooder/components/date_picker.dart';
import 'package:fooder/components/floating_action_button.dart';
import 'package:fooder/storage/base.dart';
class MainScreen extends BasedScreen {
const MainScreen({super.key, required super.apiClient});

31
lib/storage/base.dart Normal file
View file

@ -0,0 +1,31 @@
import 'dart:async';
import 'package:flutter/widgets.dart';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
import 'package:fooder/storage/product.dart';
class Storage {
Database db;
ProductStorage product;
Storage({required this.db, required this.product});
static Future<Storage> create() async {
var db = await database();
return Storage(db: db, product: ProductStorage(db: db));
}
static Future<void> createTables(Database db, int version) async {
await ProductStorage.createTable(db, version);
}
static Future<Database> database({String path = "storage.db"}) async {
WidgetsFlutterBinding.ensureInitialized();
return openDatabase(
join(await getDatabasesPath(), path),
onCreate: createTables,
version: 1,
);
}
}

52
lib/storage/product.dart Normal file
View file

@ -0,0 +1,52 @@
import 'dart:async';
import 'package:sqflite/sqflite.dart';
import 'package:fooder/models/product.dart';
class ProductStorage {
Database db;
ProductStorage({required this.db});
static Future<void> createTable(Database db, int version) async {
await db.execute('''
CREATE TABLE product(
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
barcode TEXT,
calories NOT NULL,
protein REAL NOT NULL,
carb REAL NOT NULL,
fat REAL NOT NULL,
fiber REAL NOT NULL
)
''');
}
Future<List<Product>> list() async {
var result = await db.query('product');
return result.map((e) => Product.fromJson(e)).toList();
}
Future<Product?> get(int id) async {
var result = await db.query('product', where: 'id = ?', whereArgs: [id]);
if (result.isEmpty) {
return null;
}
return Product.fromJson(result.first);
}
Future<void> insert(Product product) async {
await db.insert('product', product.toMap(),
conflictAlgorithm: ConflictAlgorithm.replace);
}
Future<void> bulkInsert(List<Product> products) async {
var batch = db.batch();
for (var product in products) {
batch.insert('product', product.toMap(),
conflictAlgorithm: ConflictAlgorithm.replace);
}
await batch.commit(noResult: true);
}
}

View file

@ -7,8 +7,10 @@ import Foundation
import flutter_secure_storage_macos
import path_provider_foundation
import sqflite
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
}

View file

@ -5,11 +5,15 @@ PODS:
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- sqflite (0.0.3):
- Flutter
- FlutterMacOS
DEPENDENCIES:
- flutter_secure_storage_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos`)
- FlutterMacOS (from `Flutter/ephemeral`)
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
- sqflite (from `Flutter/ephemeral/.symlinks/plugins/sqflite/darwin`)
EXTERNAL SOURCES:
flutter_secure_storage_macos:
@ -18,11 +22,14 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral
path_provider_foundation:
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin
sqflite:
:path: Flutter/ephemeral/.symlinks/plugins/sqflite/darwin
SPEC CHECKSUMS:
flutter_secure_storage_macos: d56e2d218c1130b262bef8b4a7d64f88d7f9c9ea
flutter_secure_storage_macos: 59459653abe1adb92abbc8ea747d79f8d19866c9
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367

View file

@ -4,29 +4,52 @@
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Fooder</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIconFile</key>
<string></string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<string>fooder_web</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>NSHumanReadableCopyright</key>
<string>$(PRODUCT_COPYRIGHT)</string>
<key>NSMainNibFile</key>
<string>MainMenu</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>NSPhotoLibraryUsageDescription</key>
<string>App needs access to photo lib for profile images</string>
<key>NSCameraUsageDescription</key>
<string>To capture profile photo please grant camera access</string>
</dict>
</plist>

View file

@ -5,10 +5,10 @@ packages:
dependency: transitive
description:
name: archive
sha256: ecf4273855368121b1caed0d10d4513c7241dfc813f7d3c8933b36622ae9b265
sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d
url: "https://pub.dev"
source: hosted
version: "3.5.1"
version: "3.6.1"
args:
dependency: transitive
description:
@ -178,10 +178,10 @@ packages:
dependency: "direct main"
description:
name: flutter_secure_storage
sha256: c0f402067fb0498934faa6bddd670de0a3db45222e2ca9a068c6177c9a2360a4
sha256: "165164745e6afb5c0e3e3fcc72a012fb9e58496fb26ffb92cf22e16a821e85d0"
url: "https://pub.dev"
source: hosted
version: "9.1.1"
version: "9.2.2"
flutter_secure_storage_linux:
dependency: transitive
description:
@ -194,18 +194,18 @@ packages:
dependency: transitive
description:
name: flutter_secure_storage_macos
sha256: "8cfa53010a294ff095d7be8fa5bb15f2252c50018d69c5104851303f3ff92510"
sha256: "1693ab11121a5f925bbea0be725abfcfbbcf36c1e29e571f84a0c0f436147a81"
url: "https://pub.dev"
source: hosted
version: "3.1.0"
version: "3.1.2"
flutter_secure_storage_platform_interface:
dependency: transitive
description:
name: flutter_secure_storage_platform_interface
sha256: "301f67ee9b87f04aef227f57f13f126fa7b13543c8e7a93f25c5d2d534c28a4a"
sha256: cf91ad32ce5adef6fba4d736a542baca9daf3beac4db2d04be350b87f69ac4a8
url: "https://pub.dev"
source: hosted
version: "1.1.1"
version: "1.1.2"
flutter_secure_storage_web:
dependency: transitive
description:
@ -244,10 +244,10 @@ packages:
dependency: "direct main"
description:
name: http
sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938"
sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010
url: "https://pub.dev"
source: hosted
version: "1.2.1"
version: "1.2.2"
http_parser:
dependency: transitive
description:
@ -260,10 +260,10 @@ packages:
dependency: transitive
description:
name: image
sha256: "4c68bfd5ae83e700b5204c1e74451e7bf3cf750e6843c6e158289cf56bda018e"
sha256: "2237616a36c0d69aef7549ab439b833fb7f9fb9fc861af2cc9ac3eedddd69ca8"
url: "https://pub.dev"
source: hosted
version: "4.1.7"
version: "4.2.0"
intl:
dependency: "direct main"
description:
@ -353,7 +353,7 @@ packages:
source: hosted
version: "1.11.0"
path:
dependency: transitive
dependency: "direct main"
description:
name: path
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
@ -364,10 +364,10 @@ packages:
dependency: transitive
description:
name: path_provider
sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161
sha256: fec0d61223fba3154d87759e3cc27fe2c8dc498f6386c6d6fc80d1afdd1bf378
url: "https://pub.dev"
source: hosted
version: "2.1.3"
version: "2.1.4"
path_provider_android:
dependency: transitive
description:
@ -404,10 +404,10 @@ packages:
dependency: transitive
description:
name: path_provider_windows
sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170"
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
url: "https://pub.dev"
source: hosted
version: "2.2.1"
version: "2.3.0"
permission_handler:
dependency: transitive
description:
@ -420,26 +420,26 @@ packages:
dependency: transitive
description:
name: permission_handler_android
sha256: "8bb852cd759488893805c3161d0b2b5db55db52f773dbb014420b304055ba2c5"
sha256: b29a799ca03be9f999aa6c39f7de5209482d638e6f857f6b93b0875c618b7e54
url: "https://pub.dev"
source: hosted
version: "12.0.6"
version: "12.0.7"
permission_handler_apple:
dependency: transitive
description:
name: permission_handler_apple
sha256: e9ad66020b89ff1b63908f247c2c6f931c6e62699b756ef8b3c4569350cd8662
sha256: e6f6d73b12438ef13e648c4ae56bd106ec60d17e90a59c4545db6781229082a0
url: "https://pub.dev"
source: hosted
version: "9.4.4"
version: "9.4.5"
permission_handler_html:
dependency: transitive
description:
name: permission_handler_html
sha256: "54bf176b90f6eddd4ece307e2c06cf977fb3973719c35a93b85cc7093eb6070d"
sha256: "6cac773d389e045a8d4f85418d07ad58ef9e42a56e063629ce14c4c26344de24"
url: "https://pub.dev"
source: hosted
version: "0.1.1"
version: "0.1.2"
permission_handler_platform_interface:
dependency: transitive
description:
@ -468,10 +468,10 @@ packages:
dependency: transitive
description:
name: platform
sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec"
sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65"
url: "https://pub.dev"
source: hosted
version: "3.1.4"
version: "3.1.5"
plugin_platform_interface:
dependency: transitive
description:
@ -501,6 +501,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.10.0"
sqflite:
dependency: "direct main"
description:
name: sqflite
sha256: a43e5a27235518c03ca238e7b4732cf35eabe863a369ceba6cbefa537a66f16d
url: "https://pub.dev"
source: hosted
version: "2.3.3+1"
sqflite_common:
dependency: transitive
description:
name: sqflite_common
sha256: "3da423ce7baf868be70e2c0976c28a1bb2f73644268b7ffa7d2e08eab71f16a4"
url: "https://pub.dev"
source: hosted
version: "2.5.4"
stack_trace:
dependency: transitive
description:
@ -525,6 +541,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.2.0"
synchronized:
dependency: transitive
description:
name: synchronized
sha256: "539ef412b170d65ecdafd780f924e5be3f60032a1128df156adad6c5b373d558"
url: "https://pub.dev"
source: hosted
version: "3.1.0+1"
term_glyph:
dependency: transitive
description:

View file

@ -44,6 +44,8 @@ dependencies:
blur: ^3.1.0
marquee: ^2.2.3
flutter_launcher_icons: ^0.13.1
sqflite: ^2.3.3+1
path: ^1.9.0
dev_dependencies:
flutter_launcher_icons: ^0.13.1