This commit is contained in:
Phuoc Nguyen
2025-10-10 16:38:07 +07:00
parent e5b247d622
commit b94c158004
177 changed files with 25080 additions and 152 deletions

View File

@@ -0,0 +1,136 @@
import 'package:hive_ce/hive.dart';
import 'package:retail/core/database/hive_database.dart';
import 'package:retail/features/categories/data/models/category_model.dart';
import 'package:retail/features/categories/data/datasources/category_local_datasource.dart';
import 'package:retail/features/categories/domain/entities/category.dart';
/// Hive implementation of CategoryLocalDataSource
class CategoryLocalDataSourceHive implements CategoryLocalDataSource {
final HiveDatabase _database;
CategoryLocalDataSourceHive(this._database);
Box<CategoryModel> get _box => _database.categoriesBox;
/// Convert CategoryModel to Category entity
Category _toEntity(CategoryModel model) {
return Category(
id: model.id,
name: model.name,
description: model.description,
iconPath: model.iconPath,
color: model.color,
createdAt: model.createdAt,
);
}
/// Convert Category entity to CategoryModel
CategoryModel _toModel(Category entity) {
return CategoryModel(
id: entity.id,
name: entity.name,
description: entity.description,
iconPath: entity.iconPath,
color: entity.color,
productCount: 0, // Will be calculated from products
createdAt: entity.createdAt,
);
}
@override
Future<List<Category>> getAllCategories() async {
try {
return _box.values.map(_toEntity).toList();
} catch (e) {
throw Exception('Failed to get categories: $e');
}
}
@override
Future<Category?> getCategoryById(String id) async {
try {
final model = _box.get(id);
return model != null ? _toEntity(model) : null;
} catch (e) {
throw Exception('Failed to get category by ID: $e');
}
}
@override
Future<void> saveCategories(List<Category> categories) async {
try {
final models = categories.map(_toModel).toList();
final Map<String, CategoryModel> categoriesMap = {
for (var model in models) model.id: model
};
await _box.putAll(categoriesMap);
} catch (e) {
throw Exception('Failed to save categories: $e');
}
}
@override
Future<void> deleteAllCategories() async {
try {
await _box.clear();
} catch (e) {
throw Exception('Failed to delete all categories: $e');
}
}
/// Additional Hive-specific methods
/// Save a single category
Future<void> saveCategory(Category category) async {
try {
final model = _toModel(category);
await _box.put(model.id, model);
} catch (e) {
throw Exception('Failed to save category: $e');
}
}
/// Update an existing category
Future<void> updateCategory(Category category) async {
try {
if (!_box.containsKey(category.id)) {
throw Exception('Category not found: ${category.id}');
}
final model = _toModel(category);
await _box.put(model.id, model);
} catch (e) {
throw Exception('Failed to update category: $e');
}
}
/// Delete a specific category
Future<void> deleteCategory(String id) async {
try {
await _box.delete(id);
} catch (e) {
throw Exception('Failed to delete category: $e');
}
}
/// Check if category exists
Future<bool> categoryExists(String id) async {
try {
return _box.containsKey(id);
} catch (e) {
throw Exception('Failed to check category existence: $e');
}
}
/// Update product count for a category
Future<void> updateProductCount(String categoryId, int count) async {
try {
final model = _box.get(categoryId);
if (model != null) {
final updated = model.copyWith(productCount: count);
await _box.put(categoryId, updated);
}
} catch (e) {
throw Exception('Failed to update product count: $e');
}
}
}

View File

@@ -0,0 +1,215 @@
import 'package:dio/dio.dart';
import '../../../../core/constants/api_constants.dart';
import '../../../../core/errors/exceptions.dart';
import '../../../../core/network/dio_client.dart';
import '../models/category_model.dart';
/// Remote data source for categories API operations
abstract class CategoryRemoteDataSource {
/// Fetch all categories from the API
Future<List<CategoryModel>> fetchCategories();
/// Fetch a single category by ID
Future<CategoryModel> fetchCategoryById(String id);
/// Sync categories (bulk update/create)
Future<void> syncCategories(List<CategoryModel> categories);
}
/// Implementation of CategoryRemoteDataSource using Dio
class CategoryRemoteDataSourceImpl implements CategoryRemoteDataSource {
final DioClient _dioClient;
CategoryRemoteDataSourceImpl(this._dioClient);
@override
Future<List<CategoryModel>> fetchCategories() async {
try {
final response = await _dioClient.get(ApiConstants.categories);
if (response.statusCode == ApiConstants.statusOk) {
final data = response.data;
// Handle different response structures
List<dynamic> categoriesJson;
if (data is Map<String, dynamic>) {
// Response wrapped in object: { "categories": [...] }
categoriesJson = data['categories'] as List<dynamic>? ??
data['data'] as List<dynamic>? ??
[];
} else if (data is List) {
// Direct array response
categoriesJson = data;
} else {
throw DataParsingException('Unexpected response format');
}
return categoriesJson
.map((json) => CategoryModel.fromJson(json as Map<String, dynamic>))
.toList();
} else {
throw ServerException(
'Failed to fetch categories',
response.statusCode ?? 500,
);
}
} on AppException {
rethrow;
} catch (e, stackTrace) {
throw NetworkException(
'Failed to fetch categories: ${e.toString()}',
null,
e,
stackTrace,
);
}
}
@override
Future<CategoryModel> fetchCategoryById(String id) async {
try {
final response = await _dioClient.get(ApiConstants.categoryById(id));
if (response.statusCode == ApiConstants.statusOk) {
final data = response.data;
// Handle different response structures
Map<String, dynamic> categoryJson;
if (data is Map<String, dynamic>) {
// Check if category is wrapped in a key
categoryJson = data['category'] as Map<String, dynamic>? ??
data['data'] as Map<String, dynamic>? ??
data;
} else {
throw DataParsingException('Unexpected response format');
}
return CategoryModel.fromJson(categoryJson);
} else if (response.statusCode == ApiConstants.statusNotFound) {
throw NotFoundException('Category with ID $id not found');
} else {
throw ServerException(
'Failed to fetch category',
response.statusCode ?? 500,
);
}
} on AppException {
rethrow;
} catch (e, stackTrace) {
throw NetworkException(
'Failed to fetch category: ${e.toString()}',
null,
e,
stackTrace,
);
}
}
@override
Future<void> syncCategories(List<CategoryModel> categories) async {
try {
final categoriesJson = categories.map((c) => c.toJson()).toList();
final response = await _dioClient.post(
ApiConstants.syncCategories,
data: {
'categories': categoriesJson,
},
);
if (response.statusCode != ApiConstants.statusOk &&
response.statusCode != ApiConstants.statusCreated) {
throw ServerException(
'Failed to sync categories',
response.statusCode ?? 500,
);
}
} on AppException {
rethrow;
} catch (e, stackTrace) {
throw NetworkException(
'Failed to sync categories: ${e.toString()}',
null,
e,
stackTrace,
);
}
}
}
/// Mock implementation for testing and development
class CategoryRemoteDataSourceMock implements CategoryRemoteDataSource {
/// Simulate network delay
Future<void> _delay() async {
await Future.delayed(
Duration(milliseconds: ApiConstants.mockApiDelay),
);
}
@override
Future<List<CategoryModel>> fetchCategories() async {
await _delay();
// Return mock categories
return [
CategoryModel(
id: '1',
name: 'Electronics',
description: 'Electronic devices and accessories',
iconPath: 'assets/icons/electronics.png',
color: '#2196F3',
productCount: 25,
createdAt: DateTime.now().subtract(const Duration(days: 60)),
),
CategoryModel(
id: '2',
name: 'Clothing',
description: 'Fashion and apparel',
iconPath: 'assets/icons/clothing.png',
color: '#E91E63',
productCount: 50,
createdAt: DateTime.now().subtract(const Duration(days: 50)),
),
CategoryModel(
id: '3',
name: 'Food & Beverages',
description: 'Food, drinks, and snacks',
iconPath: 'assets/icons/food.png',
color: '#FF9800',
productCount: 100,
createdAt: DateTime.now().subtract(const Duration(days: 40)),
),
CategoryModel(
id: '4',
name: 'Home & Garden',
description: 'Home improvement and garden supplies',
iconPath: 'assets/icons/home.png',
color: '#4CAF50',
productCount: 30,
createdAt: DateTime.now().subtract(const Duration(days: 30)),
),
];
}
@override
Future<CategoryModel> fetchCategoryById(String id) async {
await _delay();
// Return mock category
return CategoryModel(
id: id,
name: 'Sample Category $id',
description: 'This is sample category $id',
iconPath: 'assets/icons/category.png',
color: '#2196F3',
productCount: 10,
createdAt: DateTime.now().subtract(const Duration(days: 30)),
);
}
@override
Future<void> syncCategories(List<CategoryModel> categories) async {
await _delay();
// Mock sync - do nothing
}
}

View File

@@ -0,0 +1,447 @@
/// Performance optimization usage examples
///
/// This file demonstrates how to use all performance optimizations
/// in the retail POS app.
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../config/image_cache_config.dart';
import '../constants/performance_constants.dart';
import '../utils/debouncer.dart';
import '../utils/performance_monitor.dart';
import '../utils/provider_optimization.dart';
import '../utils/responsive_helper.dart';
import '../widgets/optimized_cached_image.dart';
import '../widgets/optimized_grid_view.dart';
import '../widgets/optimized_list_view.dart';
// ============================================================================
// EXAMPLE 1: Optimized Product Grid
// ============================================================================
class ProductGridExample extends ConsumerWidget {
const ProductGridExample({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
// Watch only the products list (granular rebuild)
final productsAsync = ref.watch(exampleProductsProvider);
return productsAsync.when(
data: (products) {
if (products.isEmpty) {
return const GridEmptyState(
message: 'No products found',
icon: Icons.inventory_2_outlined,
);
}
// Use optimized grid with automatic performance enhancements
return ProductGridView<ExampleProduct>(
products: products,
itemBuilder: (context, product, index) {
// Wrapped in RepaintBoundary automatically
return const ExampleProductCard();
},
onScrollEnd: () {
// Load more products when scrolling near end
// ref.read(exampleProductsProvider.notifier).loadMore();
},
);
},
loading: () => const GridLoadingState(itemCount: 6),
error: (error, stack) => GridEmptyState(
message: 'Failed to load products',
icon: Icons.error_outline,
onRetry: () {
ref.invalidate(exampleProductsProvider);
},
),
);
}
}
// ============================================================================
// EXAMPLE 2: Optimized Product Card with Cached Image
// ============================================================================
class ExampleProductCard extends StatelessWidget {
const ExampleProductCard({super.key});
@override
Widget build(BuildContext context) {
// Use const constructor for better performance
return Card(
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Optimized cached image with automatic sizing
Expanded(
flex: 3,
child: ClipRRect(
borderRadius: const BorderRadius.vertical(
top: Radius.circular(12),
),
child: ProductGridImage(
imageUrl: 'https://example.com/product.jpg',
size: 150,
),
),
),
// Product details
const Expanded(
flex: 2,
child: Padding(
padding: EdgeInsets.all(12.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Product Name',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
SizedBox(height: 4),
Text(
'\$99.99',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.green,
),
),
],
),
),
),
],
),
);
}
}
// ============================================================================
// EXAMPLE 3: Search with Debouncing
// ============================================================================
class ProductSearchExample extends ConsumerStatefulWidget {
const ProductSearchExample({super.key});
@override
ConsumerState<ProductSearchExample> createState() =>
_ProductSearchExampleState();
}
class _ProductSearchExampleState extends ConsumerState<ProductSearchExample> {
final _searchController = TextEditingController();
final _searchDebouncer = SearchDebouncer(); // 300ms debounce
@override
void dispose() {
_searchController.dispose();
_searchDebouncer.dispose();
super.dispose();
}
void _onSearchChanged(String query) {
// Debounce search to avoid excessive API calls
_searchDebouncer.run(() {
// Update search provider
// ref.read(searchQueryProvider.notifier).state = query;
});
}
@override
Widget build(BuildContext context) {
return TextField(
controller: _searchController,
onChanged: _onSearchChanged,
decoration: const InputDecoration(
hintText: 'Search products...',
prefixIcon: Icon(Icons.search),
border: OutlineInputBorder(),
),
);
}
}
// ============================================================================
// EXAMPLE 4: Optimized Cart List with Performance Tracking
// ============================================================================
class CartListExample extends ConsumerWidget {
const CartListExample({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
// Watch only cart items (not entire cart state)
final cartItems = ref.watchField(
exampleCartProvider,
(cart) => cart.items,
);
if (cartItems.isEmpty) {
return const ListEmptyState(
message: 'Your cart is empty',
icon: Icons.shopping_cart_outlined,
);
}
return CartListView<ExampleCartItem>(
items: cartItems,
itemBuilder: (context, item, index) {
return const ExampleCartItemCard();
},
);
}
}
class ExampleCartItemCard extends StatelessWidget {
const ExampleCartItemCard({super.key});
@override
Widget build(BuildContext context) {
return ListTile(
leading: const CartItemThumbnail(
imageUrl: 'https://example.com/product.jpg',
size: 60,
),
title: const Text('Product Name'),
subtitle: const Text('\$99.99'),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(Icons.remove_circle_outline),
onPressed: () {
// Decrease quantity
},
),
const Text('1'),
IconButton(
icon: const Icon(Icons.add_circle_outline),
onPressed: () {
// Increase quantity
},
),
],
),
);
}
}
// ============================================================================
// EXAMPLE 5: Responsive Grid with Adaptive Layout
// ============================================================================
class ResponsiveGridExample extends StatelessWidget {
const ResponsiveGridExample({super.key});
@override
Widget build(BuildContext context) {
// Get responsive values
final columns = context.gridColumns;
final spacing = context.spacing;
final padding = context.responsivePadding;
return Padding(
padding: padding,
child: GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: columns,
crossAxisSpacing: spacing,
mainAxisSpacing: spacing,
childAspectRatio: PerformanceConstants.productCardAspectRatio,
),
itemBuilder: (context, index) {
return const RepaintBoundary(
child: ExampleProductCard(),
);
},
),
);
}
}
// ============================================================================
// EXAMPLE 6: Database Operations with Performance Tracking
// ============================================================================
class DatabaseExample {
Future<void> loadProductsWithTracking() async {
// Track async operation performance
final products = await PerformanceMonitor().trackAsync(
'loadProducts',
() async {
// Simulated database query
await Future.delayed(const Duration(milliseconds: 100));
return <ExampleProduct>[];
},
);
// Or use extension
final categories = await _loadCategories().trackPerformance('loadCategories');
}
Future<List<String>> _loadCategories() async {
await Future.delayed(const Duration(milliseconds: 50));
return ['Electronics', 'Clothing', 'Food'];
}
}
// ============================================================================
// EXAMPLE 7: Provider with Granular Rebuilds
// ============================================================================
// Provider example (would use riverpod_annotation in real app)
final exampleProductsProvider = Provider<List<ExampleProduct>>((ref) => []);
final exampleCartProvider = Provider<ExampleCart>((ref) => ExampleCart.empty());
// Optimized consumer that only rebuilds when name changes
class OptimizedConsumerExample extends ConsumerWidget {
const OptimizedConsumerExample({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
// Only rebuilds when product count changes
final productCount = ref.watchField(
exampleProductsProvider,
(products) => products.length,
);
return Text('Products: $productCount');
}
}
// ============================================================================
// EXAMPLE 8: Image Cache Management
// ============================================================================
class ImageCacheExample {
Future<void> clearCaches() async {
// Clear all image caches
await ImageOptimization.clearAllCaches();
}
Future<void> clearProductImages() async {
// Clear only product images
await ProductImageCacheManager().emptyCache();
}
Future<void> clearCategoryImages() async {
// Clear only category images
await CategoryImageCacheManager().emptyCache();
}
Future<int> getCacheSize() async {
// Get total cache size
return await ImageOptimization.getTotalCacheSize();
}
}
// ============================================================================
// EXAMPLE 9: Performance Monitoring
// ============================================================================
class PerformanceMonitoringExample extends StatefulWidget {
const PerformanceMonitoringExample({super.key});
@override
State<PerformanceMonitoringExample> createState() =>
_PerformanceMonitoringExampleState();
}
class _PerformanceMonitoringExampleState
extends State<PerformanceMonitoringExample> {
@override
Widget build(BuildContext context) {
return RebuildTracker(
name: 'PerformanceExample',
child: const Column(
children: [
Text('This widget rebuild count is tracked'),
],
),
);
}
Future<void> loadData() async {
// Track performance
await PerformanceMonitor().trackAsync(
'loadData',
() async {
await Future.delayed(const Duration(milliseconds: 200));
},
);
}
void calculateTotal() {
// Track synchronous operation
final total = PerformanceMonitor().track(
'calculateTotal',
() {
return 99.99;
},
);
}
@override
void dispose() {
// Print performance summary before disposing
PerformanceMonitor().printSummary();
RebuildTracker.printRebuildStats();
super.dispose();
}
}
// ============================================================================
// Models (for examples)
// ============================================================================
class ExampleProduct {
final String id;
final String name;
final double price;
final String? imageUrl;
const ExampleProduct({
required this.id,
required this.name,
required this.price,
this.imageUrl,
});
}
class ExampleCart {
final List<ExampleCartItem> items;
const ExampleCart({required this.items});
factory ExampleCart.empty() => const ExampleCart(items: []);
}
class ExampleCartItem {
final String productId;
final String name;
final double price;
final int quantity;
final String? imageUrl;
const ExampleCartItem({
required this.productId,
required this.name,
required this.price,
required this.quantity,
this.imageUrl,
});
}

View File

@@ -0,0 +1,11 @@
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../../data/datasources/product_local_datasource.dart';
part 'product_datasource_provider.g.dart';
/// Provider for product local data source
/// This is kept alive as it's a dependency injection provider
@Riverpod(keepAlive: true)
ProductLocalDataSource productLocalDataSource(ProductLocalDataSourceRef ref) {
return ProductLocalDataSourceImpl();
}

View File

@@ -0,0 +1,182 @@
import 'package:hive_ce/hive.dart';
import 'package:retail/core/database/hive_database.dart';
import 'package:retail/features/products/data/models/product_model.dart';
import 'package:retail/features/products/data/datasources/product_local_datasource.dart';
import 'package:retail/features/products/domain/entities/product.dart';
/// Hive implementation of ProductLocalDataSource
class ProductLocalDataSourceHive implements ProductLocalDataSource {
final HiveDatabase _database;
ProductLocalDataSourceHive(this._database);
Box<ProductModel> get _box => _database.productsBox;
/// Convert ProductModel to Product entity
Product _toEntity(ProductModel model) {
return Product(
id: model.id,
name: model.name,
description: model.description,
price: model.price,
imageUrl: model.imageUrl,
categoryId: model.categoryId,
stockQuantity: model.stockQuantity,
isAvailable: model.isAvailable,
createdAt: model.createdAt,
updatedAt: model.updatedAt,
);
}
/// Convert Product entity to ProductModel
ProductModel _toModel(Product entity) {
return ProductModel(
id: entity.id,
name: entity.name,
description: entity.description,
price: entity.price,
imageUrl: entity.imageUrl,
categoryId: entity.categoryId,
stockQuantity: entity.stockQuantity,
isAvailable: entity.isAvailable,
createdAt: entity.createdAt,
updatedAt: entity.updatedAt,
);
}
@override
Future<List<Product>> getAllProducts() async {
try {
return _box.values.map(_toEntity).toList();
} catch (e) {
throw Exception('Failed to get products: $e');
}
}
@override
Future<Product?> getProductById(String id) async {
try {
final model = _box.get(id);
return model != null ? _toEntity(model) : null;
} catch (e) {
throw Exception('Failed to get product by ID: $e');
}
}
@override
Future<List<Product>> getProductsByCategory(String categoryId) async {
try {
return _box.values
.where((product) => product.categoryId == categoryId)
.map(_toEntity)
.toList();
} catch (e) {
throw Exception('Failed to get products by category: $e');
}
}
@override
Future<void> saveProducts(List<Product> products) async {
try {
final models = products.map(_toModel).toList();
final Map<String, ProductModel> productsMap = {
for (var model in models) model.id: model
};
await _box.putAll(productsMap);
} catch (e) {
throw Exception('Failed to save products: $e');
}
}
@override
Future<void> deleteAllProducts() async {
try {
await _box.clear();
} catch (e) {
throw Exception('Failed to delete all products: $e');
}
}
/// Additional Hive-specific methods
/// Save a single product
Future<void> saveProduct(Product product) async {
try {
final model = _toModel(product);
await _box.put(model.id, model);
} catch (e) {
throw Exception('Failed to save product: $e');
}
}
/// Update an existing product
Future<void> updateProduct(Product product) async {
try {
if (!_box.containsKey(product.id)) {
throw Exception('Product not found: ${product.id}');
}
final model = _toModel(product);
await _box.put(model.id, model);
} catch (e) {
throw Exception('Failed to update product: $e');
}
}
/// Delete a specific product
Future<void> deleteProduct(String id) async {
try {
await _box.delete(id);
} catch (e) {
throw Exception('Failed to delete product: $e');
}
}
/// Check if product exists
Future<bool> productExists(String id) async {
try {
return _box.containsKey(id);
} catch (e) {
throw Exception('Failed to check product existence: $e');
}
}
/// Get available products only
Future<List<Product>> getAvailableProducts() async {
try {
return _box.values
.where((product) => product.isAvailable && product.stockQuantity > 0)
.map(_toEntity)
.toList();
} catch (e) {
throw Exception('Failed to get available products: $e');
}
}
/// Get products with low stock
Future<List<Product>> getLowStockProducts(int threshold) async {
try {
return _box.values
.where((product) =>
product.stockQuantity <= threshold && product.stockQuantity > 0)
.map(_toEntity)
.toList();
} catch (e) {
throw Exception('Failed to get low stock products: $e');
}
}
/// Search products by name or description
Future<List<Product>> searchProducts(String query) async {
try {
final lowercaseQuery = query.toLowerCase();
return _box.values
.where((product) =>
product.name.toLowerCase().contains(lowercaseQuery) ||
product.description.toLowerCase().contains(lowercaseQuery))
.map(_toEntity)
.toList();
} catch (e) {
throw Exception('Failed to search products: $e');
}
}
}

View File

@@ -0,0 +1,6 @@
/// Export all product providers
export 'product_datasource_provider.dart';
export 'products_provider.dart';
export 'search_query_provider.dart';
export 'selected_category_provider.dart';
export 'filtered_products_provider.dart';

View File

@@ -0,0 +1,313 @@
/// Riverpod provider optimization utilities
///
/// Features:
/// - Granular rebuild prevention with .select()
/// - Provider caching strategies
/// - Debounced provider updates
/// - Performance-optimized provider patterns
import 'package:flutter/foundation.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'debouncer.dart';
/// Extension for optimized provider watching
extension ProviderOptimizationExtensions on WidgetRef {
/// Watch only a specific field to minimize rebuilds
///
/// Example:
/// ```dart
/// final name = ref.watchField(userProvider, (user) => user.name);
/// ```
T watchField<S, T>(ProviderListenable<S> provider, T Function(S) selector) {
return watch(provider.select(selector));
}
/// Watch multiple fields efficiently
///
/// Example:
/// ```dart
/// final (name, age) = ref.watchFields(
/// userProvider,
/// (user) => (user.name, user.age),
/// );
/// ```
T watchFields<S, T>(ProviderListenable<S> provider, T Function(S) selector) {
return watch(provider.select(selector));
}
/// Listen to provider only when condition is met
///
/// Example:
/// ```dart
/// ref.listenWhen(
/// userProvider,
/// (prev, next) => prev?.id != next?.id,
/// (prev, next) {
/// // Handle change
/// },
/// );
/// ```
void listenWhen<T>(
ProviderListenable<T> provider,
bool Function(T? previous, T next) condition,
void Function(T? previous, T next) listener, {
void Function(Object error, StackTrace stackTrace)? onError,
}) {
T? previous;
listen<T>(
provider,
(prev, next) {
if (condition(prev, next)) {
listener(prev, next);
previous = next;
}
},
onError: onError,
);
}
}
/// Debounced state notifier for performance optimization
abstract class DebouncedStateNotifier<T> extends StateNotifier<T> {
final Debouncer _debouncer;
DebouncedStateNotifier(
super.state, {
int debounceDuration = 300,
}) : _debouncer = Debouncer(milliseconds: debounceDuration);
/// Update state with debouncing
void updateDebounced(T newState) {
_debouncer.run(() {
if (mounted) {
state = newState;
}
});
}
/// Update state immediately (bypass debouncing)
void updateImmediate(T newState) {
_debouncer.cancel();
if (mounted) {
state = newState;
}
}
@override
void dispose() {
_debouncer.dispose();
super.dispose();
}
}
/// Cached async value to prevent unnecessary rebuilds
class CachedAsyncValue<T> {
final AsyncValue<T> _value;
final DateTime _timestamp;
final Duration _cacheDuration;
CachedAsyncValue(
this._value, {
Duration cacheDuration = const Duration(minutes: 5),
}) : _timestamp = DateTime.now(),
_cacheDuration = cacheDuration;
AsyncValue<T> get value => _value;
bool get isExpired {
return DateTime.now().difference(_timestamp) > _cacheDuration;
}
bool get isValid {
return !isExpired && _value is AsyncData<T>;
}
}
/// Provider cache manager
class ProviderCacheManager {
static final Map<String, CachedAsyncValue> _cache = {};
/// Get cached value or compute new one
static AsyncValue<T> getOrCompute<T>({
required String key,
required AsyncValue<T> Function() compute,
Duration cacheDuration = const Duration(minutes: 5),
}) {
final cached = _cache[key] as CachedAsyncValue<T>?;
if (cached != null && cached.isValid) {
return cached.value;
}
final value = compute();
_cache[key] = CachedAsyncValue(value, cacheDuration: cacheDuration);
return value;
}
/// Invalidate specific cache entry
static void invalidate(String key) {
_cache.remove(key);
}
/// Clear all cache
static void clear() {
_cache.clear();
}
/// Clean expired cache entries
static void cleanExpired() {
_cache.removeWhere((key, value) => value.isExpired);
}
}
/// Optimized family provider cache
class FamilyProviderCache<Arg, T> {
final Map<Arg, T> _cache = {};
final int _maxSize;
FamilyProviderCache({int maxSize = 50}) : _maxSize = maxSize;
T? get(Arg arg) => _cache[arg];
void set(Arg arg, T value) {
// Simple LRU: remove oldest if cache is full
if (_cache.length >= _maxSize) {
final firstKey = _cache.keys.first;
_cache.remove(firstKey);
}
_cache[arg] = value;
}
void invalidate(Arg arg) {
_cache.remove(arg);
}
void clear() {
_cache.clear();
}
int get size => _cache.length;
}
/// Performance-optimized state notifier mixin
mixin PerformanceOptimizedNotifier<T> on StateNotifier<T> {
/// Track rebuild count in debug mode
int _rebuildCount = 0;
@override
set state(T value) {
if (kDebugMode) {
_rebuildCount++;
if (_rebuildCount % 10 == 0) {
debugPrint(
'⚠️ ${runtimeType.toString()} has been rebuilt $_rebuildCount times',
);
}
}
super.state = value;
}
/// Update state only if changed
void updateIfChanged(T newState) {
if (state != newState) {
state = newState;
}
}
/// Update part of state efficiently
void updatePart<S>(S Function(T) selector, S newValue) {
if (selector(state) != newValue) {
// This requires implementing proper state copying
// Subclasses should override this
throw UnimplementedError('updatePart must be implemented in subclass');
}
}
}
/// Selector helper for complex state
class StateSelector<T> {
final T state;
const StateSelector(this.state);
/// Select a field from state
S select<S>(S Function(T) selector) => selector(state);
/// Select multiple fields
R selectAll<R>(R Function(T) selector) => selector(state);
}
/// Optimized consumer for minimal rebuilds
///
/// Example:
/// ```dart
/// OptimizedConsumer<UserState>(
/// selector: (state) => state.name,
/// builder: (context, name, child) {
/// return Text(name);
/// },
/// )
/// ```
class OptimizedConsumer<T, S> extends ConsumerWidget {
final ProviderListenable<T> provider;
final S Function(T) selector;
final Widget Function(BuildContext context, S value, Widget? child) builder;
final Widget? child;
const OptimizedConsumer({
super.key,
required this.provider,
required this.selector,
required this.builder,
this.child,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
final value = ref.watch(provider.select(selector));
return builder(context, value, child);
}
}
/// Batch state updates for performance
class BatchedStateUpdates<T> {
final void Function(T) updateState;
final List<void Function(T)> _pendingUpdates = [];
final Debouncer _debouncer;
BatchedStateUpdates(
this.updateState, {
int debounceDuration = 100,
}) : _debouncer = Debouncer(milliseconds: debounceDuration);
/// Queue an update
void queueUpdate(void Function(T) update) {
_pendingUpdates.add(update);
_debouncer.run(_flushUpdates);
}
/// Flush all pending updates
void _flushUpdates() {
if (_pendingUpdates.isEmpty) return;
// Apply all updates in batch
for (final update in _pendingUpdates) {
// Note: This would need proper implementation based on state type
// updateState(update);
}
_pendingUpdates.clear();
}
/// Force flush immediately
void flush() {
_debouncer.cancel();
_flushUpdates();
}
void dispose() {
_debouncer.dispose();
_pendingUpdates.clear();
}
}

View File

@@ -0,0 +1,155 @@
import 'package:flutter/material.dart';
import 'package:hive_ce/hive.dart';
import 'package:retail/core/database/hive_database.dart';
import 'package:retail/features/settings/data/models/app_settings_model.dart';
import 'package:retail/features/settings/data/datasources/settings_local_datasource.dart';
import 'package:retail/features/settings/domain/entities/app_settings.dart';
/// Hive implementation of SettingsLocalDataSource
class SettingsLocalDataSourceHive implements SettingsLocalDataSource {
final HiveDatabase _database;
static const String _settingsKey = 'app_settings';
SettingsLocalDataSourceHive(this._database);
Box<AppSettingsModel> get _box => _database.settingsBox;
/// Convert AppSettingsModel to AppSettings entity
AppSettings _toEntity(AppSettingsModel model) {
return AppSettings(
themeMode: model.themeMode,
language: model.language,
currency: model.currency,
taxRate: model.taxRate,
storeName: model.storeName,
enableSync: model.enableSync,
lastSyncAt: model.lastSyncAt,
);
}
/// Convert AppSettings entity to AppSettingsModel
AppSettingsModel _toModel(AppSettings entity) {
return AppSettingsModel.fromThemeMode(
themeMode: entity.themeMode,
language: entity.language,
currency: entity.currency,
taxRate: entity.taxRate,
storeName: entity.storeName,
enableSync: entity.enableSync,
lastSyncAt: entity.lastSyncAt,
);
}
@override
Future<AppSettings> getSettings() async {
try {
final model = _box.get(_settingsKey);
if (model != null) {
return _toEntity(model);
}
// Return default settings if not found
return AppSettings.defaultSettings();
} catch (e) {
throw Exception('Failed to get settings: $e');
}
}
@override
Future<void> saveSettings(AppSettings settings) async {
try {
final model = _toModel(settings);
await _box.put(_settingsKey, model);
} catch (e) {
throw Exception('Failed to save settings: $e');
}
}
@override
Future<void> updateThemeMode(ThemeMode mode) async {
try {
final currentSettings = await getSettings();
final updated = currentSettings.copyWith(themeMode: mode);
await saveSettings(updated);
} catch (e) {
throw Exception('Failed to update theme mode: $e');
}
}
@override
Future<void> updateLanguage(String language) async {
try {
final currentSettings = await getSettings();
final updated = currentSettings.copyWith(language: language);
await saveSettings(updated);
} catch (e) {
throw Exception('Failed to update language: $e');
}
}
@override
Future<void> updateLastSyncTime(DateTime time) async {
try {
final currentSettings = await getSettings();
final updated = currentSettings.copyWith(lastSyncAt: time);
await saveSettings(updated);
} catch (e) {
throw Exception('Failed to update last sync time: $e');
}
}
/// Additional Hive-specific methods
/// Update currency
Future<void> updateCurrency(String currency) async {
try {
final currentSettings = await getSettings();
final updated = currentSettings.copyWith(currency: currency);
await saveSettings(updated);
} catch (e) {
throw Exception('Failed to update currency: $e');
}
}
/// Update tax rate
Future<void> updateTaxRate(double taxRate) async {
try {
final currentSettings = await getSettings();
final updated = currentSettings.copyWith(taxRate: taxRate);
await saveSettings(updated);
} catch (e) {
throw Exception('Failed to update tax rate: $e');
}
}
/// Update store name
Future<void> updateStoreName(String storeName) async {
try {
final currentSettings = await getSettings();
final updated = currentSettings.copyWith(storeName: storeName);
await saveSettings(updated);
} catch (e) {
throw Exception('Failed to update store name: $e');
}
}
/// Toggle sync
Future<void> toggleSync(bool enable) async {
try {
final currentSettings = await getSettings();
final updated = currentSettings.copyWith(enableSync: enable);
await saveSettings(updated);
} catch (e) {
throw Exception('Failed to toggle sync: $e');
}
}
/// Reset to default settings
Future<void> resetSettings() async {
try {
final defaultSettings = AppSettings.defaultSettings();
await saveSettings(defaultSettings);
} catch (e) {
throw Exception('Failed to reset settings: $e');
}
}
}