This commit is contained in:
2025-09-16 23:14:35 +07:00
parent be2ad0a8fd
commit 9ebe7c2919
55 changed files with 5953 additions and 893 deletions

View File

@@ -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']!;
});

View 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));
});
}

View File

@@ -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;
});