runable
This commit is contained in:
@@ -75,6 +75,10 @@ class ApiConstants {
|
||||
/// Use: '${ApiConstants.categories}/:id'
|
||||
static String categoryById(String id) => '$categories/$id';
|
||||
|
||||
/// GET - Fetch category with its products
|
||||
/// Use: '${ApiConstants.categories}/:id/products'
|
||||
static String categoryWithProducts(String id) => '$categories/$id/products';
|
||||
|
||||
/// POST - Sync categories (bulk update/create)
|
||||
static const String syncCategories = '$categories/sync';
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
/// - Storage: Secure storage, database
|
||||
/// - Theme: Material 3 theme, colors, typography
|
||||
/// - Utils: Formatters, validators, extensions, helpers
|
||||
/// - DI: Dependency injection setup
|
||||
/// - Providers: Riverpod providers for core dependencies
|
||||
/// - Widgets: Reusable UI components
|
||||
/// - Errors: Exception and failure handling
|
||||
library;
|
||||
@@ -23,7 +23,6 @@ library;
|
||||
export 'config/config.dart';
|
||||
export 'constants/constants.dart';
|
||||
export 'database/database.dart';
|
||||
export 'di/di.dart';
|
||||
export 'errors/errors.dart';
|
||||
export 'network/network.dart';
|
||||
export 'performance.dart';
|
||||
|
||||
@@ -19,6 +19,7 @@ class SeedData {
|
||||
color: '#2196F3', // Blue
|
||||
productCount: 0,
|
||||
createdAt: now.subtract(const Duration(days: 60)),
|
||||
updatedAt: now.subtract(const Duration(days: 60)),
|
||||
),
|
||||
CategoryModel(
|
||||
id: 'cat_appliances',
|
||||
@@ -28,6 +29,7 @@ class SeedData {
|
||||
color: '#4CAF50', // Green
|
||||
productCount: 0,
|
||||
createdAt: now.subtract(const Duration(days: 55)),
|
||||
updatedAt: now.subtract(const Duration(days: 55)),
|
||||
),
|
||||
CategoryModel(
|
||||
id: 'cat_sports',
|
||||
@@ -37,6 +39,7 @@ class SeedData {
|
||||
color: '#FF9800', // Orange
|
||||
productCount: 0,
|
||||
createdAt: now.subtract(const Duration(days: 50)),
|
||||
updatedAt: now.subtract(const Duration(days: 50)),
|
||||
),
|
||||
CategoryModel(
|
||||
id: 'cat_fashion',
|
||||
@@ -46,6 +49,7 @@ class SeedData {
|
||||
color: '#E91E63', // Pink
|
||||
productCount: 0,
|
||||
createdAt: now.subtract(const Duration(days: 45)),
|
||||
updatedAt: now.subtract(const Duration(days: 45)),
|
||||
),
|
||||
CategoryModel(
|
||||
id: 'cat_books',
|
||||
@@ -55,6 +59,7 @@ class SeedData {
|
||||
color: '#9C27B0', // Purple
|
||||
productCount: 0,
|
||||
createdAt: now.subtract(const Duration(days: 40)),
|
||||
updatedAt: now.subtract(const Duration(days: 40)),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
/// Export all dependency injection components
|
||||
///
|
||||
/// Contains service locator and injection container setup
|
||||
library;
|
||||
|
||||
export 'injection_container.dart';
|
||||
export 'service_locator.dart';
|
||||
@@ -1,74 +0,0 @@
|
||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import '../../features/auth/data/datasources/auth_remote_datasource.dart';
|
||||
import '../../features/auth/data/repositories/auth_repository_impl.dart';
|
||||
import '../../features/auth/domain/repositories/auth_repository.dart';
|
||||
import '../network/dio_client.dart';
|
||||
import '../network/network_info.dart';
|
||||
import '../storage/secure_storage.dart';
|
||||
|
||||
/// Service locator instance
|
||||
final sl = GetIt.instance;
|
||||
|
||||
/// Initialize all dependencies
|
||||
///
|
||||
/// This function registers all the dependencies required by the app
|
||||
/// in the GetIt service locator. Call this in main() before runApp().
|
||||
Future<void> initDependencies() async {
|
||||
// ===== Core =====
|
||||
|
||||
// Connectivity (external) - Register first as it's a dependency
|
||||
sl.registerLazySingleton<Connectivity>(
|
||||
() => Connectivity(),
|
||||
);
|
||||
|
||||
// Network Info
|
||||
sl.registerLazySingleton<NetworkInfo>(
|
||||
() => NetworkInfo(sl()),
|
||||
);
|
||||
|
||||
// Dio Client
|
||||
sl.registerLazySingleton<DioClient>(
|
||||
() => DioClient(),
|
||||
);
|
||||
|
||||
// Secure Storage
|
||||
sl.registerLazySingleton<SecureStorage>(
|
||||
() => SecureStorage(),
|
||||
);
|
||||
|
||||
// ===== Authentication Feature =====
|
||||
|
||||
// Auth Remote Data Source
|
||||
sl.registerLazySingleton<AuthRemoteDataSource>(
|
||||
() => AuthRemoteDataSourceImpl(dioClient: sl()),
|
||||
);
|
||||
|
||||
// Auth Repository
|
||||
sl.registerLazySingleton<AuthRepository>(
|
||||
() => AuthRepositoryImpl(
|
||||
remoteDataSource: sl(),
|
||||
secureStorage: sl(),
|
||||
dioClient: sl(),
|
||||
),
|
||||
);
|
||||
|
||||
// ===== Data Sources =====
|
||||
// Note: Other data sources are managed by Riverpod providers
|
||||
// No direct registration needed here
|
||||
|
||||
// ===== Repositories =====
|
||||
// TODO: Register other repositories when they are implemented
|
||||
|
||||
// ===== Use Cases =====
|
||||
// TODO: Register use cases when they are implemented
|
||||
|
||||
// ===== Providers (Riverpod) =====
|
||||
// Note: Riverpod providers are registered differently
|
||||
// This is just for dependency injection of external dependencies
|
||||
}
|
||||
|
||||
/// Clear all dependencies (useful for testing)
|
||||
void resetDependencies() {
|
||||
sl.reset();
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import '../network/dio_client.dart';
|
||||
import '../network/network_info.dart';
|
||||
|
||||
final getIt = GetIt.instance;
|
||||
|
||||
/// Setup dependency injection
|
||||
Future<void> setupServiceLocator() async {
|
||||
// External dependencies
|
||||
getIt.registerLazySingleton(() => Connectivity());
|
||||
|
||||
// Core
|
||||
getIt.registerLazySingleton(() => DioClient());
|
||||
getIt.registerLazySingleton(() => NetworkInfo(getIt()));
|
||||
|
||||
// Data sources - to be added when features are implemented
|
||||
|
||||
// Repositories - to be added when features are implemented
|
||||
|
||||
// Use cases - to be added when features are implemented
|
||||
}
|
||||
104
lib/core/network/api_response.dart
Normal file
104
lib/core/network/api_response.dart
Normal file
@@ -0,0 +1,104 @@
|
||||
/// Generic API Response wrapper
|
||||
///
|
||||
/// Wraps all API responses in a consistent format with success status,
|
||||
/// data payload, optional message, and optional pagination metadata.
|
||||
class ApiResponse<T> {
|
||||
final bool success;
|
||||
final T data;
|
||||
final String? message;
|
||||
final PaginationMeta? meta;
|
||||
|
||||
const ApiResponse({
|
||||
required this.success,
|
||||
required this.data,
|
||||
this.message,
|
||||
this.meta,
|
||||
});
|
||||
|
||||
/// Create from JSON with a data parser function
|
||||
factory ApiResponse.fromJson(
|
||||
Map<String, dynamic> json,
|
||||
T Function(dynamic) dataParser,
|
||||
) {
|
||||
return ApiResponse(
|
||||
success: json['success'] as bool? ?? false,
|
||||
data: dataParser(json['data']),
|
||||
message: json['message'] as String?,
|
||||
meta: json['meta'] != null
|
||||
? PaginationMeta.fromJson(json['meta'] as Map<String, dynamic>)
|
||||
: null,
|
||||
);
|
||||
}
|
||||
|
||||
/// Convert to JSON
|
||||
Map<String, dynamic> toJson(dynamic Function(T) dataSerializer) {
|
||||
return {
|
||||
'success': success,
|
||||
'data': dataSerializer(data),
|
||||
if (message != null) 'message': message,
|
||||
if (meta != null) 'meta': meta!.toJson(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Pagination metadata
|
||||
class PaginationMeta {
|
||||
final int page;
|
||||
final int limit;
|
||||
final int total;
|
||||
final int totalPages;
|
||||
final bool hasPreviousPage;
|
||||
final bool hasNextPage;
|
||||
|
||||
const PaginationMeta({
|
||||
required this.page,
|
||||
required this.limit,
|
||||
required this.total,
|
||||
required this.totalPages,
|
||||
required this.hasPreviousPage,
|
||||
required this.hasNextPage,
|
||||
});
|
||||
|
||||
/// Create from JSON
|
||||
factory PaginationMeta.fromJson(Map<String, dynamic> json) {
|
||||
return PaginationMeta(
|
||||
page: json['page'] as int,
|
||||
limit: json['limit'] as int,
|
||||
total: json['total'] as int,
|
||||
totalPages: json['totalPages'] as int,
|
||||
hasPreviousPage: json['hasPreviousPage'] as bool,
|
||||
hasNextPage: json['hasNextPage'] as bool,
|
||||
);
|
||||
}
|
||||
|
||||
/// Convert to JSON
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'page': page,
|
||||
'limit': limit,
|
||||
'total': total,
|
||||
'totalPages': totalPages,
|
||||
'hasPreviousPage': hasPreviousPage,
|
||||
'hasNextPage': hasNextPage,
|
||||
};
|
||||
}
|
||||
|
||||
/// Create a copy with updated fields
|
||||
PaginationMeta copyWith({
|
||||
int? page,
|
||||
int? limit,
|
||||
int? total,
|
||||
int? totalPages,
|
||||
bool? hasPreviousPage,
|
||||
bool? hasNextPage,
|
||||
}) {
|
||||
return PaginationMeta(
|
||||
page: page ?? this.page,
|
||||
limit: limit ?? this.limit,
|
||||
total: total ?? this.total,
|
||||
totalPages: totalPages ?? this.totalPages,
|
||||
hasPreviousPage: hasPreviousPage ?? this.hasPreviousPage,
|
||||
hasNextPage: hasNextPage ?? this.hasNextPage,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -4,5 +4,6 @@
|
||||
library;
|
||||
|
||||
export 'api_interceptor.dart';
|
||||
export 'api_response.dart';
|
||||
export 'dio_client.dart';
|
||||
export 'network_info.dart';
|
||||
|
||||
23
lib/core/providers/core_providers.dart
Normal file
23
lib/core/providers/core_providers.dart
Normal file
@@ -0,0 +1,23 @@
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import '../network/dio_client.dart';
|
||||
import '../storage/secure_storage.dart';
|
||||
|
||||
part 'core_providers.g.dart';
|
||||
|
||||
/// Provider for DioClient (singleton)
|
||||
///
|
||||
/// This is the global HTTP client used across the entire app.
|
||||
/// It's configured with interceptors, timeout settings, and auth token injection.
|
||||
@Riverpod(keepAlive: true)
|
||||
DioClient dioClient(Ref ref) {
|
||||
return DioClient();
|
||||
}
|
||||
|
||||
/// Provider for SecureStorage (singleton)
|
||||
///
|
||||
/// This is the global secure storage used for storing sensitive data like tokens.
|
||||
/// Uses platform-specific secure storage (Keychain on iOS, EncryptedSharedPreferences on Android).
|
||||
@Riverpod(keepAlive: true)
|
||||
SecureStorage secureStorage(Ref ref) {
|
||||
return SecureStorage();
|
||||
}
|
||||
119
lib/core/providers/core_providers.g.dart
Normal file
119
lib/core/providers/core_providers.g.dart
Normal file
@@ -0,0 +1,119 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'core_providers.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint, type=warning
|
||||
/// Provider for DioClient (singleton)
|
||||
///
|
||||
/// This is the global HTTP client used across the entire app.
|
||||
/// It's configured with interceptors, timeout settings, and auth token injection.
|
||||
|
||||
@ProviderFor(dioClient)
|
||||
const dioClientProvider = DioClientProvider._();
|
||||
|
||||
/// Provider for DioClient (singleton)
|
||||
///
|
||||
/// This is the global HTTP client used across the entire app.
|
||||
/// It's configured with interceptors, timeout settings, and auth token injection.
|
||||
|
||||
final class DioClientProvider
|
||||
extends $FunctionalProvider<DioClient, DioClient, DioClient>
|
||||
with $Provider<DioClient> {
|
||||
/// Provider for DioClient (singleton)
|
||||
///
|
||||
/// This is the global HTTP client used across the entire app.
|
||||
/// It's configured with interceptors, timeout settings, and auth token injection.
|
||||
const DioClientProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'dioClientProvider',
|
||||
isAutoDispose: false,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$dioClientHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
$ProviderElement<DioClient> $createElement($ProviderPointer pointer) =>
|
||||
$ProviderElement(pointer);
|
||||
|
||||
@override
|
||||
DioClient create(Ref ref) {
|
||||
return dioClient(ref);
|
||||
}
|
||||
|
||||
/// {@macro riverpod.override_with_value}
|
||||
Override overrideWithValue(DioClient value) {
|
||||
return $ProviderOverride(
|
||||
origin: this,
|
||||
providerOverride: $SyncValueProvider<DioClient>(value),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _$dioClientHash() => r'895f0dc2f8d5eab562ad65390e5c6d4a1f722b0d';
|
||||
|
||||
/// Provider for SecureStorage (singleton)
|
||||
///
|
||||
/// This is the global secure storage used for storing sensitive data like tokens.
|
||||
/// Uses platform-specific secure storage (Keychain on iOS, EncryptedSharedPreferences on Android).
|
||||
|
||||
@ProviderFor(secureStorage)
|
||||
const secureStorageProvider = SecureStorageProvider._();
|
||||
|
||||
/// Provider for SecureStorage (singleton)
|
||||
///
|
||||
/// This is the global secure storage used for storing sensitive data like tokens.
|
||||
/// Uses platform-specific secure storage (Keychain on iOS, EncryptedSharedPreferences on Android).
|
||||
|
||||
final class SecureStorageProvider
|
||||
extends $FunctionalProvider<SecureStorage, SecureStorage, SecureStorage>
|
||||
with $Provider<SecureStorage> {
|
||||
/// Provider for SecureStorage (singleton)
|
||||
///
|
||||
/// This is the global secure storage used for storing sensitive data like tokens.
|
||||
/// Uses platform-specific secure storage (Keychain on iOS, EncryptedSharedPreferences on Android).
|
||||
const SecureStorageProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'secureStorageProvider',
|
||||
isAutoDispose: false,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$secureStorageHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
$ProviderElement<SecureStorage> $createElement($ProviderPointer pointer) =>
|
||||
$ProviderElement(pointer);
|
||||
|
||||
@override
|
||||
SecureStorage create(Ref ref) {
|
||||
return secureStorage(ref);
|
||||
}
|
||||
|
||||
/// {@macro riverpod.override_with_value}
|
||||
Override overrideWithValue(SecureStorage value) {
|
||||
return $ProviderOverride(
|
||||
origin: this,
|
||||
providerOverride: $SyncValueProvider<SecureStorage>(value),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _$secureStorageHash() => r'5c9908c0046ad0e39469ee7acbb5540397b36693';
|
||||
@@ -1,3 +1,4 @@
|
||||
/// Export all core providers
|
||||
export 'core_providers.dart';
|
||||
export 'network_info_provider.dart';
|
||||
export 'sync_status_provider.dart';
|
||||
|
||||
@@ -45,10 +45,10 @@ class SyncStatus extends _$SyncStatus {
|
||||
|
||||
try {
|
||||
// Sync categories first (products depend on categories)
|
||||
await ref.read(categoriesProvider.notifier).syncCategories();
|
||||
await ref.read(categoriesProvider.notifier).refresh();
|
||||
|
||||
// Then sync products
|
||||
await ref.read(productsProvider.notifier).syncProducts();
|
||||
await ref.read(productsProvider.notifier).refresh();
|
||||
|
||||
// Update last sync time in settings
|
||||
await ref.read(settingsProvider.notifier).updateLastSyncTime();
|
||||
@@ -100,7 +100,7 @@ class SyncStatus extends _$SyncStatus {
|
||||
);
|
||||
|
||||
try {
|
||||
await ref.read(productsProvider.notifier).syncProducts();
|
||||
await ref.read(productsProvider.notifier).refresh();
|
||||
await ref.read(settingsProvider.notifier).updateLastSyncTime();
|
||||
|
||||
state = AsyncValue.data(
|
||||
@@ -146,7 +146,7 @@ class SyncStatus extends _$SyncStatus {
|
||||
);
|
||||
|
||||
try {
|
||||
await ref.read(categoriesProvider.notifier).syncCategories();
|
||||
await ref.read(categoriesProvider.notifier).refresh();
|
||||
await ref.read(settingsProvider.notifier).updateLastSyncTime();
|
||||
|
||||
state = AsyncValue.data(
|
||||
|
||||
@@ -36,7 +36,7 @@ final class SyncStatusProvider
|
||||
SyncStatus create() => SyncStatus();
|
||||
}
|
||||
|
||||
String _$syncStatusHash() => r'dc92a1b83c89af94dfe94b646aa81d9501f371d7';
|
||||
String _$syncStatusHash() => r'bf09683a3a67b6c7104274c7a4b92ee410de8e45';
|
||||
|
||||
/// Sync status provider - manages data synchronization state
|
||||
|
||||
|
||||
Reference in New Issue
Block a user