runable
This commit is contained in:
119
lib/core/database/QUICK_START.md
Normal file
119
lib/core/database/QUICK_START.md
Normal file
@@ -0,0 +1,119 @@
|
||||
# Hive CE Quick Start Guide
|
||||
|
||||
## 1. Initialize in main.dart
|
||||
|
||||
```dart
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'core/database/hive_initializer.dart';
|
||||
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
// Initialize Hive
|
||||
await HiveInitializer.initialize(verbose: true);
|
||||
|
||||
runApp(const ProviderScope(child: MyApp()));
|
||||
}
|
||||
```
|
||||
|
||||
## 2. Save & Retrieve Data
|
||||
|
||||
```dart
|
||||
import 'package:worker/core/database/database.dart';
|
||||
|
||||
final dbManager = DatabaseManager();
|
||||
|
||||
// Save
|
||||
await dbManager.save(
|
||||
boxName: HiveBoxNames.productBox,
|
||||
key: 'product_123',
|
||||
value: product,
|
||||
);
|
||||
|
||||
// Get
|
||||
final product = dbManager.get(
|
||||
boxName: HiveBoxNames.productBox,
|
||||
key: 'product_123',
|
||||
);
|
||||
```
|
||||
|
||||
## 3. Cache with Expiration
|
||||
|
||||
```dart
|
||||
// Save to cache
|
||||
await dbManager.saveToCache(
|
||||
key: HiveKeys.productsCacheKey,
|
||||
data: products,
|
||||
);
|
||||
|
||||
// Get from cache
|
||||
final cached = dbManager.getFromCache<List<Product>>(
|
||||
key: HiveKeys.productsCacheKey,
|
||||
maxAge: CacheDuration.products, // 6 hours
|
||||
);
|
||||
|
||||
if (cached == null) {
|
||||
// Cache expired - fetch fresh data
|
||||
}
|
||||
```
|
||||
|
||||
## 4. Create New Model
|
||||
|
||||
```dart
|
||||
import 'package:hive_ce/hive.dart';
|
||||
import 'package:worker/core/constants/storage_constants.dart';
|
||||
|
||||
part 'product_model.g.dart';
|
||||
|
||||
@HiveType(typeId: HiveTypeIds.product)
|
||||
class ProductModel extends HiveObject {
|
||||
@HiveField(0)
|
||||
final String id;
|
||||
|
||||
@HiveField(1)
|
||||
final String name;
|
||||
|
||||
ProductModel({required this.id, required this.name});
|
||||
}
|
||||
```
|
||||
|
||||
Then run:
|
||||
```bash
|
||||
dart run build_runner build --delete-conflicting-outputs
|
||||
```
|
||||
|
||||
## 5. Logout (Clear User Data)
|
||||
|
||||
```dart
|
||||
await HiveInitializer.logout();
|
||||
```
|
||||
|
||||
## Available Boxes
|
||||
|
||||
- `HiveBoxNames.userBox` - User profile
|
||||
- `HiveBoxNames.productBox` - Products
|
||||
- `HiveBoxNames.cartBox` - Cart items
|
||||
- `HiveBoxNames.orderBox` - Orders
|
||||
- `HiveBoxNames.projectBox` - Projects
|
||||
- `HiveBoxNames.loyaltyBox` - Loyalty data
|
||||
- `HiveBoxNames.settingsBox` - Settings
|
||||
- `HiveBoxNames.cacheBox` - API cache
|
||||
- `HiveBoxNames.notificationBox` - Notifications
|
||||
|
||||
See `/lib/core/constants/storage_constants.dart` for complete list.
|
||||
|
||||
## Cache Durations
|
||||
|
||||
Pre-configured expiration times:
|
||||
- `CacheDuration.products` - 6 hours
|
||||
- `CacheDuration.categories` - 24 hours
|
||||
- `CacheDuration.loyaltyPoints` - 1 hour
|
||||
- `CacheDuration.rewards` - 12 hours
|
||||
- `CacheDuration.promotions` - 2 hours
|
||||
|
||||
## Need More Info?
|
||||
|
||||
- Full Documentation: `/lib/core/database/README.md`
|
||||
- Setup Summary: `/HIVE_SETUP.md`
|
||||
- Storage Constants: `/lib/core/constants/storage_constants.dart`
|
||||
478
lib/core/database/README.md
Normal file
478
lib/core/database/README.md
Normal file
@@ -0,0 +1,478 @@
|
||||
# Hive CE Database Setup
|
||||
|
||||
This directory contains the Hive CE (Community Edition) database configuration and services for the Worker Flutter app.
|
||||
|
||||
## Overview
|
||||
|
||||
The app uses Hive CE for offline-first local data persistence. Hive is a lightweight, fast NoSQL database written in pure Dart, perfect for Flutter applications.
|
||||
|
||||
## Key Features
|
||||
|
||||
- **Offline-First**: All data is stored locally and synced with the backend
|
||||
- **Fast Performance**: Hive is optimized for speed with minimal overhead
|
||||
- **Type-Safe**: Uses type adapters for strong typing
|
||||
- **Encryption Support**: Optional AES encryption for sensitive data
|
||||
- **Auto-Compaction**: Automatic database maintenance and cleanup
|
||||
- **Migration Support**: Built-in schema versioning and migrations
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
lib/core/database/
|
||||
├── README.md # This file
|
||||
├── hive_service.dart # Main Hive initialization and lifecycle management
|
||||
├── database_manager.dart # High-level database operations
|
||||
└── models/
|
||||
├── cached_data.dart # Generic cache wrapper model
|
||||
└── enums.dart # All enum type adapters
|
||||
```
|
||||
|
||||
## Setup Instructions
|
||||
|
||||
### 1. Install Dependencies
|
||||
|
||||
The required packages are already in `pubspec.yaml`:
|
||||
|
||||
```yaml
|
||||
dependencies:
|
||||
hive_ce: ^2.6.0
|
||||
hive_ce_flutter: ^2.1.0
|
||||
|
||||
dev_dependencies:
|
||||
hive_ce_generator: ^1.6.0
|
||||
build_runner: ^2.4.11
|
||||
```
|
||||
|
||||
Run:
|
||||
```bash
|
||||
flutter pub get
|
||||
```
|
||||
|
||||
### 2. Generate Type Adapters
|
||||
|
||||
After creating Hive models with `@HiveType` annotations, run:
|
||||
|
||||
```bash
|
||||
dart run build_runner build --delete-conflicting-outputs
|
||||
```
|
||||
|
||||
Or for continuous watching during development:
|
||||
```bash
|
||||
dart run build_runner watch --delete-conflicting-outputs
|
||||
```
|
||||
|
||||
### 3. Initialize Hive in main.dart
|
||||
|
||||
```dart
|
||||
import 'package:flutter/material.dart';
|
||||
import 'core/database/hive_service.dart';
|
||||
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
// Initialize Hive
|
||||
final hiveService = HiveService();
|
||||
await hiveService.initialize();
|
||||
|
||||
runApp(const MyApp());
|
||||
}
|
||||
```
|
||||
|
||||
## Creating Hive Models
|
||||
|
||||
### Basic Model Example
|
||||
|
||||
```dart
|
||||
import 'package:hive_ce/hive.dart';
|
||||
import '../../constants/storage_constants.dart';
|
||||
|
||||
part 'user_model.g.dart'; // Generated file
|
||||
|
||||
@HiveType(typeId: HiveTypeIds.user)
|
||||
class UserModel extends HiveObject {
|
||||
@HiveField(0)
|
||||
final String id;
|
||||
|
||||
@HiveField(1)
|
||||
final String name;
|
||||
|
||||
@HiveField(2)
|
||||
final String email;
|
||||
|
||||
UserModel({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.email,
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### Enum Example
|
||||
|
||||
```dart
|
||||
@HiveType(typeId: HiveTypeIds.memberTier)
|
||||
enum MemberTier {
|
||||
@HiveField(0)
|
||||
gold,
|
||||
|
||||
@HiveField(1)
|
||||
platinum,
|
||||
|
||||
@HiveField(2)
|
||||
diamond,
|
||||
}
|
||||
```
|
||||
|
||||
### Important Rules
|
||||
|
||||
1. **Type IDs must be unique** across the entire app (0-223 for user types)
|
||||
2. **Never change field numbers** once assigned - it will break existing data
|
||||
3. **Use `part` directive** to include generated adapter file
|
||||
4. **Extend HiveObject** for model classes (optional but recommended for auto-save)
|
||||
5. **Register adapters** before opening boxes (handled by HiveService)
|
||||
|
||||
## Box Management
|
||||
|
||||
### Available Boxes
|
||||
|
||||
The app uses these pre-configured boxes (see `storage_constants.dart`):
|
||||
|
||||
- `user_box` - User profile and auth data (encrypted)
|
||||
- `product_box` - Product catalog cache
|
||||
- `cart_box` - Shopping cart items (encrypted)
|
||||
- `order_box` - Order history (encrypted)
|
||||
- `project_box` - Construction projects (encrypted)
|
||||
- `loyalty_box` - Loyalty transactions (encrypted)
|
||||
- `rewards_box` - Rewards catalog
|
||||
- `settings_box` - App settings
|
||||
- `cache_box` - Generic API cache
|
||||
- `sync_state_box` - Sync timestamps
|
||||
- `notification_box` - Notifications
|
||||
- `address_box` - Delivery addresses (encrypted)
|
||||
- `offline_queue_box` - Failed API requests queue (encrypted)
|
||||
|
||||
### Using Boxes
|
||||
|
||||
```dart
|
||||
// Using DatabaseManager (recommended)
|
||||
final dbManager = DatabaseManager();
|
||||
|
||||
// Save data
|
||||
await dbManager.save(
|
||||
boxName: HiveBoxNames.productBox,
|
||||
key: 'product_123',
|
||||
value: product,
|
||||
);
|
||||
|
||||
// Get data
|
||||
final product = dbManager.get(
|
||||
boxName: HiveBoxNames.productBox,
|
||||
key: 'product_123',
|
||||
);
|
||||
|
||||
// Get all
|
||||
final products = dbManager.getAll(boxName: HiveBoxNames.productBox);
|
||||
```
|
||||
|
||||
## Caching Strategy
|
||||
|
||||
### Save to Cache
|
||||
|
||||
```dart
|
||||
final dbManager = DatabaseManager();
|
||||
|
||||
await dbManager.saveToCache(
|
||||
key: HiveKeys.productsCacheKey,
|
||||
data: products,
|
||||
);
|
||||
```
|
||||
|
||||
### Get from Cache
|
||||
|
||||
```dart
|
||||
final products = dbManager.getFromCache<List<Product>>(
|
||||
key: HiveKeys.productsCacheKey,
|
||||
maxAge: CacheDuration.products, // 6 hours
|
||||
);
|
||||
|
||||
if (products == null) {
|
||||
// Cache miss or expired - fetch from API
|
||||
final freshProducts = await api.getProducts();
|
||||
await dbManager.saveToCache(
|
||||
key: HiveKeys.productsCacheKey,
|
||||
data: freshProducts,
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Check Cache Validity
|
||||
|
||||
```dart
|
||||
final isValid = dbManager.isCacheValid(
|
||||
key: HiveKeys.productsCacheKey,
|
||||
maxAge: CacheDuration.products,
|
||||
);
|
||||
|
||||
if (!isValid) {
|
||||
// Refresh cache
|
||||
}
|
||||
```
|
||||
|
||||
## Offline Queue
|
||||
|
||||
Handle failed API requests when offline:
|
||||
|
||||
```dart
|
||||
// Add to queue when API call fails
|
||||
await dbManager.addToOfflineQueue({
|
||||
'endpoint': '/api/orders',
|
||||
'method': 'POST',
|
||||
'body': orderData,
|
||||
});
|
||||
|
||||
// Process queue when back online
|
||||
final queue = dbManager.getOfflineQueue();
|
||||
for (var i = 0; i < queue.length; i++) {
|
||||
try {
|
||||
await api.request(queue[i]);
|
||||
await dbManager.removeFromOfflineQueue(i);
|
||||
} catch (e) {
|
||||
// Keep in queue for next retry
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Data Synchronization
|
||||
|
||||
Track sync state for different data types:
|
||||
|
||||
```dart
|
||||
// Update sync timestamp
|
||||
await dbManager.updateSyncTime(HiveKeys.productsSyncTime);
|
||||
|
||||
// Get last sync time
|
||||
final lastSync = dbManager.getLastSyncTime(HiveKeys.productsSyncTime);
|
||||
|
||||
// Check if needs sync
|
||||
final needsSync = dbManager.needsSync(
|
||||
dataType: HiveKeys.productsSyncTime,
|
||||
syncInterval: Duration(hours: 6),
|
||||
);
|
||||
```
|
||||
|
||||
## Encryption
|
||||
|
||||
Enable encryption for sensitive data in `storage_constants.dart`:
|
||||
|
||||
```dart
|
||||
class HiveDatabaseConfig {
|
||||
static const bool enableEncryption = true;
|
||||
}
|
||||
```
|
||||
|
||||
Generate and store encryption key securely:
|
||||
|
||||
```dart
|
||||
// Generate key
|
||||
final encryptionKey = HiveService.generateEncryptionKey();
|
||||
|
||||
// Store securely using flutter_secure_storage
|
||||
final secureStorage = FlutterSecureStorage();
|
||||
await secureStorage.write(
|
||||
key: 'hive_encryption_key',
|
||||
value: base64Encode(encryptionKey),
|
||||
);
|
||||
|
||||
// Initialize with key
|
||||
final storedKey = await secureStorage.read(key: 'hive_encryption_key');
|
||||
await hiveService.initialize(
|
||||
encryptionKey: base64Decode(storedKey!),
|
||||
);
|
||||
```
|
||||
|
||||
## Migrations
|
||||
|
||||
Handle schema changes:
|
||||
|
||||
```dart
|
||||
// In hive_service.dart, add migration logic:
|
||||
|
||||
Future<void> _migrateToVersion(int version) async {
|
||||
switch (version) {
|
||||
case 2:
|
||||
await _migrateV1ToV2();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _migrateV1ToV2() async {
|
||||
// Example: Add new field to existing data
|
||||
final userBox = Hive.box(HiveBoxNames.userBox);
|
||||
|
||||
for (var key in userBox.keys) {
|
||||
final user = userBox.get(key);
|
||||
// Update user data structure
|
||||
await userBox.put(key, updatedUser);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Database Maintenance
|
||||
|
||||
### Clear Expired Cache
|
||||
|
||||
```dart
|
||||
await dbManager.clearExpiredCache();
|
||||
```
|
||||
|
||||
### Compact Boxes
|
||||
|
||||
```dart
|
||||
final hiveService = HiveService();
|
||||
// Compaction happens automatically during initialization
|
||||
```
|
||||
|
||||
### Clear User Data (Logout)
|
||||
|
||||
```dart
|
||||
await hiveService.clearUserData();
|
||||
```
|
||||
|
||||
### Clear All Data
|
||||
|
||||
```dart
|
||||
await hiveService.clearAllData();
|
||||
```
|
||||
|
||||
### Get Statistics
|
||||
|
||||
```dart
|
||||
final stats = dbManager.getStatistics();
|
||||
dbManager.printStatistics();
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Always initialize Hive before using any boxes**
|
||||
2. **Use DatabaseManager for common operations**
|
||||
3. **Cache frequently accessed data**
|
||||
4. **Set appropriate cache expiration times**
|
||||
5. **Handle errors gracefully** - Hive operations can fail
|
||||
6. **Use transactions for multiple related updates**
|
||||
7. **Compact boxes periodically** for optimal performance
|
||||
8. **Never store large files in Hive** - use file system instead
|
||||
9. **Test migrations thoroughly** before release
|
||||
10. **Monitor database size** in production
|
||||
|
||||
## Debugging
|
||||
|
||||
### Print Box Contents
|
||||
|
||||
```dart
|
||||
final box = Hive.box(HiveBoxNames.productBox);
|
||||
print('Box length: ${box.length}');
|
||||
print('Keys: ${box.keys}');
|
||||
print('Values: ${box.values}');
|
||||
```
|
||||
|
||||
### Check Box Location
|
||||
|
||||
```dart
|
||||
print('Hive path: ${Hive.box(HiveBoxNames.settingsBox).path}');
|
||||
```
|
||||
|
||||
### View Statistics
|
||||
|
||||
```dart
|
||||
DatabaseManager().printStatistics();
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "Box not found" Error
|
||||
- Ensure Hive is initialized before accessing boxes
|
||||
- Check that box name is correct
|
||||
|
||||
### "TypeAdapter not registered" Error
|
||||
- Run `build_runner` to generate adapters
|
||||
- Ensure adapter is registered in `HiveService._registerTypeAdapters()`
|
||||
|
||||
### "Cannot write null values" Error
|
||||
- Make fields nullable with `?` or provide default values
|
||||
- Check that HiveField annotations are correct
|
||||
|
||||
### Data Corruption
|
||||
- Enable backup/restore functionality
|
||||
- Implement data validation before saving
|
||||
- Use try-catch blocks around Hive operations
|
||||
|
||||
## Testing
|
||||
|
||||
```dart
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:hive_ce/hive.dart';
|
||||
import 'package:hive_ce_flutter/hive_flutter.dart';
|
||||
|
||||
void main() {
|
||||
setUp(() async {
|
||||
await Hive.initFlutter();
|
||||
// Register test adapters
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await Hive.deleteFromDisk();
|
||||
});
|
||||
|
||||
test('Save and retrieve user', () async {
|
||||
final box = await Hive.openBox('test_box');
|
||||
await box.put('user', UserModel(id: '1', name: 'Test'));
|
||||
|
||||
final user = box.get('user');
|
||||
expect(user.name, 'Test');
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- [Hive CE Documentation](https://github.com/IO-Design-Team/hive_ce)
|
||||
- [Original Hive Documentation](https://docs.hivedb.dev/)
|
||||
- [Flutter Offline-First Best Practices](https://flutter.dev/docs/cookbook/persistence)
|
||||
|
||||
## Type Adapter Registry
|
||||
|
||||
### Registered Type IDs (0-223)
|
||||
|
||||
| Type ID | Model | Status |
|
||||
|---------|-------|--------|
|
||||
| 0 | UserModel | TODO |
|
||||
| 1 | ProductModel | TODO |
|
||||
| 2 | CartItemModel | TODO |
|
||||
| 3 | OrderModel | TODO |
|
||||
| 4 | ProjectModel | TODO |
|
||||
| 5 | LoyaltyTransactionModel | TODO |
|
||||
| 10 | OrderItemModel | TODO |
|
||||
| 11 | AddressModel | TODO |
|
||||
| 12 | CategoryModel | TODO |
|
||||
| 13 | RewardModel | TODO |
|
||||
| 14 | GiftModel | TODO |
|
||||
| 15 | NotificationModel | TODO |
|
||||
| 16 | QuoteModel | TODO |
|
||||
| 17 | PaymentModel | TODO |
|
||||
| 18 | PromotionModel | TODO |
|
||||
| 19 | ReferralModel | TODO |
|
||||
| 20 | MemberTier (enum) | Created |
|
||||
| 21 | UserType (enum) | Created |
|
||||
| 22 | OrderStatus (enum) | Created |
|
||||
| 23 | ProjectStatus (enum) | Created |
|
||||
| 24 | ProjectType (enum) | Created |
|
||||
| 25 | TransactionType (enum) | Created |
|
||||
| 26 | GiftStatus (enum) | Created |
|
||||
| 27 | PaymentStatus (enum) | Created |
|
||||
| 28 | NotificationType (enum) | Created |
|
||||
| 29 | PaymentMethod (enum) | Created |
|
||||
| 30 | CachedData | Created |
|
||||
| 31 | SyncState | TODO |
|
||||
| 32 | OfflineRequest | TODO |
|
||||
|
||||
**IMPORTANT**: Never reuse or change these type IDs once assigned!
|
||||
25
lib/core/database/database.dart
Normal file
25
lib/core/database/database.dart
Normal file
@@ -0,0 +1,25 @@
|
||||
/// Hive CE Database Export
|
||||
///
|
||||
/// This file provides a convenient way to import all database-related
|
||||
/// classes and utilities in a single import.
|
||||
///
|
||||
/// Usage:
|
||||
/// ```dart
|
||||
/// import 'package:worker/core/database/database.dart';
|
||||
/// ```
|
||||
library;
|
||||
|
||||
// Constants
|
||||
export 'package:worker/core/constants/storage_constants.dart';
|
||||
|
||||
// Services
|
||||
export 'package:worker/core/database/database_manager.dart';
|
||||
export 'package:worker/core/database/hive_initializer.dart';
|
||||
export 'package:worker/core/database/hive_service.dart';
|
||||
|
||||
// Models
|
||||
export 'package:worker/core/database/models/cached_data.dart';
|
||||
export 'package:worker/core/database/models/enums.dart';
|
||||
|
||||
// Auto-generated registrar
|
||||
export 'package:worker/hive_registrar.g.dart';
|
||||
411
lib/core/database/database_manager.dart
Normal file
411
lib/core/database/database_manager.dart
Normal file
@@ -0,0 +1,411 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:hive_ce/hive.dart';
|
||||
|
||||
import 'package:worker/core/constants/storage_constants.dart';
|
||||
import 'package:worker/core/database/hive_service.dart';
|
||||
|
||||
/// Database Manager for common Hive operations
|
||||
///
|
||||
/// Provides high-level database operations and utilities for working
|
||||
/// with Hive boxes across the application.
|
||||
///
|
||||
/// Features:
|
||||
/// - CRUD operations with error handling
|
||||
/// - Cache management with expiration
|
||||
/// - Bulk operations
|
||||
/// - Data validation
|
||||
/// - Sync state tracking
|
||||
class DatabaseManager {
|
||||
DatabaseManager({HiveService? hiveService})
|
||||
: _hiveService = hiveService ?? HiveService();
|
||||
|
||||
final HiveService _hiveService;
|
||||
|
||||
/// Get a box safely
|
||||
Box<T> _getBox<T>(String boxName) {
|
||||
if (!_hiveService.isBoxOpen(boxName)) {
|
||||
throw HiveError('Box $boxName is not open. Initialize HiveService first.');
|
||||
}
|
||||
return _hiveService.getBox<T>(boxName);
|
||||
}
|
||||
|
||||
// ==================== Generic CRUD Operations ====================
|
||||
|
||||
/// Save a value to a box
|
||||
Future<void> save<T>({
|
||||
required String boxName,
|
||||
required String key,
|
||||
required T value,
|
||||
}) async {
|
||||
try {
|
||||
final box = _getBox<T>(boxName);
|
||||
await box.put(key, value);
|
||||
debugPrint('DatabaseManager: Saved $key to $boxName');
|
||||
} catch (e, stackTrace) {
|
||||
debugPrint('DatabaseManager: Error saving $key to $boxName: $e');
|
||||
debugPrint('StackTrace: $stackTrace');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a value from a box
|
||||
T? get<T>({
|
||||
required String boxName,
|
||||
required String key,
|
||||
T? defaultValue,
|
||||
}) {
|
||||
try {
|
||||
final box = _getBox<T>(boxName);
|
||||
return box.get(key, defaultValue: defaultValue);
|
||||
} catch (e, stackTrace) {
|
||||
debugPrint('DatabaseManager: Error getting $key from $boxName: $e');
|
||||
debugPrint('StackTrace: $stackTrace');
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
/// Delete a value from a box
|
||||
Future<void> delete({
|
||||
required String boxName,
|
||||
required String key,
|
||||
}) async {
|
||||
try {
|
||||
final box = _getBox<dynamic>(boxName);
|
||||
await box.delete(key);
|
||||
debugPrint('DatabaseManager: Deleted $key from $boxName');
|
||||
} catch (e, stackTrace) {
|
||||
debugPrint('DatabaseManager: Error deleting $key from $boxName: $e');
|
||||
debugPrint('StackTrace: $stackTrace');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if a key exists in a box
|
||||
bool exists({
|
||||
required String boxName,
|
||||
required String key,
|
||||
}) {
|
||||
try {
|
||||
final box = _getBox<dynamic>(boxName);
|
||||
return box.containsKey(key);
|
||||
} catch (e) {
|
||||
debugPrint('DatabaseManager: Error checking $key in $boxName: $e');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Get all values from a box
|
||||
List<T> getAll<T>({required String boxName}) {
|
||||
try {
|
||||
final box = _getBox<T>(boxName);
|
||||
return box.values.toList();
|
||||
} catch (e, stackTrace) {
|
||||
debugPrint('DatabaseManager: Error getting all from $boxName: $e');
|
||||
debugPrint('StackTrace: $stackTrace');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/// Save multiple values to a box
|
||||
Future<void> saveAll<T>({
|
||||
required String boxName,
|
||||
required Map<String, T> entries,
|
||||
}) async {
|
||||
try {
|
||||
final box = _getBox<T>(boxName);
|
||||
await box.putAll(entries);
|
||||
debugPrint('DatabaseManager: Saved ${entries.length} items to $boxName');
|
||||
} catch (e, stackTrace) {
|
||||
debugPrint('DatabaseManager: Error saving all to $boxName: $e');
|
||||
debugPrint('StackTrace: $stackTrace');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// Clear all data from a box
|
||||
Future<void> clearBox({required String boxName}) async {
|
||||
try {
|
||||
final box = _getBox<dynamic>(boxName);
|
||||
await box.clear();
|
||||
debugPrint('DatabaseManager: Cleared $boxName');
|
||||
} catch (e, stackTrace) {
|
||||
debugPrint('DatabaseManager: Error clearing $boxName: $e');
|
||||
debugPrint('StackTrace: $stackTrace');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== Cache Operations ====================
|
||||
|
||||
/// Save data to cache with timestamp
|
||||
Future<void> saveToCache<T>({
|
||||
required String key,
|
||||
required T data,
|
||||
}) async {
|
||||
try {
|
||||
final cacheBox = _getBox<dynamic>(HiveBoxNames.cacheBox);
|
||||
await cacheBox.put(key, {
|
||||
'data': data,
|
||||
'timestamp': DateTime.now().toIso8601String(),
|
||||
});
|
||||
debugPrint('DatabaseManager: Cached $key');
|
||||
} catch (e, stackTrace) {
|
||||
debugPrint('DatabaseManager: Error caching $key: $e');
|
||||
debugPrint('StackTrace: $stackTrace');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// Get data from cache
|
||||
///
|
||||
/// Returns null if cache is expired or doesn't exist
|
||||
T? getFromCache<T>({
|
||||
required String key,
|
||||
Duration? maxAge,
|
||||
}) {
|
||||
try {
|
||||
final cacheBox = _getBox<dynamic>(HiveBoxNames.cacheBox);
|
||||
final cachedData = cacheBox.get(key) as Map<dynamic, dynamic>?;
|
||||
|
||||
if (cachedData == null) {
|
||||
debugPrint('DatabaseManager: Cache miss for $key');
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check if cache is expired
|
||||
if (maxAge != null) {
|
||||
final timestamp = DateTime.parse(cachedData['timestamp'] as String);
|
||||
final age = DateTime.now().difference(timestamp);
|
||||
|
||||
if (age > maxAge) {
|
||||
debugPrint('DatabaseManager: Cache expired for $key (age: $age)');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
debugPrint('DatabaseManager: Cache hit for $key');
|
||||
return cachedData['data'] as T?;
|
||||
} catch (e, stackTrace) {
|
||||
debugPrint('DatabaseManager: Error getting cache $key: $e');
|
||||
debugPrint('StackTrace: $stackTrace');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if cache is valid (exists and not expired)
|
||||
bool isCacheValid({
|
||||
required String key,
|
||||
Duration? maxAge,
|
||||
}) {
|
||||
try {
|
||||
final cacheBox = _getBox<dynamic>(HiveBoxNames.cacheBox);
|
||||
final cachedData = cacheBox.get(key) as Map<dynamic, dynamic>?;
|
||||
|
||||
if (cachedData == null) return false;
|
||||
|
||||
if (maxAge != null) {
|
||||
final timestamp = DateTime.parse(cachedData['timestamp'] as String);
|
||||
final age = DateTime.now().difference(timestamp);
|
||||
return age <= maxAge;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (e) {
|
||||
debugPrint('DatabaseManager: Error checking cache validity $key: $e');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Clear expired cache entries
|
||||
Future<void> clearExpiredCache() async {
|
||||
try {
|
||||
final cacheBox = _getBox<dynamic>(HiveBoxNames.cacheBox);
|
||||
final keysToDelete = <String>[];
|
||||
|
||||
for (final key in cacheBox.keys) {
|
||||
final cachedData = cacheBox.get(key) as Map<dynamic, dynamic>?;
|
||||
if (cachedData != null) {
|
||||
try {
|
||||
final timestamp = DateTime.parse(cachedData['timestamp'] as String);
|
||||
final age = DateTime.now().difference(timestamp);
|
||||
|
||||
// Use default max age of 24 hours
|
||||
if (age > const Duration(hours: 24)) {
|
||||
keysToDelete.add(key as String);
|
||||
}
|
||||
} catch (e) {
|
||||
// Invalid cache entry, mark for deletion
|
||||
keysToDelete.add(key as String);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (final key in keysToDelete) {
|
||||
await cacheBox.delete(key);
|
||||
}
|
||||
|
||||
debugPrint('DatabaseManager: Cleared ${keysToDelete.length} expired cache entries');
|
||||
} catch (e, stackTrace) {
|
||||
debugPrint('DatabaseManager: Error clearing expired cache: $e');
|
||||
debugPrint('StackTrace: $stackTrace');
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== Sync State Operations ====================
|
||||
|
||||
/// Update sync timestamp for a data type
|
||||
Future<void> updateSyncTime(String dataType) async {
|
||||
try {
|
||||
final syncBox = _getBox<dynamic>(HiveBoxNames.syncStateBox);
|
||||
await syncBox.put(dataType, DateTime.now().toIso8601String());
|
||||
debugPrint('DatabaseManager: Updated sync time for $dataType');
|
||||
} catch (e, stackTrace) {
|
||||
debugPrint('DatabaseManager: Error updating sync time for $dataType: $e');
|
||||
debugPrint('StackTrace: $stackTrace');
|
||||
}
|
||||
}
|
||||
|
||||
/// Get last sync time for a data type
|
||||
DateTime? getLastSyncTime(String dataType) {
|
||||
try {
|
||||
final syncBox = _getBox<dynamic>(HiveBoxNames.syncStateBox);
|
||||
final timestamp = syncBox.get(dataType);
|
||||
|
||||
if (timestamp == null) return null;
|
||||
|
||||
return DateTime.parse(timestamp as String);
|
||||
} catch (e) {
|
||||
debugPrint('DatabaseManager: Error getting sync time for $dataType: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if data needs sync
|
||||
bool needsSync({
|
||||
required String dataType,
|
||||
required Duration syncInterval,
|
||||
}) {
|
||||
final lastSync = getLastSyncTime(dataType);
|
||||
|
||||
if (lastSync == null) return true;
|
||||
|
||||
final timeSinceSync = DateTime.now().difference(lastSync);
|
||||
return timeSinceSync > syncInterval;
|
||||
}
|
||||
|
||||
// ==================== Settings Operations ====================
|
||||
|
||||
/// Save a setting
|
||||
Future<void> saveSetting<T>({
|
||||
required String key,
|
||||
required T value,
|
||||
}) async {
|
||||
await save(
|
||||
boxName: HiveBoxNames.settingsBox,
|
||||
key: key,
|
||||
value: value,
|
||||
);
|
||||
}
|
||||
|
||||
/// Get a setting
|
||||
T? getSetting<T>({
|
||||
required String key,
|
||||
T? defaultValue,
|
||||
}) {
|
||||
return get(
|
||||
boxName: HiveBoxNames.settingsBox,
|
||||
key: key,
|
||||
defaultValue: defaultValue,
|
||||
);
|
||||
}
|
||||
|
||||
// ==================== Offline Queue Operations ====================
|
||||
|
||||
/// Add request to offline queue
|
||||
Future<void> addToOfflineQueue(Map<String, dynamic> request) async {
|
||||
try {
|
||||
final queueBox = _getBox<dynamic>(HiveBoxNames.offlineQueueBox);
|
||||
|
||||
// Check queue size limit
|
||||
if (queueBox.length >= HiveDatabaseConfig.maxOfflineQueueSize) {
|
||||
debugPrint('DatabaseManager: Offline queue is full, removing oldest item');
|
||||
await queueBox.deleteAt(0);
|
||||
}
|
||||
|
||||
await queueBox.add({
|
||||
...request,
|
||||
'timestamp': DateTime.now().toIso8601String(),
|
||||
});
|
||||
|
||||
debugPrint('DatabaseManager: Added request to offline queue');
|
||||
} catch (e, stackTrace) {
|
||||
debugPrint('DatabaseManager: Error adding to offline queue: $e');
|
||||
debugPrint('StackTrace: $stackTrace');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// Get all offline queue items
|
||||
List<Map<String, dynamic>> getOfflineQueue() {
|
||||
try {
|
||||
final queueBox = _getBox<dynamic>(HiveBoxNames.offlineQueueBox);
|
||||
return queueBox.values
|
||||
.map((e) => Map<String, dynamic>.from(e as Map<dynamic, dynamic>))
|
||||
.toList();
|
||||
} catch (e) {
|
||||
debugPrint('DatabaseManager: Error getting offline queue: $e');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove item from offline queue
|
||||
Future<void> removeFromOfflineQueue(int index) async {
|
||||
try {
|
||||
final queueBox = _getBox<dynamic>(HiveBoxNames.offlineQueueBox);
|
||||
await queueBox.deleteAt(index);
|
||||
debugPrint('DatabaseManager: Removed item $index from offline queue');
|
||||
} catch (e, stackTrace) {
|
||||
debugPrint('DatabaseManager: Error removing from offline queue: $e');
|
||||
debugPrint('StackTrace: $stackTrace');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// Clear offline queue
|
||||
Future<void> clearOfflineQueue() async {
|
||||
await clearBox(boxName: HiveBoxNames.offlineQueueBox);
|
||||
}
|
||||
|
||||
// ==================== Statistics ====================
|
||||
|
||||
/// Get database statistics
|
||||
Map<String, dynamic> getStatistics() {
|
||||
final stats = <String, dynamic>{};
|
||||
|
||||
for (final boxName in HiveBoxNames.allBoxes) {
|
||||
try {
|
||||
if (_hiveService.isBoxOpen(boxName)) {
|
||||
final box = _getBox<dynamic>(boxName);
|
||||
stats[boxName] = {
|
||||
'count': box.length,
|
||||
'keys': box.keys.length,
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
stats[boxName] = {'error': e.toString()};
|
||||
}
|
||||
}
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
/// Print database statistics
|
||||
void printStatistics() {
|
||||
final stats = getStatistics();
|
||||
debugPrint('=== Hive Database Statistics ===');
|
||||
stats.forEach((boxName, data) {
|
||||
debugPrint('$boxName: $data');
|
||||
});
|
||||
debugPrint('================================');
|
||||
}
|
||||
}
|
||||
115
lib/core/database/hive_initializer.dart
Normal file
115
lib/core/database/hive_initializer.dart
Normal file
@@ -0,0 +1,115 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import 'package:worker/core/database/database_manager.dart';
|
||||
import 'package:worker/core/database/hive_service.dart';
|
||||
|
||||
/// Hive Database Initializer
|
||||
///
|
||||
/// Provides a simple API for initializing the Hive database
|
||||
/// in the main.dart file.
|
||||
///
|
||||
/// Example usage:
|
||||
/// ```dart
|
||||
/// void main() async {
|
||||
/// WidgetsFlutterBinding.ensureInitialized();
|
||||
///
|
||||
/// // Initialize Hive
|
||||
/// await HiveInitializer.initialize();
|
||||
///
|
||||
/// runApp(const MyApp());
|
||||
/// }
|
||||
/// ```
|
||||
class HiveInitializer {
|
||||
/// Initialize Hive database
|
||||
///
|
||||
/// This method should be called once during app startup.
|
||||
/// It initializes Hive, registers adapters, and opens boxes.
|
||||
///
|
||||
/// [enableEncryption] - Enable AES encryption for sensitive boxes
|
||||
/// [encryptionKey] - Optional custom encryption key (256-bit)
|
||||
/// [verbose] - Enable verbose logging for debugging
|
||||
static Future<void> initialize({
|
||||
bool enableEncryption = false,
|
||||
List<int>? encryptionKey,
|
||||
bool verbose = false,
|
||||
}) async {
|
||||
try {
|
||||
if (verbose) {
|
||||
debugPrint('HiveInitializer: Starting initialization...');
|
||||
}
|
||||
|
||||
// Get HiveService instance
|
||||
final hiveService = HiveService();
|
||||
|
||||
// Initialize Hive
|
||||
await hiveService.initialize(
|
||||
encryptionKey: enableEncryption ? encryptionKey : null,
|
||||
);
|
||||
|
||||
// Perform initial maintenance
|
||||
if (verbose) {
|
||||
debugPrint('HiveInitializer: Performing initial maintenance...');
|
||||
}
|
||||
|
||||
final dbManager = DatabaseManager();
|
||||
|
||||
// Clear expired cache on app start
|
||||
await dbManager.clearExpiredCache();
|
||||
|
||||
// Print statistics in debug mode
|
||||
if (verbose && kDebugMode) {
|
||||
dbManager.printStatistics();
|
||||
}
|
||||
|
||||
if (verbose) {
|
||||
debugPrint('HiveInitializer: Initialization complete');
|
||||
}
|
||||
} catch (e, stackTrace) {
|
||||
debugPrint('HiveInitializer: Initialization failed: $e');
|
||||
debugPrint('StackTrace: $stackTrace');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// Close Hive database
|
||||
///
|
||||
/// Should be called when app is terminating.
|
||||
/// Usually not needed for normal app lifecycle.
|
||||
static Future<void> close() async {
|
||||
final hiveService = HiveService();
|
||||
await hiveService.close();
|
||||
}
|
||||
|
||||
/// Reset database (clear all data)
|
||||
///
|
||||
/// WARNING: This will delete all local data!
|
||||
/// Use only for logout or app reset functionality.
|
||||
static Future<void> reset() async {
|
||||
final hiveService = HiveService();
|
||||
await hiveService.clearAllData();
|
||||
}
|
||||
|
||||
/// Clear user data (logout)
|
||||
///
|
||||
/// Clears user-specific data while preserving app settings and cache.
|
||||
static Future<void> logout() async {
|
||||
final hiveService = HiveService();
|
||||
await hiveService.clearUserData();
|
||||
}
|
||||
|
||||
/// Get database statistics
|
||||
///
|
||||
/// Returns statistics about all Hive boxes.
|
||||
static Map<String, dynamic> getStatistics() {
|
||||
final dbManager = DatabaseManager();
|
||||
return dbManager.getStatistics();
|
||||
}
|
||||
|
||||
/// Print database statistics (debug only)
|
||||
static void printStatistics() {
|
||||
if (kDebugMode) {
|
||||
final dbManager = DatabaseManager();
|
||||
dbManager.printStatistics();
|
||||
}
|
||||
}
|
||||
}
|
||||
409
lib/core/database/hive_service.dart
Normal file
409
lib/core/database/hive_service.dart
Normal file
@@ -0,0 +1,409 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:hive_ce_flutter/hive_flutter.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
import 'package:worker/core/constants/storage_constants.dart';
|
||||
import 'package:worker/hive_registrar.g.dart';
|
||||
|
||||
/// Hive CE (Community Edition) Database Service
|
||||
///
|
||||
/// This service manages the initialization, configuration, and lifecycle
|
||||
/// of the Hive database for offline-first functionality.
|
||||
///
|
||||
/// Features:
|
||||
/// - Box initialization and registration
|
||||
/// - Type adapter registration
|
||||
/// - Encryption support
|
||||
/// - Database compaction
|
||||
/// - Migration handling
|
||||
/// - Error recovery
|
||||
class HiveService {
|
||||
HiveService._internal();
|
||||
|
||||
// Singleton pattern
|
||||
factory HiveService() => _instance;
|
||||
|
||||
static final HiveService _instance = HiveService._internal();
|
||||
|
||||
/// Indicates whether Hive has been initialized
|
||||
bool _isInitialized = false;
|
||||
bool get isInitialized => _isInitialized;
|
||||
|
||||
/// Encryption cipher (if enabled)
|
||||
HiveAesCipher? _encryptionCipher;
|
||||
|
||||
/// Initialize Hive database
|
||||
///
|
||||
/// This should be called once during app startup, before any
|
||||
/// Hive operations are performed.
|
||||
///
|
||||
/// [encryptionKey] - Optional 256-bit encryption key for secure storage
|
||||
/// If not provided and encryption is enabled, a new key will be generated.
|
||||
Future<void> initialize({List<int>? encryptionKey}) async {
|
||||
if (_isInitialized) {
|
||||
debugPrint('HiveService: Already initialized');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
debugPrint('HiveService: Initializing Hive CE...');
|
||||
|
||||
// Initialize Hive for Flutter
|
||||
await Hive.initFlutter();
|
||||
|
||||
// Setup encryption if enabled
|
||||
if (HiveDatabaseConfig.enableEncryption) {
|
||||
_encryptionCipher = HiveAesCipher(
|
||||
encryptionKey ?? Hive.generateSecureKey(),
|
||||
);
|
||||
debugPrint('HiveService: Encryption enabled');
|
||||
}
|
||||
|
||||
// Register all type adapters
|
||||
await _registerTypeAdapters();
|
||||
|
||||
// Open all boxes
|
||||
await _openBoxes();
|
||||
|
||||
// Check and perform migrations if needed
|
||||
await _performMigrations();
|
||||
|
||||
// Perform initial cleanup/compaction if needed
|
||||
await _performMaintenance();
|
||||
|
||||
_isInitialized = true;
|
||||
debugPrint('HiveService: Initialization complete');
|
||||
} catch (e, stackTrace) {
|
||||
debugPrint('HiveService: Initialization failed: $e');
|
||||
debugPrint('StackTrace: $stackTrace');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// Register all Hive type adapters
|
||||
///
|
||||
/// Type adapters must be registered before opening boxes.
|
||||
/// Uses auto-generated registrar from hive_registrar.g.dart
|
||||
Future<void> _registerTypeAdapters() async {
|
||||
debugPrint('HiveService: Registering type adapters...');
|
||||
|
||||
// Register all adapters using the auto-generated extension
|
||||
// This automatically registers:
|
||||
// - CachedDataAdapter (typeId: 30)
|
||||
// - All enum adapters (typeIds: 20-29)
|
||||
Hive.registerAdapters();
|
||||
|
||||
debugPrint('HiveService: ${Hive.isAdapterRegistered(HiveTypeIds.memberTier) ? "✓" : "✗"} MemberTier adapter');
|
||||
debugPrint('HiveService: ${Hive.isAdapterRegistered(HiveTypeIds.userType) ? "✓" : "✗"} UserType adapter');
|
||||
debugPrint('HiveService: ${Hive.isAdapterRegistered(HiveTypeIds.orderStatus) ? "✓" : "✗"} OrderStatus adapter');
|
||||
debugPrint('HiveService: ${Hive.isAdapterRegistered(HiveTypeIds.projectStatus) ? "✓" : "✗"} ProjectStatus adapter');
|
||||
debugPrint('HiveService: ${Hive.isAdapterRegistered(HiveTypeIds.projectType) ? "✓" : "✗"} ProjectType adapter');
|
||||
debugPrint('HiveService: ${Hive.isAdapterRegistered(HiveTypeIds.transactionType) ? "✓" : "✗"} TransactionType adapter');
|
||||
debugPrint('HiveService: ${Hive.isAdapterRegistered(HiveTypeIds.giftStatus) ? "✓" : "✗"} GiftStatus adapter');
|
||||
debugPrint('HiveService: ${Hive.isAdapterRegistered(HiveTypeIds.paymentStatus) ? "✓" : "✗"} PaymentStatus adapter');
|
||||
debugPrint('HiveService: ${Hive.isAdapterRegistered(HiveTypeIds.notificationType) ? "✓" : "✗"} NotificationType adapter');
|
||||
debugPrint('HiveService: ${Hive.isAdapterRegistered(HiveTypeIds.paymentMethod) ? "✓" : "✗"} PaymentMethod adapter');
|
||||
debugPrint('HiveService: ${Hive.isAdapterRegistered(HiveTypeIds.cachedData) ? "✓" : "✗"} CachedData adapter');
|
||||
|
||||
// TODO: Register actual model type adapters when models are created
|
||||
// These will be added to the auto-generated registrar when models are created
|
||||
// Example:
|
||||
// - UserModel (typeId: 0)
|
||||
// - ProductModel (typeId: 1)
|
||||
// - CartItemModel (typeId: 2)
|
||||
// - OrderModel (typeId: 3)
|
||||
// - ProjectModel (typeId: 4)
|
||||
// - LoyaltyTransactionModel (typeId: 5)
|
||||
// etc.
|
||||
|
||||
debugPrint('HiveService: Type adapters registered successfully');
|
||||
}
|
||||
|
||||
/// Open all Hive boxes
|
||||
///
|
||||
/// Opens boxes for immediate access. Some boxes use encryption if enabled.
|
||||
Future<void> _openBoxes() async {
|
||||
debugPrint('HiveService: Opening boxes...');
|
||||
|
||||
try {
|
||||
// Open non-encrypted boxes
|
||||
await Future.wait([
|
||||
// Settings and preferences (non-sensitive)
|
||||
Hive.openBox<dynamic>(HiveBoxNames.settingsBox),
|
||||
|
||||
// Cache boxes (non-sensitive)
|
||||
Hive.openBox<dynamic>(HiveBoxNames.cacheBox),
|
||||
Hive.openBox<dynamic>(HiveBoxNames.syncStateBox),
|
||||
|
||||
// Product and catalog data (non-sensitive)
|
||||
Hive.openBox<dynamic>(HiveBoxNames.productBox),
|
||||
Hive.openBox<dynamic>(HiveBoxNames.rewardsBox),
|
||||
|
||||
// Notification box (non-sensitive)
|
||||
Hive.openBox<dynamic>(HiveBoxNames.notificationBox),
|
||||
]);
|
||||
|
||||
// Open potentially encrypted boxes (sensitive data)
|
||||
final encryptedBoxes = [
|
||||
HiveBoxNames.userBox,
|
||||
HiveBoxNames.cartBox,
|
||||
HiveBoxNames.orderBox,
|
||||
HiveBoxNames.projectBox,
|
||||
HiveBoxNames.loyaltyBox,
|
||||
HiveBoxNames.addressBox,
|
||||
HiveBoxNames.offlineQueueBox,
|
||||
];
|
||||
|
||||
for (final boxName in encryptedBoxes) {
|
||||
await Hive.openBox<dynamic>(
|
||||
boxName,
|
||||
encryptionCipher: _encryptionCipher,
|
||||
);
|
||||
}
|
||||
|
||||
debugPrint('HiveService: All boxes opened successfully');
|
||||
} catch (e, stackTrace) {
|
||||
debugPrint('HiveService: Error opening boxes: $e');
|
||||
debugPrint('StackTrace: $stackTrace');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// Perform database migrations
|
||||
///
|
||||
/// Handles schema version upgrades and data migrations.
|
||||
Future<void> _performMigrations() async {
|
||||
final settingsBox = Hive.box<dynamic>(HiveBoxNames.settingsBox);
|
||||
final currentVersion = settingsBox.get(
|
||||
HiveKeys.schemaVersion,
|
||||
defaultValue: 0,
|
||||
) as int;
|
||||
|
||||
debugPrint('HiveService: Current schema version: $currentVersion');
|
||||
debugPrint('HiveService: Target schema version: ${HiveDatabaseConfig.currentSchemaVersion}');
|
||||
|
||||
if (currentVersion < HiveDatabaseConfig.currentSchemaVersion) {
|
||||
debugPrint('HiveService: Performing migrations...');
|
||||
|
||||
// Perform migrations sequentially
|
||||
for (int version = currentVersion + 1;
|
||||
version <= HiveDatabaseConfig.currentSchemaVersion;
|
||||
version++) {
|
||||
await _migrateToVersion(version);
|
||||
}
|
||||
|
||||
// Update schema version
|
||||
await settingsBox.put(
|
||||
HiveKeys.schemaVersion,
|
||||
HiveDatabaseConfig.currentSchemaVersion,
|
||||
);
|
||||
|
||||
debugPrint('HiveService: Migrations complete');
|
||||
} else {
|
||||
debugPrint('HiveService: No migrations needed');
|
||||
}
|
||||
}
|
||||
|
||||
/// Migrate to a specific version
|
||||
Future<void> _migrateToVersion(int version) async {
|
||||
debugPrint('HiveService: Migrating to version $version');
|
||||
|
||||
switch (version) {
|
||||
case 1:
|
||||
// Initial version - no migration needed
|
||||
break;
|
||||
|
||||
// Future migrations will be added here
|
||||
// case 2:
|
||||
// await _migrateV1ToV2();
|
||||
// break;
|
||||
|
||||
default:
|
||||
debugPrint('HiveService: Unknown migration version: $version');
|
||||
}
|
||||
}
|
||||
|
||||
/// Perform database maintenance
|
||||
///
|
||||
/// Includes compaction, cleanup of expired cache, etc.
|
||||
Future<void> _performMaintenance() async {
|
||||
debugPrint('HiveService: Performing maintenance...');
|
||||
|
||||
try {
|
||||
// Compact boxes if needed
|
||||
await _compactBoxes();
|
||||
|
||||
// Clear expired cache
|
||||
await _clearExpiredCache();
|
||||
|
||||
// Limit offline queue size
|
||||
await _limitOfflineQueue();
|
||||
|
||||
debugPrint('HiveService: Maintenance complete');
|
||||
} catch (e, stackTrace) {
|
||||
debugPrint('HiveService: Maintenance error: $e');
|
||||
debugPrint('StackTrace: $stackTrace');
|
||||
// Don't throw - maintenance errors shouldn't prevent app startup
|
||||
}
|
||||
}
|
||||
|
||||
/// Compact boxes to reduce file size
|
||||
Future<void> _compactBoxes() async {
|
||||
for (final boxName in HiveBoxNames.allBoxes) {
|
||||
try {
|
||||
if (Hive.isBoxOpen(boxName)) {
|
||||
final box = Hive.box<dynamic>(boxName);
|
||||
await box.compact();
|
||||
debugPrint('HiveService: Compacted box: $boxName');
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('HiveService: Error compacting box $boxName: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Clear expired cache entries
|
||||
Future<void> _clearExpiredCache() async {
|
||||
final cacheBox = Hive.box<dynamic>(HiveBoxNames.cacheBox);
|
||||
|
||||
// TODO: Implement cache expiration logic
|
||||
// This will be implemented when cache models are created
|
||||
|
||||
debugPrint('HiveService: Cleared expired cache entries');
|
||||
}
|
||||
|
||||
/// Limit offline queue size
|
||||
Future<void> _limitOfflineQueue() async {
|
||||
final queueBox = Hive.box<dynamic>(HiveBoxNames.offlineQueueBox);
|
||||
|
||||
if (queueBox.length > HiveDatabaseConfig.maxOfflineQueueSize) {
|
||||
final itemsToRemove = queueBox.length - HiveDatabaseConfig.maxOfflineQueueSize;
|
||||
|
||||
// Remove oldest items
|
||||
for (int i = 0; i < itemsToRemove; i++) {
|
||||
await queueBox.deleteAt(0);
|
||||
}
|
||||
|
||||
debugPrint('HiveService: Removed $itemsToRemove old items from offline queue');
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a box by name
|
||||
///
|
||||
/// Returns an already opened box. Throws if box is not open.
|
||||
Box<T> getBox<T>(String boxName) {
|
||||
if (!Hive.isBoxOpen(boxName)) {
|
||||
throw HiveError('Box $boxName is not open');
|
||||
}
|
||||
return Hive.box<T>(boxName);
|
||||
}
|
||||
|
||||
/// Check if a box is open
|
||||
bool isBoxOpen(String boxName) {
|
||||
return Hive.isBoxOpen(boxName);
|
||||
}
|
||||
|
||||
/// Clear all data from all boxes
|
||||
///
|
||||
/// WARNING: This will delete all local data. Use with caution.
|
||||
Future<void> clearAllData() async {
|
||||
debugPrint('HiveService: Clearing all data...');
|
||||
|
||||
for (final boxName in HiveBoxNames.allBoxes) {
|
||||
try {
|
||||
if (Hive.isBoxOpen(boxName)) {
|
||||
final box = Hive.box<dynamic>(boxName);
|
||||
await box.clear();
|
||||
debugPrint('HiveService: Cleared box: $boxName');
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('HiveService: Error clearing box $boxName: $e');
|
||||
}
|
||||
}
|
||||
|
||||
debugPrint('HiveService: All data cleared');
|
||||
}
|
||||
|
||||
/// Clear user-specific data (logout)
|
||||
///
|
||||
/// Clears user data while preserving app settings and cache
|
||||
Future<void> clearUserData() async {
|
||||
debugPrint('HiveService: Clearing user data...');
|
||||
|
||||
final boxesToClear = [
|
||||
HiveBoxNames.userBox,
|
||||
HiveBoxNames.cartBox,
|
||||
HiveBoxNames.orderBox,
|
||||
HiveBoxNames.projectBox,
|
||||
HiveBoxNames.loyaltyBox,
|
||||
HiveBoxNames.addressBox,
|
||||
HiveBoxNames.notificationBox,
|
||||
];
|
||||
|
||||
for (final boxName in boxesToClear) {
|
||||
try {
|
||||
if (Hive.isBoxOpen(boxName)) {
|
||||
final box = Hive.box<dynamic>(boxName);
|
||||
await box.clear();
|
||||
debugPrint('HiveService: Cleared box: $boxName');
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('HiveService: Error clearing box $boxName: $e');
|
||||
}
|
||||
}
|
||||
|
||||
debugPrint('HiveService: User data cleared');
|
||||
}
|
||||
|
||||
/// Close all boxes
|
||||
///
|
||||
/// Should be called when app is terminating
|
||||
Future<void> close() async {
|
||||
debugPrint('HiveService: Closing all boxes...');
|
||||
|
||||
try {
|
||||
await Hive.close();
|
||||
_isInitialized = false;
|
||||
debugPrint('HiveService: All boxes closed');
|
||||
} catch (e, stackTrace) {
|
||||
debugPrint('HiveService: Error closing boxes: $e');
|
||||
debugPrint('StackTrace: $stackTrace');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// Delete all Hive data from disk
|
||||
///
|
||||
/// WARNING: This completely removes the database. Use only for testing or reset.
|
||||
Future<void> deleteFromDisk() async {
|
||||
debugPrint('HiveService: Deleting database from disk...');
|
||||
|
||||
try {
|
||||
// Close all boxes first
|
||||
await close();
|
||||
|
||||
// Delete Hive directory
|
||||
final appDocDir = await getApplicationDocumentsDirectory();
|
||||
final hiveDir = Directory('${appDocDir.path}/hive');
|
||||
|
||||
if (await hiveDir.exists()) {
|
||||
await hiveDir.delete(recursive: true);
|
||||
debugPrint('HiveService: Database deleted from disk');
|
||||
}
|
||||
} catch (e, stackTrace) {
|
||||
debugPrint('HiveService: Error deleting database: $e');
|
||||
debugPrint('StackTrace: $stackTrace');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a secure encryption key
|
||||
///
|
||||
/// Returns a 256-bit encryption key for secure box encryption.
|
||||
/// Store this key securely (e.g., in flutter_secure_storage).
|
||||
static List<int> generateEncryptionKey() {
|
||||
return Hive.generateSecureKey();
|
||||
}
|
||||
}
|
||||
79
lib/core/database/models/cached_data.dart
Normal file
79
lib/core/database/models/cached_data.dart
Normal file
@@ -0,0 +1,79 @@
|
||||
import 'package:hive_ce/hive.dart';
|
||||
|
||||
import 'package:worker/core/constants/storage_constants.dart';
|
||||
|
||||
part 'cached_data.g.dart';
|
||||
|
||||
/// Cached Data Model
|
||||
///
|
||||
/// Wrapper for caching API responses with timestamp and expiration.
|
||||
/// Used for offline-first functionality and reducing API calls.
|
||||
///
|
||||
/// Example usage:
|
||||
/// ```dart
|
||||
/// final cachedProducts = CachedData(
|
||||
/// data: products,
|
||||
/// lastUpdated: DateTime.now(),
|
||||
/// );
|
||||
/// ```
|
||||
@HiveType(typeId: HiveTypeIds.cachedData)
|
||||
class CachedData extends HiveObject {
|
||||
CachedData({
|
||||
required this.data,
|
||||
required this.lastUpdated,
|
||||
this.expiresAt,
|
||||
this.source,
|
||||
});
|
||||
|
||||
/// The cached data (stored as dynamic)
|
||||
@HiveField(0)
|
||||
final dynamic data;
|
||||
|
||||
/// When the data was last updated
|
||||
@HiveField(1)
|
||||
final DateTime lastUpdated;
|
||||
|
||||
/// Optional expiration time
|
||||
@HiveField(2)
|
||||
final DateTime? expiresAt;
|
||||
|
||||
/// Source of the data (e.g., 'api', 'local')
|
||||
@HiveField(3)
|
||||
final String? source;
|
||||
|
||||
/// Check if cache is expired
|
||||
bool get isExpired {
|
||||
if (expiresAt == null) return false;
|
||||
return DateTime.now().isAfter(expiresAt!);
|
||||
}
|
||||
|
||||
/// Check if cache is fresh (not expired and within max age)
|
||||
bool isFresh(Duration maxAge) {
|
||||
if (isExpired) return false;
|
||||
final age = DateTime.now().difference(lastUpdated);
|
||||
return age <= maxAge;
|
||||
}
|
||||
|
||||
/// Get age of cached data
|
||||
Duration get age => DateTime.now().difference(lastUpdated);
|
||||
|
||||
/// Create a copy with updated data
|
||||
CachedData copyWith({
|
||||
dynamic data,
|
||||
DateTime? lastUpdated,
|
||||
DateTime? expiresAt,
|
||||
String? source,
|
||||
}) {
|
||||
return CachedData(
|
||||
data: data ?? this.data,
|
||||
lastUpdated: lastUpdated ?? this.lastUpdated,
|
||||
expiresAt: expiresAt ?? this.expiresAt,
|
||||
source: source ?? this.source,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'CachedData(lastUpdated: $lastUpdated, expiresAt: $expiresAt, source: $source, isExpired: $isExpired)';
|
||||
}
|
||||
}
|
||||
50
lib/core/database/models/cached_data.g.dart
Normal file
50
lib/core/database/models/cached_data.g.dart
Normal file
@@ -0,0 +1,50 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'cached_data.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// TypeAdapterGenerator
|
||||
// **************************************************************************
|
||||
|
||||
class CachedDataAdapter extends TypeAdapter<CachedData> {
|
||||
@override
|
||||
final typeId = 30;
|
||||
|
||||
@override
|
||||
CachedData read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return CachedData(
|
||||
data: fields[0] as dynamic,
|
||||
lastUpdated: fields[1] as DateTime,
|
||||
expiresAt: fields[2] as DateTime?,
|
||||
source: fields[3] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, CachedData obj) {
|
||||
writer
|
||||
..writeByte(4)
|
||||
..writeByte(0)
|
||||
..write(obj.data)
|
||||
..writeByte(1)
|
||||
..write(obj.lastUpdated)
|
||||
..writeByte(2)
|
||||
..write(obj.expiresAt)
|
||||
..writeByte(3)
|
||||
..write(obj.source);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is CachedDataAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
425
lib/core/database/models/enums.dart
Normal file
425
lib/core/database/models/enums.dart
Normal file
@@ -0,0 +1,425 @@
|
||||
import 'package:hive_ce/hive.dart';
|
||||
|
||||
import 'package:worker/core/constants/storage_constants.dart';
|
||||
|
||||
part 'enums.g.dart';
|
||||
|
||||
/// Member Tier Levels
|
||||
///
|
||||
/// Represents the loyalty program membership tiers.
|
||||
/// Higher tiers receive more benefits and rewards.
|
||||
@HiveType(typeId: HiveTypeIds.memberTier)
|
||||
enum MemberTier {
|
||||
/// Gold tier - Entry level membership
|
||||
@HiveField(0)
|
||||
gold,
|
||||
|
||||
/// Platinum tier - Mid-level membership
|
||||
@HiveField(1)
|
||||
platinum,
|
||||
|
||||
/// Diamond tier - Premium membership
|
||||
@HiveField(2)
|
||||
diamond,
|
||||
}
|
||||
|
||||
/// User Type Categories
|
||||
///
|
||||
/// Represents the different types of users in the app.
|
||||
@HiveType(typeId: HiveTypeIds.userType)
|
||||
enum UserType {
|
||||
/// Construction contractor
|
||||
@HiveField(0)
|
||||
contractor,
|
||||
|
||||
/// Architect or designer
|
||||
@HiveField(1)
|
||||
architect,
|
||||
|
||||
/// Product distributor
|
||||
@HiveField(2)
|
||||
distributor,
|
||||
|
||||
/// Real estate broker
|
||||
@HiveField(3)
|
||||
broker,
|
||||
}
|
||||
|
||||
/// Order Status
|
||||
///
|
||||
/// Represents the current state of an order.
|
||||
@HiveType(typeId: HiveTypeIds.orderStatus)
|
||||
enum OrderStatus {
|
||||
/// Order placed, awaiting confirmation
|
||||
@HiveField(0)
|
||||
pending,
|
||||
|
||||
/// Order confirmed and being processed
|
||||
@HiveField(1)
|
||||
processing,
|
||||
|
||||
/// Order is being shipped/delivered
|
||||
@HiveField(2)
|
||||
shipping,
|
||||
|
||||
/// Order completed successfully
|
||||
@HiveField(3)
|
||||
completed,
|
||||
|
||||
/// Order cancelled
|
||||
@HiveField(4)
|
||||
cancelled,
|
||||
|
||||
/// Order refunded
|
||||
@HiveField(5)
|
||||
refunded,
|
||||
}
|
||||
|
||||
/// Project Status
|
||||
///
|
||||
/// Represents the current state of a construction project.
|
||||
@HiveType(typeId: HiveTypeIds.projectStatus)
|
||||
enum ProjectStatus {
|
||||
/// Project in planning phase
|
||||
@HiveField(0)
|
||||
planning,
|
||||
|
||||
/// Project actively in progress
|
||||
@HiveField(1)
|
||||
inProgress,
|
||||
|
||||
/// Project on hold
|
||||
@HiveField(2)
|
||||
onHold,
|
||||
|
||||
/// Project completed
|
||||
@HiveField(3)
|
||||
completed,
|
||||
|
||||
/// Project cancelled
|
||||
@HiveField(4)
|
||||
cancelled,
|
||||
}
|
||||
|
||||
/// Project Type
|
||||
///
|
||||
/// Represents the category of construction project.
|
||||
@HiveType(typeId: HiveTypeIds.projectType)
|
||||
enum ProjectType {
|
||||
/// Residential building project
|
||||
@HiveField(0)
|
||||
residential,
|
||||
|
||||
/// Commercial building project
|
||||
@HiveField(1)
|
||||
commercial,
|
||||
|
||||
/// Industrial facility project
|
||||
@HiveField(2)
|
||||
industrial,
|
||||
|
||||
/// Infrastructure project
|
||||
@HiveField(3)
|
||||
infrastructure,
|
||||
|
||||
/// Renovation project
|
||||
@HiveField(4)
|
||||
renovation,
|
||||
}
|
||||
|
||||
/// Loyalty Transaction Type
|
||||
///
|
||||
/// Represents the type of loyalty points transaction.
|
||||
@HiveType(typeId: HiveTypeIds.transactionType)
|
||||
enum TransactionType {
|
||||
/// Points earned from purchase
|
||||
@HiveField(0)
|
||||
earnedPurchase,
|
||||
|
||||
/// Points earned from referral
|
||||
@HiveField(1)
|
||||
earnedReferral,
|
||||
|
||||
/// Points earned from promotion
|
||||
@HiveField(2)
|
||||
earnedPromotion,
|
||||
|
||||
/// Bonus points from admin
|
||||
@HiveField(3)
|
||||
earnedBonus,
|
||||
|
||||
/// Points redeemed for reward
|
||||
@HiveField(4)
|
||||
redeemedReward,
|
||||
|
||||
/// Points redeemed for discount
|
||||
@HiveField(5)
|
||||
redeemedDiscount,
|
||||
|
||||
/// Points adjusted by admin
|
||||
@HiveField(6)
|
||||
adjustment,
|
||||
|
||||
/// Points expired
|
||||
@HiveField(7)
|
||||
expired,
|
||||
}
|
||||
|
||||
/// Gift Status
|
||||
///
|
||||
/// Represents the status of a redeemed gift/reward.
|
||||
@HiveType(typeId: HiveTypeIds.giftStatus)
|
||||
enum GiftStatus {
|
||||
/// Gift is active and can be used
|
||||
@HiveField(0)
|
||||
active,
|
||||
|
||||
/// Gift has been used
|
||||
@HiveField(1)
|
||||
used,
|
||||
|
||||
/// Gift has expired
|
||||
@HiveField(2)
|
||||
expired,
|
||||
|
||||
/// Gift is reserved but not activated
|
||||
@HiveField(3)
|
||||
reserved,
|
||||
|
||||
/// Gift has been cancelled
|
||||
@HiveField(4)
|
||||
cancelled,
|
||||
}
|
||||
|
||||
/// Payment Status
|
||||
///
|
||||
/// Represents the status of a payment transaction.
|
||||
@HiveType(typeId: HiveTypeIds.paymentStatus)
|
||||
enum PaymentStatus {
|
||||
/// Payment pending
|
||||
@HiveField(0)
|
||||
pending,
|
||||
|
||||
/// Payment being processed
|
||||
@HiveField(1)
|
||||
processing,
|
||||
|
||||
/// Payment completed successfully
|
||||
@HiveField(2)
|
||||
completed,
|
||||
|
||||
/// Payment failed
|
||||
@HiveField(3)
|
||||
failed,
|
||||
|
||||
/// Payment refunded
|
||||
@HiveField(4)
|
||||
refunded,
|
||||
|
||||
/// Payment cancelled
|
||||
@HiveField(5)
|
||||
cancelled,
|
||||
}
|
||||
|
||||
/// Notification Type
|
||||
///
|
||||
/// Represents different categories of notifications.
|
||||
@HiveType(typeId: HiveTypeIds.notificationType)
|
||||
enum NotificationType {
|
||||
/// Order-related notification
|
||||
@HiveField(0)
|
||||
order,
|
||||
|
||||
/// Promotion or offer notification
|
||||
@HiveField(1)
|
||||
promotion,
|
||||
|
||||
/// System announcement
|
||||
@HiveField(2)
|
||||
system,
|
||||
|
||||
/// Loyalty program notification
|
||||
@HiveField(3)
|
||||
loyalty,
|
||||
|
||||
/// Project-related notification
|
||||
@HiveField(4)
|
||||
project,
|
||||
|
||||
/// Payment notification
|
||||
@HiveField(5)
|
||||
payment,
|
||||
|
||||
/// General message
|
||||
@HiveField(6)
|
||||
message,
|
||||
}
|
||||
|
||||
/// Payment Method
|
||||
///
|
||||
/// Represents available payment methods.
|
||||
@HiveType(typeId: HiveTypeIds.paymentMethod)
|
||||
enum PaymentMethod {
|
||||
/// Cash on delivery
|
||||
@HiveField(0)
|
||||
cashOnDelivery,
|
||||
|
||||
/// Bank transfer
|
||||
@HiveField(1)
|
||||
bankTransfer,
|
||||
|
||||
/// Credit/Debit card
|
||||
@HiveField(2)
|
||||
card,
|
||||
|
||||
/// E-wallet (Momo, ZaloPay, etc.)
|
||||
@HiveField(3)
|
||||
eWallet,
|
||||
|
||||
/// QR code payment
|
||||
@HiveField(4)
|
||||
qrCode,
|
||||
|
||||
/// Pay later / Credit
|
||||
@HiveField(5)
|
||||
payLater,
|
||||
}
|
||||
|
||||
/// Extension methods for enums
|
||||
|
||||
extension MemberTierExtension on MemberTier {
|
||||
/// Get display name
|
||||
String get displayName {
|
||||
switch (this) {
|
||||
case MemberTier.gold:
|
||||
return 'Gold';
|
||||
case MemberTier.platinum:
|
||||
return 'Platinum';
|
||||
case MemberTier.diamond:
|
||||
return 'Diamond';
|
||||
}
|
||||
}
|
||||
|
||||
/// Get tier level (higher is better)
|
||||
int get level {
|
||||
switch (this) {
|
||||
case MemberTier.gold:
|
||||
return 1;
|
||||
case MemberTier.platinum:
|
||||
return 2;
|
||||
case MemberTier.diamond:
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension UserTypeExtension on UserType {
|
||||
/// Get display name (Vietnamese)
|
||||
String get displayName {
|
||||
switch (this) {
|
||||
case UserType.contractor:
|
||||
return 'Thầu thợ';
|
||||
case UserType.architect:
|
||||
return 'Kiến trúc sư';
|
||||
case UserType.distributor:
|
||||
return 'Đại lý phân phối';
|
||||
case UserType.broker:
|
||||
return 'Môi giới';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension OrderStatusExtension on OrderStatus {
|
||||
/// Get display name (Vietnamese)
|
||||
String get displayName {
|
||||
switch (this) {
|
||||
case OrderStatus.pending:
|
||||
return 'Chờ xác nhận';
|
||||
case OrderStatus.processing:
|
||||
return 'Đang xử lý';
|
||||
case OrderStatus.shipping:
|
||||
return 'Đang giao hàng';
|
||||
case OrderStatus.completed:
|
||||
return 'Hoàn thành';
|
||||
case OrderStatus.cancelled:
|
||||
return 'Đã hủy';
|
||||
case OrderStatus.refunded:
|
||||
return 'Đã hoàn tiền';
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if order is active
|
||||
bool get isActive {
|
||||
return this == OrderStatus.pending ||
|
||||
this == OrderStatus.processing ||
|
||||
this == OrderStatus.shipping;
|
||||
}
|
||||
|
||||
/// Check if order is final
|
||||
bool get isFinal {
|
||||
return this == OrderStatus.completed ||
|
||||
this == OrderStatus.cancelled ||
|
||||
this == OrderStatus.refunded;
|
||||
}
|
||||
}
|
||||
|
||||
extension ProjectStatusExtension on ProjectStatus {
|
||||
/// Get display name (Vietnamese)
|
||||
String get displayName {
|
||||
switch (this) {
|
||||
case ProjectStatus.planning:
|
||||
return 'Lập kế hoạch';
|
||||
case ProjectStatus.inProgress:
|
||||
return 'Đang thực hiện';
|
||||
case ProjectStatus.onHold:
|
||||
return 'Tạm dừng';
|
||||
case ProjectStatus.completed:
|
||||
return 'Hoàn thành';
|
||||
case ProjectStatus.cancelled:
|
||||
return 'Đã hủy';
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if project is active
|
||||
bool get isActive {
|
||||
return this == ProjectStatus.planning || this == ProjectStatus.inProgress;
|
||||
}
|
||||
}
|
||||
|
||||
extension TransactionTypeExtension on TransactionType {
|
||||
/// Check if transaction is earning points
|
||||
bool get isEarning {
|
||||
return this == TransactionType.earnedPurchase ||
|
||||
this == TransactionType.earnedReferral ||
|
||||
this == TransactionType.earnedPromotion ||
|
||||
this == TransactionType.earnedBonus;
|
||||
}
|
||||
|
||||
/// Check if transaction is spending points
|
||||
bool get isSpending {
|
||||
return this == TransactionType.redeemedReward ||
|
||||
this == TransactionType.redeemedDiscount;
|
||||
}
|
||||
|
||||
/// Get display name (Vietnamese)
|
||||
String get displayName {
|
||||
switch (this) {
|
||||
case TransactionType.earnedPurchase:
|
||||
return 'Mua hàng';
|
||||
case TransactionType.earnedReferral:
|
||||
return 'Giới thiệu bạn bè';
|
||||
case TransactionType.earnedPromotion:
|
||||
return 'Khuyến mãi';
|
||||
case TransactionType.earnedBonus:
|
||||
return 'Thưởng';
|
||||
case TransactionType.redeemedReward:
|
||||
return 'Đổi quà';
|
||||
case TransactionType.redeemedDiscount:
|
||||
return 'Đổi giảm giá';
|
||||
case TransactionType.adjustment:
|
||||
return 'Điều chỉnh';
|
||||
case TransactionType.expired:
|
||||
return 'Hết hạn';
|
||||
}
|
||||
}
|
||||
}
|
||||
517
lib/core/database/models/enums.g.dart
Normal file
517
lib/core/database/models/enums.g.dart
Normal file
@@ -0,0 +1,517 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'enums.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// TypeAdapterGenerator
|
||||
// **************************************************************************
|
||||
|
||||
class MemberTierAdapter extends TypeAdapter<MemberTier> {
|
||||
@override
|
||||
final typeId = 20;
|
||||
|
||||
@override
|
||||
MemberTier read(BinaryReader reader) {
|
||||
switch (reader.readByte()) {
|
||||
case 0:
|
||||
return MemberTier.gold;
|
||||
case 1:
|
||||
return MemberTier.platinum;
|
||||
case 2:
|
||||
return MemberTier.diamond;
|
||||
default:
|
||||
return MemberTier.gold;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, MemberTier obj) {
|
||||
switch (obj) {
|
||||
case MemberTier.gold:
|
||||
writer.writeByte(0);
|
||||
case MemberTier.platinum:
|
||||
writer.writeByte(1);
|
||||
case MemberTier.diamond:
|
||||
writer.writeByte(2);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is MemberTierAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
|
||||
class UserTypeAdapter extends TypeAdapter<UserType> {
|
||||
@override
|
||||
final typeId = 21;
|
||||
|
||||
@override
|
||||
UserType read(BinaryReader reader) {
|
||||
switch (reader.readByte()) {
|
||||
case 0:
|
||||
return UserType.contractor;
|
||||
case 1:
|
||||
return UserType.architect;
|
||||
case 2:
|
||||
return UserType.distributor;
|
||||
case 3:
|
||||
return UserType.broker;
|
||||
default:
|
||||
return UserType.contractor;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, UserType obj) {
|
||||
switch (obj) {
|
||||
case UserType.contractor:
|
||||
writer.writeByte(0);
|
||||
case UserType.architect:
|
||||
writer.writeByte(1);
|
||||
case UserType.distributor:
|
||||
writer.writeByte(2);
|
||||
case UserType.broker:
|
||||
writer.writeByte(3);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is UserTypeAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
|
||||
class OrderStatusAdapter extends TypeAdapter<OrderStatus> {
|
||||
@override
|
||||
final typeId = 22;
|
||||
|
||||
@override
|
||||
OrderStatus read(BinaryReader reader) {
|
||||
switch (reader.readByte()) {
|
||||
case 0:
|
||||
return OrderStatus.pending;
|
||||
case 1:
|
||||
return OrderStatus.processing;
|
||||
case 2:
|
||||
return OrderStatus.shipping;
|
||||
case 3:
|
||||
return OrderStatus.completed;
|
||||
case 4:
|
||||
return OrderStatus.cancelled;
|
||||
case 5:
|
||||
return OrderStatus.refunded;
|
||||
default:
|
||||
return OrderStatus.pending;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, OrderStatus obj) {
|
||||
switch (obj) {
|
||||
case OrderStatus.pending:
|
||||
writer.writeByte(0);
|
||||
case OrderStatus.processing:
|
||||
writer.writeByte(1);
|
||||
case OrderStatus.shipping:
|
||||
writer.writeByte(2);
|
||||
case OrderStatus.completed:
|
||||
writer.writeByte(3);
|
||||
case OrderStatus.cancelled:
|
||||
writer.writeByte(4);
|
||||
case OrderStatus.refunded:
|
||||
writer.writeByte(5);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is OrderStatusAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
|
||||
class ProjectStatusAdapter extends TypeAdapter<ProjectStatus> {
|
||||
@override
|
||||
final typeId = 23;
|
||||
|
||||
@override
|
||||
ProjectStatus read(BinaryReader reader) {
|
||||
switch (reader.readByte()) {
|
||||
case 0:
|
||||
return ProjectStatus.planning;
|
||||
case 1:
|
||||
return ProjectStatus.inProgress;
|
||||
case 2:
|
||||
return ProjectStatus.onHold;
|
||||
case 3:
|
||||
return ProjectStatus.completed;
|
||||
case 4:
|
||||
return ProjectStatus.cancelled;
|
||||
default:
|
||||
return ProjectStatus.planning;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, ProjectStatus obj) {
|
||||
switch (obj) {
|
||||
case ProjectStatus.planning:
|
||||
writer.writeByte(0);
|
||||
case ProjectStatus.inProgress:
|
||||
writer.writeByte(1);
|
||||
case ProjectStatus.onHold:
|
||||
writer.writeByte(2);
|
||||
case ProjectStatus.completed:
|
||||
writer.writeByte(3);
|
||||
case ProjectStatus.cancelled:
|
||||
writer.writeByte(4);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is ProjectStatusAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
|
||||
class ProjectTypeAdapter extends TypeAdapter<ProjectType> {
|
||||
@override
|
||||
final typeId = 24;
|
||||
|
||||
@override
|
||||
ProjectType read(BinaryReader reader) {
|
||||
switch (reader.readByte()) {
|
||||
case 0:
|
||||
return ProjectType.residential;
|
||||
case 1:
|
||||
return ProjectType.commercial;
|
||||
case 2:
|
||||
return ProjectType.industrial;
|
||||
case 3:
|
||||
return ProjectType.infrastructure;
|
||||
case 4:
|
||||
return ProjectType.renovation;
|
||||
default:
|
||||
return ProjectType.residential;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, ProjectType obj) {
|
||||
switch (obj) {
|
||||
case ProjectType.residential:
|
||||
writer.writeByte(0);
|
||||
case ProjectType.commercial:
|
||||
writer.writeByte(1);
|
||||
case ProjectType.industrial:
|
||||
writer.writeByte(2);
|
||||
case ProjectType.infrastructure:
|
||||
writer.writeByte(3);
|
||||
case ProjectType.renovation:
|
||||
writer.writeByte(4);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is ProjectTypeAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
|
||||
class TransactionTypeAdapter extends TypeAdapter<TransactionType> {
|
||||
@override
|
||||
final typeId = 25;
|
||||
|
||||
@override
|
||||
TransactionType read(BinaryReader reader) {
|
||||
switch (reader.readByte()) {
|
||||
case 0:
|
||||
return TransactionType.earnedPurchase;
|
||||
case 1:
|
||||
return TransactionType.earnedReferral;
|
||||
case 2:
|
||||
return TransactionType.earnedPromotion;
|
||||
case 3:
|
||||
return TransactionType.earnedBonus;
|
||||
case 4:
|
||||
return TransactionType.redeemedReward;
|
||||
case 5:
|
||||
return TransactionType.redeemedDiscount;
|
||||
case 6:
|
||||
return TransactionType.adjustment;
|
||||
case 7:
|
||||
return TransactionType.expired;
|
||||
default:
|
||||
return TransactionType.earnedPurchase;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, TransactionType obj) {
|
||||
switch (obj) {
|
||||
case TransactionType.earnedPurchase:
|
||||
writer.writeByte(0);
|
||||
case TransactionType.earnedReferral:
|
||||
writer.writeByte(1);
|
||||
case TransactionType.earnedPromotion:
|
||||
writer.writeByte(2);
|
||||
case TransactionType.earnedBonus:
|
||||
writer.writeByte(3);
|
||||
case TransactionType.redeemedReward:
|
||||
writer.writeByte(4);
|
||||
case TransactionType.redeemedDiscount:
|
||||
writer.writeByte(5);
|
||||
case TransactionType.adjustment:
|
||||
writer.writeByte(6);
|
||||
case TransactionType.expired:
|
||||
writer.writeByte(7);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is TransactionTypeAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
|
||||
class GiftStatusAdapter extends TypeAdapter<GiftStatus> {
|
||||
@override
|
||||
final typeId = 26;
|
||||
|
||||
@override
|
||||
GiftStatus read(BinaryReader reader) {
|
||||
switch (reader.readByte()) {
|
||||
case 0:
|
||||
return GiftStatus.active;
|
||||
case 1:
|
||||
return GiftStatus.used;
|
||||
case 2:
|
||||
return GiftStatus.expired;
|
||||
case 3:
|
||||
return GiftStatus.reserved;
|
||||
case 4:
|
||||
return GiftStatus.cancelled;
|
||||
default:
|
||||
return GiftStatus.active;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, GiftStatus obj) {
|
||||
switch (obj) {
|
||||
case GiftStatus.active:
|
||||
writer.writeByte(0);
|
||||
case GiftStatus.used:
|
||||
writer.writeByte(1);
|
||||
case GiftStatus.expired:
|
||||
writer.writeByte(2);
|
||||
case GiftStatus.reserved:
|
||||
writer.writeByte(3);
|
||||
case GiftStatus.cancelled:
|
||||
writer.writeByte(4);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is GiftStatusAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
|
||||
class PaymentStatusAdapter extends TypeAdapter<PaymentStatus> {
|
||||
@override
|
||||
final typeId = 27;
|
||||
|
||||
@override
|
||||
PaymentStatus read(BinaryReader reader) {
|
||||
switch (reader.readByte()) {
|
||||
case 0:
|
||||
return PaymentStatus.pending;
|
||||
case 1:
|
||||
return PaymentStatus.processing;
|
||||
case 2:
|
||||
return PaymentStatus.completed;
|
||||
case 3:
|
||||
return PaymentStatus.failed;
|
||||
case 4:
|
||||
return PaymentStatus.refunded;
|
||||
case 5:
|
||||
return PaymentStatus.cancelled;
|
||||
default:
|
||||
return PaymentStatus.pending;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, PaymentStatus obj) {
|
||||
switch (obj) {
|
||||
case PaymentStatus.pending:
|
||||
writer.writeByte(0);
|
||||
case PaymentStatus.processing:
|
||||
writer.writeByte(1);
|
||||
case PaymentStatus.completed:
|
||||
writer.writeByte(2);
|
||||
case PaymentStatus.failed:
|
||||
writer.writeByte(3);
|
||||
case PaymentStatus.refunded:
|
||||
writer.writeByte(4);
|
||||
case PaymentStatus.cancelled:
|
||||
writer.writeByte(5);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is PaymentStatusAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
|
||||
class NotificationTypeAdapter extends TypeAdapter<NotificationType> {
|
||||
@override
|
||||
final typeId = 28;
|
||||
|
||||
@override
|
||||
NotificationType read(BinaryReader reader) {
|
||||
switch (reader.readByte()) {
|
||||
case 0:
|
||||
return NotificationType.order;
|
||||
case 1:
|
||||
return NotificationType.promotion;
|
||||
case 2:
|
||||
return NotificationType.system;
|
||||
case 3:
|
||||
return NotificationType.loyalty;
|
||||
case 4:
|
||||
return NotificationType.project;
|
||||
case 5:
|
||||
return NotificationType.payment;
|
||||
case 6:
|
||||
return NotificationType.message;
|
||||
default:
|
||||
return NotificationType.order;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, NotificationType obj) {
|
||||
switch (obj) {
|
||||
case NotificationType.order:
|
||||
writer.writeByte(0);
|
||||
case NotificationType.promotion:
|
||||
writer.writeByte(1);
|
||||
case NotificationType.system:
|
||||
writer.writeByte(2);
|
||||
case NotificationType.loyalty:
|
||||
writer.writeByte(3);
|
||||
case NotificationType.project:
|
||||
writer.writeByte(4);
|
||||
case NotificationType.payment:
|
||||
writer.writeByte(5);
|
||||
case NotificationType.message:
|
||||
writer.writeByte(6);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is NotificationTypeAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
|
||||
class PaymentMethodAdapter extends TypeAdapter<PaymentMethod> {
|
||||
@override
|
||||
final typeId = 29;
|
||||
|
||||
@override
|
||||
PaymentMethod read(BinaryReader reader) {
|
||||
switch (reader.readByte()) {
|
||||
case 0:
|
||||
return PaymentMethod.cashOnDelivery;
|
||||
case 1:
|
||||
return PaymentMethod.bankTransfer;
|
||||
case 2:
|
||||
return PaymentMethod.card;
|
||||
case 3:
|
||||
return PaymentMethod.eWallet;
|
||||
case 4:
|
||||
return PaymentMethod.qrCode;
|
||||
case 5:
|
||||
return PaymentMethod.payLater;
|
||||
default:
|
||||
return PaymentMethod.cashOnDelivery;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, PaymentMethod obj) {
|
||||
switch (obj) {
|
||||
case PaymentMethod.cashOnDelivery:
|
||||
writer.writeByte(0);
|
||||
case PaymentMethod.bankTransfer:
|
||||
writer.writeByte(1);
|
||||
case PaymentMethod.card:
|
||||
writer.writeByte(2);
|
||||
case PaymentMethod.eWallet:
|
||||
writer.writeByte(3);
|
||||
case PaymentMethod.qrCode:
|
||||
writer.writeByte(4);
|
||||
case PaymentMethod.payLater:
|
||||
writer.writeByte(5);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is PaymentMethodAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
Reference in New Issue
Block a user