add firebase, add screen flow

This commit is contained in:
Phuoc Nguyen
2025-12-03 11:07:33 +07:00
parent 9fb4ba621b
commit cae04b3ae7
22 changed files with 504 additions and 131 deletions

View File

@@ -16,8 +16,8 @@ abstract class CartRemoteDataSource {
/// Add items to cart
///
/// [items] - List of items with item_id, quantity, and amount
/// Returns list of cart items from API
Future<List<CartItemModel>> addToCart({
/// Returns true if successful
Future<bool> addToCart({
required List<Map<String, dynamic>> items,
});
@@ -47,7 +47,7 @@ class CartRemoteDataSourceImpl implements CartRemoteDataSource {
final DioClient _dioClient;
@override
Future<List<CartItemModel>> addToCart({
Future<bool> addToCart({
required List<Map<String, dynamic>> items,
}) async {
try {
@@ -78,8 +78,7 @@ class CartRemoteDataSourceImpl implements CartRemoteDataSource {
throw const ParseException('Invalid response format from add to cart API');
}
// After adding, fetch updated cart
return await getUserCart();
return true;
} on DioException catch (e) {
throw _handleDioException(e);
} catch (e) {

View File

@@ -32,7 +32,7 @@ class CartRepositoryImpl implements CartRepository {
final CartLocalDataSource _localDataSource;
@override
Future<List<CartItem>> addToCart({
Future<bool> addToCart({
required List<String> itemIds,
required List<double> quantities,
required List<double> prices,
@@ -57,17 +57,24 @@ class CartRepositoryImpl implements CartRepository {
// Try API first
try {
final cartItemModels = await _remoteDataSource.addToCart(items: items);
final success = await _remoteDataSource.addToCart(items: items);
// Sync to local storage
await _localDataSource.saveCartItems(cartItemModels);
// Also save to local storage for offline access
if (success) {
for (int i = 0; i < itemIds.length; i++) {
final cartItemModel = _createCartItemModel(
productId: itemIds[i],
quantity: quantities[i],
unitPrice: prices[i],
);
await _localDataSource.addCartItem(cartItemModel);
}
}
// Convert to domain entities
return cartItemModels.map(_modelToEntity).toList();
return success;
} on NetworkException catch (e) {
// If no internet, add to local cart only
if (e is NoInternetException || e is TimeoutException) {
// Add items to local cart
for (int i = 0; i < itemIds.length; i++) {
final cartItemModel = _createCartItemModel(
productId: itemIds[i],
@@ -79,9 +86,7 @@ class CartRepositoryImpl implements CartRepository {
// TODO: Queue for sync when online
// Return local cart items
final localItems = await _localDataSource.getCartItems();
return localItems.map(_modelToEntity).toList();
return true;
}
rethrow;
}
@@ -167,7 +172,7 @@ class CartRepositoryImpl implements CartRepository {
}
@override
Future<List<CartItem>> updateQuantity({
Future<bool> updateQuantity({
required String itemId,
required double quantity,
required double price,

View File

@@ -22,14 +22,13 @@ import 'package:worker/features/cart/domain/entities/cart_item.dart';
abstract class CartRepository {
/// Add items to cart
///
/// [items] - List of cart items to add
/// [itemIds] - Product ERPNext item codes
/// [quantities] - Quantities for each item
/// [prices] - Unit prices for each item
///
/// Returns list of cart items on success.
/// Returns true if successful.
/// Throws exceptions on failure.
Future<List<CartItem>> addToCart({
Future<bool> addToCart({
required List<String> itemIds,
required List<double> quantities,
required List<double> prices,
@@ -57,9 +56,9 @@ abstract class CartRepository {
/// [quantity] - New quantity
/// [price] - Unit price
///
/// Returns updated cart item list.
/// Returns true if successful.
/// Throws exceptions on failure.
Future<List<CartItem>> updateQuantity({
Future<bool> updateQuantity({
required String itemId,
required double quantity,
required double price,

View File

@@ -419,7 +419,7 @@ class _CartPageState extends ConsumerState<CartPage> {
),
const SizedBox(height: 24),
ElevatedButton.icon(
onPressed: () => context.go(RouteNames.products),
onPressed: () => context.push(RouteNames.products),
icon: const FaIcon(FontAwesomeIcons.bagShopping, size: 20),
label: const Text('Xem sản phẩm'),
),

View File

@@ -7,9 +7,11 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:go_router/go_router.dart';
import 'package:worker/core/services/analytics_service.dart';
import 'package:worker/core/widgets/loading_indicator.dart';
import 'package:worker/core/constants/ui_constants.dart';
import 'package:worker/core/theme/colors.dart';
import 'package:worker/features/cart/presentation/providers/cart_provider.dart';
import 'package:worker/features/favorites/presentation/providers/favorites_provider.dart';
import 'package:worker/features/products/domain/entities/product.dart';
import 'package:worker/features/products/presentation/providers/products_provider.dart';
@@ -154,8 +156,25 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
);
}
void _addToCart(Product product) {
// TODO: Add to cart logic
void _addToCart(Product product) async {
// Add to cart via provider
await ref.read(cartProvider.notifier).addToCart(
product,
quantity: _quantity.toDouble(),
);
// Log analytics event
await AnalyticsService.logAddToCart(
productId: product.productId,
productName: product.name,
price: product.basePrice,
quantity: _quantity,
brand: product.itemGroupName,
);
if (!mounted) return;
ScaffoldMessenger.of(context).clearSnackBars();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
@@ -165,7 +184,7 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
action: SnackBarAction(
label: 'Xem giỏ hàng',
onPressed: () {
// TODO: Navigate to cart
context.push('/cart');
},
),
),
@@ -245,20 +264,15 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
),
// Sticky Action Bar
Positioned(
bottom: 0,
left: 0,
right: 0,
child: StickyActionBar(
quantity: _quantity,
unit: '',
conversionOfSm: product.conversionOfSm,
uomFromIntroAttributes: product.getIntroAttribute('UOM'),
onIncrease: _increaseQuantity,
onDecrease: _decreaseQuantity,
onQuantityChanged: _updateQuantity,
onAddToCart: () => _addToCart(product),
),
StickyActionBar(
quantity: _quantity,
unit: '',
conversionOfSm: product.conversionOfSm,
uomFromIntroAttributes: product.getIntroAttribute('UOM'),
onIncrease: _increaseQuantity,
onDecrease: _decreaseQuantity,
onQuantityChanged: _updateQuantity,
onAddToCart: () => _addToCart(product),
),
],
);

View File

@@ -7,6 +7,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:go_router/go_router.dart';
import 'package:worker/core/services/analytics_service.dart';
import 'package:worker/core/widgets/loading_indicator.dart';
import 'package:worker/core/constants/ui_constants.dart';
import 'package:worker/core/router/app_router.dart';
@@ -44,7 +45,13 @@ class ProductsPage extends ConsumerWidget {
appBar: AppBar(
leading: IconButton(
icon: FaIcon(FontAwesomeIcons.arrowLeft, color: colorScheme.onSurface, size: 20),
onPressed: () => context.pop(),
onPressed: () {
if (context.canPop()) {
context.pop();
} else {
context.go(RouteNames.home);
}
},
),
title: Text('Sản phẩm', style: TextStyle(color: colorScheme.onSurface)),
elevation: AppBarSpecs.elevation,
@@ -135,6 +142,14 @@ class ProductsPage extends ConsumerWidget {
// Add to cart
ref.read(cartProvider.notifier).addToCart(product);
AnalyticsService.logAddToCart(
productId: product.productId,
productName: product.name,
price: product.basePrice,
quantity: 1,
brand: product.itemGroupName,
);
// Show SnackBar with manual dismissal
final messenger = ScaffoldMessenger.of(context)
..clearSnackBars();