runable
This commit is contained in:
136
.archive/category_local_datasource_hive.dart
Normal file
136
.archive/category_local_datasource_hive.dart
Normal 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');
|
||||
}
|
||||
}
|
||||
}
|
||||
215
.archive/category_remote_datasource.dart
Normal file
215
.archive/category_remote_datasource.dart
Normal 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
|
||||
}
|
||||
}
|
||||
447
.archive/examples/performance_examples.dart
Normal file
447
.archive/examples/performance_examples.dart
Normal 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,
|
||||
});
|
||||
}
|
||||
11
.archive/product_datasource_provider.dart
Normal file
11
.archive/product_datasource_provider.dart
Normal 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();
|
||||
}
|
||||
182
.archive/product_local_datasource_hive.dart
Normal file
182
.archive/product_local_datasource_hive.dart
Normal 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');
|
||||
}
|
||||
}
|
||||
}
|
||||
6
.archive/products_providers.dart
Normal file
6
.archive/products_providers.dart
Normal 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';
|
||||
313
.archive/provider_optimization.dart
Normal file
313
.archive/provider_optimization.dart
Normal 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();
|
||||
}
|
||||
}
|
||||
155
.archive/settings_local_datasource_hive.dart
Normal file
155
.archive/settings_local_datasource_hive.dart
Normal 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');
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user