runable
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:hive_ce/hive.dart';
|
||||
|
||||
import '../../../../core/network/api_client.dart';
|
||||
import '../../data/datasources/scanner_local_datasource.dart';
|
||||
import '../../data/datasources/scanner_remote_datasource.dart';
|
||||
import '../../data/models/scan_item.dart';
|
||||
import '../../data/repositories/scanner_repository_impl.dart';
|
||||
import '../../domain/repositories/scanner_repository.dart';
|
||||
import '../../domain/usecases/get_scan_history_usecase.dart';
|
||||
import '../../domain/usecases/save_scan_usecase.dart';
|
||||
|
||||
/// Network layer providers
|
||||
final dioProvider = Provider<Dio>((ref) {
|
||||
final dio = Dio();
|
||||
dio.options.baseUrl = 'https://api.example.com'; // Replace with actual API URL
|
||||
dio.options.connectTimeout = const Duration(seconds: 30);
|
||||
dio.options.receiveTimeout = const Duration(seconds: 30);
|
||||
dio.options.headers['Content-Type'] = 'application/json';
|
||||
dio.options.headers['Accept'] = 'application/json';
|
||||
|
||||
// Add interceptors for logging, authentication, etc.
|
||||
dio.interceptors.add(
|
||||
LogInterceptor(
|
||||
requestBody: true,
|
||||
responseBody: true,
|
||||
logPrint: (obj) {
|
||||
// Log to console in debug mode using debugPrint
|
||||
// This will only log in debug mode
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
return dio;
|
||||
});
|
||||
|
||||
final apiClientProvider = Provider<ApiClient>((ref) {
|
||||
return ApiClient();
|
||||
});
|
||||
|
||||
/// Local storage providers
|
||||
final hiveBoxProvider = Provider<Box<ScanItem>>((ref) {
|
||||
return Hive.box<ScanItem>('scans');
|
||||
});
|
||||
|
||||
/// Settings box provider
|
||||
final settingsBoxProvider = Provider<Box>((ref) {
|
||||
return Hive.box('settings');
|
||||
});
|
||||
|
||||
/// Data source providers
|
||||
final scannerRemoteDataSourceProvider = Provider<ScannerRemoteDataSource>((ref) {
|
||||
return ScannerRemoteDataSourceImpl(apiClient: ref.watch(apiClientProvider));
|
||||
});
|
||||
|
||||
final scannerLocalDataSourceProvider = Provider<ScannerLocalDataSource>((ref) {
|
||||
return ScannerLocalDataSourceImpl();
|
||||
});
|
||||
|
||||
/// Repository providers
|
||||
final scannerRepositoryProvider = Provider<ScannerRepository>((ref) {
|
||||
return ScannerRepositoryImpl(
|
||||
remoteDataSource: ref.watch(scannerRemoteDataSourceProvider),
|
||||
localDataSource: ref.watch(scannerLocalDataSourceProvider),
|
||||
);
|
||||
});
|
||||
|
||||
/// Use case providers
|
||||
final saveScanUseCaseProvider = Provider<SaveScanUseCase>((ref) {
|
||||
return SaveScanUseCase(ref.watch(scannerRepositoryProvider));
|
||||
});
|
||||
|
||||
final getScanHistoryUseCaseProvider = Provider<GetScanHistoryUseCase>((ref) {
|
||||
return GetScanHistoryUseCase(ref.watch(scannerRepositoryProvider));
|
||||
});
|
||||
|
||||
/// Additional utility providers
|
||||
final currentTimestampProvider = Provider<DateTime>((ref) {
|
||||
return DateTime.now();
|
||||
});
|
||||
|
||||
/// Provider for checking network connectivity
|
||||
final networkStatusProvider = Provider<bool>((ref) {
|
||||
// This would typically use connectivity_plus package
|
||||
// For now, returning true as a placeholder
|
||||
return true;
|
||||
});
|
||||
|
||||
/// Provider for app configuration
|
||||
final appConfigProvider = Provider<Map<String, dynamic>>((ref) {
|
||||
return {
|
||||
'apiBaseUrl': 'https://api.example.com',
|
||||
'apiTimeout': 30000,
|
||||
'maxHistoryItems': 100,
|
||||
'enableLogging': !const bool.fromEnvironment('dart.vm.product'),
|
||||
};
|
||||
});
|
||||
|
||||
/// Provider for error handling configuration
|
||||
final errorHandlingConfigProvider = Provider<Map<String, String>>((ref) {
|
||||
return {
|
||||
'networkError': 'Network connection failed. Please check your internet connection.',
|
||||
'serverError': 'Server error occurred. Please try again later.',
|
||||
'validationError': 'Please check your input and try again.',
|
||||
'unknownError': 'An unexpected error occurred. Please try again.',
|
||||
};
|
||||
});
|
||||
|
||||
/// Provider for checking if required dependencies are initialized
|
||||
final dependenciesInitializedProvider = Provider<bool>((ref) {
|
||||
try {
|
||||
// Check if all critical dependencies are available
|
||||
ref.read(scannerRepositoryProvider);
|
||||
ref.read(saveScanUseCaseProvider);
|
||||
ref.read(getScanHistoryUseCaseProvider);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
/// Helper provider for getting localized error messages
|
||||
final errorMessageProvider = Provider.family<String, String>((ref, errorKey) {
|
||||
final config = ref.watch(errorHandlingConfigProvider);
|
||||
return config[errorKey] ?? config['unknownError']!;
|
||||
});
|
||||
253
lib/features/scanner/presentation/providers/form_provider.dart
Normal file
253
lib/features/scanner/presentation/providers/form_provider.dart
Normal file
@@ -0,0 +1,253 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../../data/models/scan_item.dart';
|
||||
import '../../domain/usecases/save_scan_usecase.dart';
|
||||
import 'dependency_injection.dart';
|
||||
import 'scanner_provider.dart';
|
||||
|
||||
/// State for the form functionality
|
||||
class FormDetailState {
|
||||
final String barcode;
|
||||
final String field1;
|
||||
final String field2;
|
||||
final String field3;
|
||||
final String field4;
|
||||
final bool isLoading;
|
||||
final bool isSaveSuccess;
|
||||
final String? error;
|
||||
|
||||
const FormDetailState({
|
||||
required this.barcode,
|
||||
this.field1 = '',
|
||||
this.field2 = '',
|
||||
this.field3 = '',
|
||||
this.field4 = '',
|
||||
this.isLoading = false,
|
||||
this.isSaveSuccess = false,
|
||||
this.error,
|
||||
});
|
||||
|
||||
FormDetailState copyWith({
|
||||
String? barcode,
|
||||
String? field1,
|
||||
String? field2,
|
||||
String? field3,
|
||||
String? field4,
|
||||
bool? isLoading,
|
||||
bool? isSaveSuccess,
|
||||
String? error,
|
||||
}) {
|
||||
return FormDetailState(
|
||||
barcode: barcode ?? this.barcode,
|
||||
field1: field1 ?? this.field1,
|
||||
field2: field2 ?? this.field2,
|
||||
field3: field3 ?? this.field3,
|
||||
field4: field4 ?? this.field4,
|
||||
isLoading: isLoading ?? this.isLoading,
|
||||
isSaveSuccess: isSaveSuccess ?? this.isSaveSuccess,
|
||||
error: error,
|
||||
);
|
||||
}
|
||||
|
||||
/// Check if all required fields are filled
|
||||
bool get isValid {
|
||||
return barcode.trim().isNotEmpty &&
|
||||
field1.trim().isNotEmpty &&
|
||||
field2.trim().isNotEmpty &&
|
||||
field3.trim().isNotEmpty &&
|
||||
field4.trim().isNotEmpty;
|
||||
}
|
||||
|
||||
/// Get validation error messages
|
||||
List<String> get validationErrors {
|
||||
final errors = <String>[];
|
||||
|
||||
if (barcode.trim().isEmpty) {
|
||||
errors.add('Barcode is required');
|
||||
}
|
||||
if (field1.trim().isEmpty) {
|
||||
errors.add('Field 1 is required');
|
||||
}
|
||||
if (field2.trim().isEmpty) {
|
||||
errors.add('Field 2 is required');
|
||||
}
|
||||
if (field3.trim().isEmpty) {
|
||||
errors.add('Field 3 is required');
|
||||
}
|
||||
if (field4.trim().isEmpty) {
|
||||
errors.add('Field 4 is required');
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is FormDetailState &&
|
||||
runtimeType == other.runtimeType &&
|
||||
barcode == other.barcode &&
|
||||
field1 == other.field1 &&
|
||||
field2 == other.field2 &&
|
||||
field3 == other.field3 &&
|
||||
field4 == other.field4 &&
|
||||
isLoading == other.isLoading &&
|
||||
isSaveSuccess == other.isSaveSuccess &&
|
||||
error == other.error;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
barcode.hashCode ^
|
||||
field1.hashCode ^
|
||||
field2.hashCode ^
|
||||
field3.hashCode ^
|
||||
field4.hashCode ^
|
||||
isLoading.hashCode ^
|
||||
isSaveSuccess.hashCode ^
|
||||
error.hashCode;
|
||||
}
|
||||
|
||||
/// Form state notifier
|
||||
class FormNotifier extends StateNotifier<FormDetailState> {
|
||||
final SaveScanUseCase _saveScanUseCase;
|
||||
final Ref _ref;
|
||||
|
||||
FormNotifier(
|
||||
this._saveScanUseCase,
|
||||
this._ref,
|
||||
String barcode,
|
||||
) : super(FormDetailState(barcode: barcode));
|
||||
|
||||
/// Update field 1
|
||||
void updateField1(String value) {
|
||||
state = state.copyWith(field1: value, error: null);
|
||||
}
|
||||
|
||||
/// Update field 2
|
||||
void updateField2(String value) {
|
||||
state = state.copyWith(field2: value, error: null);
|
||||
}
|
||||
|
||||
/// Update field 3
|
||||
void updateField3(String value) {
|
||||
state = state.copyWith(field3: value, error: null);
|
||||
}
|
||||
|
||||
/// Update field 4
|
||||
void updateField4(String value) {
|
||||
state = state.copyWith(field4: value, error: null);
|
||||
}
|
||||
|
||||
/// Update barcode
|
||||
void updateBarcode(String value) {
|
||||
state = state.copyWith(barcode: value, error: null);
|
||||
}
|
||||
|
||||
/// Clear all fields
|
||||
void clearFields() {
|
||||
state = FormDetailState(barcode: state.barcode);
|
||||
}
|
||||
|
||||
/// Populate form with existing scan data
|
||||
void populateWithScanItem(ScanItem scanItem) {
|
||||
state = state.copyWith(
|
||||
barcode: scanItem.barcode,
|
||||
field1: scanItem.field1,
|
||||
field2: scanItem.field2,
|
||||
field3: scanItem.field3,
|
||||
field4: scanItem.field4,
|
||||
error: null,
|
||||
);
|
||||
}
|
||||
|
||||
/// Save form data to server and local storage
|
||||
Future<void> saveData() async {
|
||||
if (!state.isValid) {
|
||||
final errors = state.validationErrors;
|
||||
state = state.copyWith(error: errors.join(', '));
|
||||
return;
|
||||
}
|
||||
|
||||
state = state.copyWith(isLoading: true, error: null, isSaveSuccess: false);
|
||||
|
||||
final params = SaveScanParams(
|
||||
barcode: state.barcode,
|
||||
field1: state.field1,
|
||||
field2: state.field2,
|
||||
field3: state.field3,
|
||||
field4: state.field4,
|
||||
);
|
||||
|
||||
final result = await _saveScanUseCase.call(params);
|
||||
|
||||
result.fold(
|
||||
(failure) => state = state.copyWith(
|
||||
isLoading: false,
|
||||
error: failure.message,
|
||||
isSaveSuccess: false,
|
||||
),
|
||||
(_) {
|
||||
state = state.copyWith(
|
||||
isLoading: false,
|
||||
isSaveSuccess: true,
|
||||
error: null,
|
||||
);
|
||||
|
||||
// Update the scanner history with saved data
|
||||
final savedScanItem = ScanItem(
|
||||
barcode: state.barcode,
|
||||
timestamp: DateTime.now(),
|
||||
field1: state.field1,
|
||||
field2: state.field2,
|
||||
field3: state.field3,
|
||||
field4: state.field4,
|
||||
);
|
||||
|
||||
_ref.read(scannerProvider.notifier).updateScanItem(savedScanItem);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Print form data
|
||||
Future<void> printData() async {
|
||||
try {
|
||||
|
||||
} catch (e) {
|
||||
state = state.copyWith(error: 'Failed to print: ${e.toString()}');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Clear error message
|
||||
void clearError() {
|
||||
state = state.copyWith(error: null);
|
||||
}
|
||||
|
||||
/// Reset save success state
|
||||
void resetSaveSuccess() {
|
||||
state = state.copyWith(isSaveSuccess: false);
|
||||
}
|
||||
}
|
||||
|
||||
/// Provider factory for form state (requires barcode parameter)
|
||||
final formProviderFamily = StateNotifierProvider.family<FormNotifier, FormDetailState, String>(
|
||||
(ref, barcode) => FormNotifier(
|
||||
ref.watch(saveScanUseCaseProvider),
|
||||
ref,
|
||||
barcode,
|
||||
),
|
||||
);
|
||||
|
||||
/// Convenience provider for accessing form state with a specific barcode
|
||||
/// This should be used with Provider.of or ref.watch(formProvider(barcode))
|
||||
Provider<FormNotifier> formProvider(String barcode) {
|
||||
return Provider<FormNotifier>((ref) {
|
||||
return ref.watch(formProviderFamily(barcode).notifier);
|
||||
});
|
||||
}
|
||||
|
||||
/// Convenience provider for accessing form state
|
||||
Provider<FormDetailState> formStateProvider(String barcode) {
|
||||
return Provider<FormDetailState>((ref) {
|
||||
return ref.watch(formProviderFamily(barcode));
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../../data/models/scan_item.dart';
|
||||
import '../../domain/usecases/get_scan_history_usecase.dart';
|
||||
import 'dependency_injection.dart';
|
||||
|
||||
/// State for the scanner functionality
|
||||
class ScannerState {
|
||||
final String? currentBarcode;
|
||||
final List<ScanItem> history;
|
||||
final bool isLoading;
|
||||
final String? error;
|
||||
|
||||
const ScannerState({
|
||||
this.currentBarcode,
|
||||
this.history = const [],
|
||||
this.isLoading = false,
|
||||
this.error,
|
||||
});
|
||||
|
||||
ScannerState copyWith({
|
||||
String? currentBarcode,
|
||||
List<ScanItem>? history,
|
||||
bool? isLoading,
|
||||
String? error,
|
||||
}) {
|
||||
return ScannerState(
|
||||
currentBarcode: currentBarcode ?? this.currentBarcode,
|
||||
history: history ?? this.history,
|
||||
isLoading: isLoading ?? this.isLoading,
|
||||
error: error ?? this.error,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is ScannerState &&
|
||||
runtimeType == other.runtimeType &&
|
||||
currentBarcode == other.currentBarcode &&
|
||||
history == other.history &&
|
||||
isLoading == other.isLoading &&
|
||||
error == other.error;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
currentBarcode.hashCode ^
|
||||
history.hashCode ^
|
||||
isLoading.hashCode ^
|
||||
error.hashCode;
|
||||
}
|
||||
|
||||
/// Scanner state notifier
|
||||
class ScannerNotifier extends StateNotifier<ScannerState> {
|
||||
final GetScanHistoryUseCase _getScanHistoryUseCase;
|
||||
|
||||
ScannerNotifier(this._getScanHistoryUseCase) : super(const ScannerState()) {
|
||||
_loadHistory();
|
||||
}
|
||||
|
||||
/// Load scan history from local storage
|
||||
Future<void> _loadHistory() async {
|
||||
state = state.copyWith(isLoading: true, error: null);
|
||||
|
||||
final result = await _getScanHistoryUseCase();
|
||||
result.fold(
|
||||
(failure) => state = state.copyWith(
|
||||
isLoading: false,
|
||||
error: failure.message,
|
||||
),
|
||||
(history) => state = state.copyWith(
|
||||
isLoading: false,
|
||||
history: history.map((entity) => ScanItem.fromEntity(entity)).toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Update current scanned barcode
|
||||
void updateBarcode(String barcode) {
|
||||
if (barcode.trim().isEmpty) return;
|
||||
|
||||
state = state.copyWith(currentBarcode: barcode);
|
||||
|
||||
// Add to history if not already present
|
||||
final existingIndex = state.history.indexWhere((item) => item.barcode == barcode);
|
||||
if (existingIndex == -1) {
|
||||
final newScanItem = ScanItem(
|
||||
barcode: barcode,
|
||||
timestamp: DateTime.now(),
|
||||
);
|
||||
|
||||
final updatedHistory = [newScanItem, ...state.history];
|
||||
state = state.copyWith(history: updatedHistory);
|
||||
} else {
|
||||
// Move existing item to top
|
||||
final existingItem = state.history[existingIndex];
|
||||
final updatedHistory = List<ScanItem>.from(state.history);
|
||||
updatedHistory.removeAt(existingIndex);
|
||||
updatedHistory.insert(0, existingItem.copyWith(timestamp: DateTime.now()));
|
||||
state = state.copyWith(history: updatedHistory);
|
||||
}
|
||||
}
|
||||
|
||||
/// Clear current barcode
|
||||
void clearBarcode() {
|
||||
state = state.copyWith(currentBarcode: null);
|
||||
}
|
||||
|
||||
/// Refresh history from storage
|
||||
Future<void> refreshHistory() async {
|
||||
await _loadHistory();
|
||||
}
|
||||
|
||||
/// Add or update scan item in history
|
||||
void updateScanItem(ScanItem scanItem) {
|
||||
final existingIndex = state.history.indexWhere(
|
||||
(item) => item.barcode == scanItem.barcode,
|
||||
);
|
||||
|
||||
List<ScanItem> updatedHistory;
|
||||
if (existingIndex != -1) {
|
||||
// Update existing item
|
||||
updatedHistory = List<ScanItem>.from(state.history);
|
||||
updatedHistory[existingIndex] = scanItem;
|
||||
} else {
|
||||
// Add new item at the beginning
|
||||
updatedHistory = [scanItem, ...state.history];
|
||||
}
|
||||
|
||||
state = state.copyWith(history: updatedHistory);
|
||||
}
|
||||
|
||||
/// Clear error message
|
||||
void clearError() {
|
||||
state = state.copyWith(error: null);
|
||||
}
|
||||
}
|
||||
|
||||
/// Provider for scanner state
|
||||
final scannerProvider = StateNotifierProvider<ScannerNotifier, ScannerState>(
|
||||
(ref) => ScannerNotifier(
|
||||
ref.watch(getScanHistoryUseCaseProvider),
|
||||
),
|
||||
);
|
||||
|
||||
/// Provider for current barcode (for easy access)
|
||||
final currentBarcodeProvider = Provider<String?>((ref) {
|
||||
return ref.watch(scannerProvider).currentBarcode;
|
||||
});
|
||||
|
||||
/// Provider for scan history (for easy access)
|
||||
final scanHistoryProvider = Provider<List<ScanItem>>((ref) {
|
||||
return ref.watch(scannerProvider).history;
|
||||
});
|
||||
|
||||
/// Provider for scanner loading state
|
||||
final scannerLoadingProvider = Provider<bool>((ref) {
|
||||
return ref.watch(scannerProvider).isLoading;
|
||||
});
|
||||
|
||||
/// Provider for scanner error state
|
||||
final scannerErrorProvider = Provider<String?>((ref) {
|
||||
return ref.watch(scannerProvider).error;
|
||||
});
|
||||
Reference in New Issue
Block a user