mirror of
https://github.com/Mercantec-GHC/h4-projekt-gruppe-0-sm.git
synced 2025-05-11 13:44:06 +02:00
caching for cart
This commit is contained in:
parent
44ef95ba63
commit
de4e91db72
mobile
lib
controllers
main.dartmodels
pages
all_products_page.dartcart_page.dartdashboard.dartfinish_shopping_page.darthome_page.dartproduct_page.dartreceipt_page.dart
settings_pages
server
utils
@ -1,16 +1,38 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mobile/models/cart_item.dart';
|
||||
import 'package:mobile/models/product.dart';
|
||||
import 'package:mobile/results.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
class ProductIdException implements Exception {}
|
||||
|
||||
class CartController extends ChangeNotifier {
|
||||
abstract class CartController extends ChangeNotifier {
|
||||
List<CartItem> allCartItems();
|
||||
CartItem? withProductId(int productId);
|
||||
void incrementAmount(int productId);
|
||||
void decrementAmount(int productId);
|
||||
bool willRemoveOnNextDecrement(int productId);
|
||||
void removeCartItem(int productId);
|
||||
void addToCart(Product product);
|
||||
int totalItemsInCart();
|
||||
int totalPrice();
|
||||
void clearCart();
|
||||
}
|
||||
|
||||
class CartControllerMemory extends CartController {
|
||||
final List<CartItem> cart = [];
|
||||
|
||||
CartControllerMemory();
|
||||
|
||||
@override
|
||||
List<CartItem> allCartItems() {
|
||||
return cart;
|
||||
}
|
||||
|
||||
@override
|
||||
CartItem? withProductId(int productId) {
|
||||
for (var i = 0; i < cart.length; i++) {
|
||||
if (cart[i].product.id == productId) {
|
||||
@ -20,6 +42,7 @@ class CartController extends ChangeNotifier {
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
void incrementAmount(int productId) {
|
||||
final cartItem = withProductId(productId);
|
||||
if (cartItem == null) {
|
||||
@ -29,6 +52,7 @@ class CartController extends ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@override
|
||||
void decrementAmount(int productId) {
|
||||
final cartItem = withProductId(productId);
|
||||
if (cartItem == null) {
|
||||
@ -41,6 +65,7 @@ class CartController extends ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@override
|
||||
bool willRemoveOnNextDecrement(int productId) {
|
||||
final cartItem = withProductId(productId);
|
||||
if (cartItem == null) {
|
||||
@ -49,6 +74,7 @@ class CartController extends ChangeNotifier {
|
||||
return cartItem.amount <= 1;
|
||||
}
|
||||
|
||||
@override
|
||||
void removeCartItem(int productId) {
|
||||
final cartItem = withProductId(productId);
|
||||
if (cartItem == null) {
|
||||
@ -58,6 +84,7 @@ class CartController extends ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@override
|
||||
void addToCart(Product product) {
|
||||
final cartItem = withProductId(product.id);
|
||||
if (cartItem == null) {
|
||||
@ -68,17 +95,20 @@ class CartController extends ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@override
|
||||
int totalItemsInCart() {
|
||||
return cart.fold<int>(0, (prev, cartItem) => prev + cartItem.amount);
|
||||
}
|
||||
|
||||
@override
|
||||
int totalPrice() {
|
||||
return cart.fold<int>(
|
||||
0,
|
||||
(prev, cartItem) =>
|
||||
prev + cartItem.amount * cartItem.product.priceInDkkCents);
|
||||
prev + cartItem.amount * cartItem.product.priceDkkCent);
|
||||
}
|
||||
|
||||
@override
|
||||
void clearCart() {
|
||||
cart.clear();
|
||||
notifyListeners();
|
||||
@ -89,9 +119,63 @@ class CartController extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
class CartItem {
|
||||
final Product product;
|
||||
int amount;
|
||||
class CartControllerCache extends CartControllerMemory {
|
||||
static Future<File> get _cacheFile async {
|
||||
final directory = await getApplicationCacheDirectory();
|
||||
return File("${directory.path}/cart.json").create();
|
||||
}
|
||||
|
||||
CartItem({required this.product, required this.amount});
|
||||
CartControllerCache() {
|
||||
load();
|
||||
}
|
||||
|
||||
void save() async {
|
||||
final json =
|
||||
jsonEncode(cart.map((cartItem) => CartItem.toJson(cartItem)).toList());
|
||||
(await _cacheFile).writeAsString(json);
|
||||
}
|
||||
|
||||
void load() async {
|
||||
final json = await (await _cacheFile).readAsString();
|
||||
print("Loading cache: $json");
|
||||
if (json.isEmpty) {
|
||||
return;
|
||||
}
|
||||
final res = jsonDecode(json);
|
||||
final cartItems = (res as List<dynamic>)
|
||||
.map(((cartItems) => CartItem.fromJson(cartItems)))
|
||||
.toList();
|
||||
cart.insertAll(0, cartItems);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@override
|
||||
void incrementAmount(int productId) {
|
||||
super.incrementAmount(productId);
|
||||
save();
|
||||
}
|
||||
|
||||
@override
|
||||
void decrementAmount(int productId) {
|
||||
super.decrementAmount(productId);
|
||||
save();
|
||||
}
|
||||
|
||||
@override
|
||||
void removeCartItem(int productId) {
|
||||
super.removeCartItem(productId);
|
||||
save();
|
||||
}
|
||||
|
||||
@override
|
||||
void addToCart(Product product) {
|
||||
super.addToCart(product);
|
||||
save();
|
||||
}
|
||||
|
||||
@override
|
||||
void clearCart() {
|
||||
super.clearCart();
|
||||
save();
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ class ProductController extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
get filteredProducts {
|
||||
List<Product> get filteredProducts {
|
||||
if (query.trim().isEmpty) {
|
||||
return products;
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mobile/models/cart_item.dart';
|
||||
import 'package:mobile/models/product.dart';
|
||||
import 'package:mobile/controllers/cart.dart';
|
||||
|
||||
class ReceiptController extends ChangeNotifier {
|
||||
int nextId = 0;
|
||||
@ -80,6 +80,6 @@ class ReceiptItem {
|
||||
ReceiptItem({required this.product, required this.amount});
|
||||
|
||||
int totalPrice() {
|
||||
return product.priceInDkkCents * amount;
|
||||
return product.priceDkkCent * amount;
|
||||
}
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ class MyApp extends StatelessWidget {
|
||||
ChangeNotifierProvider(create: (_) => RoutingController()),
|
||||
ChangeNotifierProvider(
|
||||
create: (_) => ProductController(server: server)),
|
||||
ChangeNotifierProvider(create: (_) => CartController()),
|
||||
ChangeNotifierProvider(create: (_) => CartControllerCache()),
|
||||
ChangeNotifierProvider(create: (_) => ReceiptController()),
|
||||
ChangeNotifierProvider(create: (_) => PayingStateController()),
|
||||
ChangeNotifierProvider(create: (_) => AddToCartStateController()),
|
||||
|
19
mobile/lib/models/cart_item.dart
Normal file
19
mobile/lib/models/cart_item.dart
Normal file
@ -0,0 +1,19 @@
|
||||
import 'package:mobile/models/product.dart';
|
||||
|
||||
class CartItem {
|
||||
final Product product;
|
||||
int amount;
|
||||
|
||||
CartItem({required this.product, required this.amount});
|
||||
|
||||
static Map<String, dynamic> toJson(CartItem cartItem) {
|
||||
return {
|
||||
"product": Product.toJson(cartItem.product),
|
||||
"amount": cartItem.amount
|
||||
};
|
||||
}
|
||||
|
||||
CartItem.fromJson(Map<String, dynamic> json)
|
||||
: product = Product.fromJson(json["product"]),
|
||||
amount = json["amount"];
|
||||
}
|
@ -4,24 +4,35 @@ class Product {
|
||||
final int id;
|
||||
final String name;
|
||||
final String description;
|
||||
final int priceInDkkCents;
|
||||
final int priceDkkCent;
|
||||
final Coordinate? location;
|
||||
final String? barcode;
|
||||
|
||||
Product({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.priceInDkkCents,
|
||||
required this.priceDkkCent,
|
||||
required this.description,
|
||||
this.location,
|
||||
this.barcode,
|
||||
});
|
||||
|
||||
static Map<String, dynamic> toJson(Product product) {
|
||||
return {
|
||||
"id": product.id,
|
||||
"name": product.name,
|
||||
"description": product.description,
|
||||
"price_dkk_cent": product.priceDkkCent,
|
||||
"location": product.location,
|
||||
"barcode": product.barcode,
|
||||
};
|
||||
}
|
||||
|
||||
Product.fromJson(Map<String, dynamic> json)
|
||||
: id = json["id"],
|
||||
name = json["name"],
|
||||
description = json["description"],
|
||||
priceInDkkCents = json["price_dkk_cent"],
|
||||
priceDkkCent = json["price_dkk_cent"],
|
||||
location = null,
|
||||
barcode = json["barcode"];
|
||||
}
|
||||
|
@ -6,30 +6,30 @@ class User {
|
||||
final String name;
|
||||
|
||||
// balance is in øre
|
||||
int balanceInDkkCents;
|
||||
int balanceDkkCents;
|
||||
|
||||
User({
|
||||
required this.id,
|
||||
required this.email,
|
||||
required this.name,
|
||||
required this.balanceInDkkCents,
|
||||
required this.balanceDkkCents,
|
||||
});
|
||||
|
||||
User.fromJson(Map<String, dynamic> json)
|
||||
: email = json["email"],
|
||||
id = json["id"],
|
||||
name = json["name"],
|
||||
balanceInDkkCents = json["balance_dkk_cent"];
|
||||
balanceDkkCents = json["balance_dkk_cent"];
|
||||
|
||||
void addBalanceFounds(int amount) {
|
||||
balanceInDkkCents += amount;
|
||||
balanceDkkCents += amount;
|
||||
}
|
||||
|
||||
Result<int, String> pay(int amount) {
|
||||
if (balanceInDkkCents < amount) {
|
||||
if (balanceDkkCents < amount) {
|
||||
return Err("User can not afford paying amount $amount");
|
||||
}
|
||||
balanceInDkkCents -= amount;
|
||||
return Ok(balanceInDkkCents);
|
||||
balanceDkkCents -= amount;
|
||||
return Ok(balanceDkkCents);
|
||||
}
|
||||
}
|
||||
|
@ -129,7 +129,7 @@ class AllProductsPage extends StatelessWidget {
|
||||
itemBuilder: (_, idx) => ProductListItem(
|
||||
productId: products[idx].id,
|
||||
name: products[idx].name,
|
||||
price: products[idx].priceInDkkCents,
|
||||
price: products[idx].priceDkkCent,
|
||||
productPage: ProductPage(product: products[idx]),
|
||||
product: products[idx],
|
||||
),
|
||||
|
@ -13,7 +13,7 @@ import 'package:mobile/widgets/sized_card.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class CartItemView extends StatelessWidget {
|
||||
final CartController cartRepo;
|
||||
final CartControllerCache cartRepo;
|
||||
final int productId;
|
||||
final String name;
|
||||
final int price;
|
||||
@ -150,7 +150,7 @@ class CartPage extends StatelessWidget {
|
||||
return Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Consumer<CartController>(
|
||||
child: Consumer<CartControllerCache>(
|
||||
builder: (_, cartRepo, __) {
|
||||
final cart = cartRepo.allCartItems();
|
||||
return ListView.builder(
|
||||
@ -159,7 +159,7 @@ class CartPage extends StatelessWidget {
|
||||
cartRepo: cartRepo,
|
||||
productId: cart[idx].product.id,
|
||||
name: cart[idx].product.name,
|
||||
price: cart[idx].product.priceInDkkCents,
|
||||
price: cart[idx].product.priceDkkCent,
|
||||
amount: cart[idx].amount),
|
||||
itemCount: cart.length,
|
||||
);
|
||||
@ -202,9 +202,9 @@ class CartPage extends StatelessWidget {
|
||||
onPressed: () {
|
||||
final productRepo = context
|
||||
.read<ProductController>();
|
||||
final CartController cartRepo =
|
||||
context
|
||||
.read<CartController>();
|
||||
final CartControllerCache
|
||||
cartRepo = context.read<
|
||||
CartControllerCache>();
|
||||
final productResult = productRepo
|
||||
.productWithBarcode(
|
||||
inputController.text);
|
||||
@ -260,8 +260,8 @@ class CartPage extends StatelessWidget {
|
||||
if (!context.mounted) {
|
||||
return;
|
||||
}
|
||||
final CartController cartRepo =
|
||||
context.read<CartController>();
|
||||
final CartControllerCache cartRepo =
|
||||
context.read<CartControllerCache>();
|
||||
final productRepo =
|
||||
context.read<ProductController>();
|
||||
final productResult = productRepo
|
||||
|
@ -23,7 +23,7 @@ class Dashboard extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final pageIndexProvider = Provider.of<RoutingController>(context);
|
||||
int currentIndex = pageIndexProvider.currentIndex;
|
||||
final CartController cartRepo = context.watch<CartController>();
|
||||
final CartControllerCache cartRepo = context.watch<CartControllerCache>();
|
||||
|
||||
return Scaffold(
|
||||
bottomNavigationBar: BottomNavigationBar(
|
||||
|
@ -14,7 +14,8 @@ class FinishShoppingPage extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final CartController cartController = context.read<CartController>();
|
||||
final CartControllerCache cartController =
|
||||
context.read<CartControllerCache>();
|
||||
final ReceiptController receiptRepo = context.read<ReceiptController>();
|
||||
final PayingStateController payingStateRepo =
|
||||
context.watch<PayingStateController>();
|
||||
@ -33,7 +34,7 @@ class FinishShoppingPage extends StatelessWidget {
|
||||
child: ListView.builder(
|
||||
shrinkWrap: true,
|
||||
itemBuilder: (_, idx) => ReceiptItemView(
|
||||
pricePerAmount: cart[idx].product.priceInDkkCents,
|
||||
pricePerAmount: cart[idx].product.priceDkkCent,
|
||||
name: cart[idx].product.name,
|
||||
amount: cart[idx].amount),
|
||||
itemCount: cart.length),
|
||||
|
@ -38,7 +38,7 @@ class HomePage extends StatelessWidget {
|
||||
sessionController: sessionController,
|
||||
placeholder: const CircularProgressIndicator(),
|
||||
builder: (context, user) => Text(
|
||||
"Saldo: ${formatDkkCents(user.balanceInDkkCents)}",
|
||||
"Saldo: ${formatDkkCents(user.balanceDkkCents)}",
|
||||
style: Theme.of(context).textTheme.headlineSmall))),
|
||||
),
|
||||
Expanded(
|
||||
|
@ -38,7 +38,7 @@ class ProductPage extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
Text(
|
||||
formatDkkCents(product.priceInDkkCents),
|
||||
formatDkkCents(product.priceDkkCent),
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
),
|
||||
@ -66,7 +66,7 @@ class ProductPage extends StatelessWidget {
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
Text(
|
||||
formatDkkCents(product.priceInDkkCents),
|
||||
formatDkkCents(product.priceDkkCent),
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
Padding(
|
||||
@ -90,7 +90,7 @@ class ProductPage extends StatelessWidget {
|
||||
duration: const Duration(seconds: 2),
|
||||
);
|
||||
ScaffoldMessenger.of(context).removeCurrentSnackBar();
|
||||
final cartRepo = context.read<CartController>();
|
||||
final cartRepo = context.read<CartControllerCache>();
|
||||
cartRepo.addToCart(product);
|
||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||
},
|
||||
|
@ -29,7 +29,7 @@ class ReceiptView extends StatelessWidget {
|
||||
shrinkWrap: true,
|
||||
itemBuilder: (_, idx) => ReceiptItemView(
|
||||
pricePerAmount:
|
||||
receiptItems[idx].product.priceInDkkCents,
|
||||
receiptItems[idx].product.priceDkkCent,
|
||||
name: receiptItems[idx].product.name,
|
||||
amount: receiptItems[idx].amount),
|
||||
itemCount: receiptItems.length),
|
||||
|
@ -29,7 +29,7 @@ class SaldoSettingsPage extends StatelessWidget {
|
||||
sessionController: sessionController,
|
||||
placeholder: const CircularProgressIndicator(),
|
||||
builder: (context, user) => Text(
|
||||
"Nuværende saldo: ${formatDkkCents(user.balanceInDkkCents)}",
|
||||
"Nuværende saldo: ${formatDkkCents(user.balanceDkkCents)}",
|
||||
style: Theme.of(context).textTheme.bodyLarge),
|
||||
),
|
||||
ElevatedButton.icon(
|
||||
|
@ -11,90 +11,80 @@ class MockServer implements Server {
|
||||
Product(
|
||||
id: nextId++,
|
||||
name: "Minimælk",
|
||||
priceInDkkCents: 1200,
|
||||
priceDkkCent: 1200,
|
||||
description: "Konventionel minimælk med fedtprocent på 0,4%"),
|
||||
Product(
|
||||
id: nextId++,
|
||||
name: "Letmælk",
|
||||
priceInDkkCents: 1300,
|
||||
priceDkkCent: 1300,
|
||||
description: "Konventionel letmælk med fedtprocent på 1,5%",
|
||||
location: Coordinate(x: 1800, y: 100)),
|
||||
Product(
|
||||
id: nextId++,
|
||||
name: "Frilands Øko Supermælk",
|
||||
priceInDkkCents: 2000,
|
||||
priceDkkCent: 2000,
|
||||
description:
|
||||
"Økologisk mælk af frilandskøer med fedtprocent på 3,5%. Ikke homogeniseret eller pasteuriseret. Skaber store muskler og styrker knoglerne 💪"),
|
||||
Product(
|
||||
id: nextId++,
|
||||
name: "Øko Gulerødder 1 kg",
|
||||
priceInDkkCents: 1000,
|
||||
priceDkkCent: 1000,
|
||||
description: ""),
|
||||
Product(
|
||||
id: nextId++,
|
||||
name: "Øko Agurk",
|
||||
priceInDkkCents: 1000,
|
||||
description: ""),
|
||||
id: nextId++, name: "Øko Agurk", priceDkkCent: 1000, description: ""),
|
||||
Product(
|
||||
id: nextId++,
|
||||
name: "Æbler 1 kg",
|
||||
priceInDkkCents: 1000,
|
||||
priceDkkCent: 1000,
|
||||
description: ""),
|
||||
Product(
|
||||
id: nextId++,
|
||||
name: "Basmati Ris",
|
||||
priceInDkkCents: 2000,
|
||||
priceDkkCent: 2000,
|
||||
description: ""),
|
||||
Product(
|
||||
id: nextId++,
|
||||
name: "Haribo Mix",
|
||||
priceInDkkCents: 3000,
|
||||
priceDkkCent: 3000,
|
||||
description: ""),
|
||||
Product(
|
||||
id: nextId++, name: "Smør", priceInDkkCents: 3000, description: ""),
|
||||
Product(id: nextId++, name: "Smør", priceDkkCent: 3000, description: ""),
|
||||
Product(
|
||||
id: nextId++,
|
||||
name: "Harboe Cola",
|
||||
priceInDkkCents: 500,
|
||||
priceDkkCent: 500,
|
||||
description: ""),
|
||||
Product(
|
||||
id: nextId++,
|
||||
barcode: "5060337502900",
|
||||
name: "Monster Energi Drik",
|
||||
priceInDkkCents: 1500,
|
||||
priceDkkCent: 1500,
|
||||
description: ""),
|
||||
Product(
|
||||
id: nextId++,
|
||||
barcode: "5712870659220",
|
||||
name: "Amper Energi Drik",
|
||||
priceInDkkCents: 750,
|
||||
priceDkkCent: 750,
|
||||
description: ""),
|
||||
Product(
|
||||
id: nextId++,
|
||||
barcode: "5710326001937",
|
||||
name: "Danskvand Med Brus",
|
||||
priceInDkkCents: 500,
|
||||
priceDkkCent: 500,
|
||||
description: "Med smag a blåbær"),
|
||||
Product(
|
||||
id: nextId++,
|
||||
name: "Spaghetti",
|
||||
priceInDkkCents: 1000,
|
||||
description: ""),
|
||||
id: nextId++, name: "Spaghetti", priceDkkCent: 1000, description: ""),
|
||||
Product(
|
||||
id: nextId++,
|
||||
name: "Rød Cecil",
|
||||
priceInDkkCents: 6000,
|
||||
description: ""),
|
||||
id: nextId++, name: "Rød Cecil", priceDkkCent: 6000, description: ""),
|
||||
Product(
|
||||
id: nextId++,
|
||||
name: "Jägermeister 750 ml",
|
||||
priceInDkkCents: 12000,
|
||||
priceDkkCent: 12000,
|
||||
description: ""),
|
||||
Product(
|
||||
id: nextId++,
|
||||
barcode: "5711953068881",
|
||||
name: "Protein Chokoladedrik",
|
||||
priceInDkkCents: 1500,
|
||||
priceDkkCent: 1500,
|
||||
description: "Arla's protein chokolade drik der giver store muskler"),
|
||||
]);
|
||||
}
|
||||
@ -128,7 +118,7 @@ class MockServer implements Server {
|
||||
id: 0,
|
||||
email: "test@test.com",
|
||||
name: "testuser",
|
||||
balanceInDkkCents: 10000));
|
||||
balanceDkkCents: 10000));
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -1,6 +1,6 @@
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
String formatDkkCents(int priceInDkkCents) {
|
||||
String formatDkkCents(int priceDkkCents) {
|
||||
final formatter = NumberFormat("###,##0.00", "da_DK");
|
||||
return "${formatter.format(priceInDkkCents / 100.0)} kr";
|
||||
return "${formatter.format(priceDkkCents / 100.0)} kr";
|
||||
}
|
||||
|
@ -268,7 +268,7 @@ packages:
|
||||
source: hosted
|
||||
version: "1.9.1"
|
||||
path_provider:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: path_provider
|
||||
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
|
||||
|
@ -39,6 +39,7 @@ dependencies:
|
||||
google_fonts: ^6.2.1
|
||||
intl: ^0.20.2
|
||||
http: ^1.3.0
|
||||
path_provider: ^2.1.5
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
Loading…
x
Reference in New Issue
Block a user