ionic-planb-logistic-app-fl.../lib/api/types.dart
Claude Code 4b03e9aba5 Initial commit: Plan B Logistics Flutter app with dark mode and responsive design
Implements complete refactor of Ionic Angular logistics app to Flutter/Dart with:
- Svrnty dark mode console theme (Material Design 3)
- Responsive layouts (mobile, tablet, desktop) following FRONTEND standards
- CQRS API integration with Result<T> error handling
- OAuth2/OIDC authentication support (mocked for initial testing)
- Delivery route and delivery management features
- Multi-language support (EN/FR) with i18n
- Native integrations (camera, phone calls, maps)
- Strict typing throughout codebase
- Mock data for UI testing without backend

Follows all FRONTEND style guides, design patterns, and conventions.
App is running in dark mode and fully responsive across all device sizes.

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-31 04:58:10 -04:00

161 lines
3.3 KiB
Dart

abstract interface class Serializable {
Map<String, Object?> toJson();
}
enum ApiErrorType {
network,
timeout,
validation,
http,
unknown,
}
class ApiError {
final ApiErrorType type;
final String message;
final int? statusCode;
final Map<String, List<String>>? details;
final Exception? originalException;
const ApiError({
required this.type,
required this.message,
this.statusCode,
this.details,
this.originalException,
});
factory ApiError.network(String message) => ApiError(
type: ApiErrorType.network,
message: message,
);
factory ApiError.timeout() => const ApiError(
type: ApiErrorType.timeout,
message: 'Request timeout',
);
factory ApiError.validation(String message, Map<String, List<String>>? details) => ApiError(
type: ApiErrorType.validation,
message: message,
details: details,
);
factory ApiError.http({
required int statusCode,
required String message,
}) => ApiError(
type: ApiErrorType.http,
message: message,
statusCode: statusCode,
);
factory ApiError.unknown(String message, {Exception? exception}) => ApiError(
type: ApiErrorType.unknown,
message: message,
originalException: exception,
);
}
sealed class Result<T> {
const Result();
factory Result.success(T data) => Success<T>(data);
factory Result.error(ApiError error) => Error<T>(error);
R when<R>({
required R Function(T data) success,
required R Function(ApiError error) onError,
}) {
return switch (this) {
Success<T>(:final data) => success(data),
Error<T>(:final error) => onError(error),
};
}
R? whenSuccess<R>(R Function(T data) fn) {
return switch (this) {
Success<T>(:final data) => fn(data),
Error<T>() => null,
};
}
R? whenError<R>(R Function(ApiError error) fn) {
return switch (this) {
Success<T>() => null,
Error<T>(:final error) => fn(error),
};
}
bool get isSuccess => this is Success<T>;
bool get isError => this is Error<T>;
T? getOrNull() => whenSuccess((data) => data);
ApiError? getErrorOrNull() => whenError((error) => error);
}
final class Success<T> extends Result<T> {
final T data;
const Success(this.data);
}
final class Error<T> extends Result<T> {
final ApiError error;
const Error(this.error);
}
class PaginatedResult<T> {
final List<T> items;
final int page;
final int pageSize;
final int totalCount;
const PaginatedResult({
required this.items,
required this.page,
required this.pageSize,
required this.totalCount,
});
int get totalPages => (totalCount / pageSize).ceil();
bool get hasNextPage => page < totalPages;
}
enum FilterOperator {
equals('eq'),
notEquals('neq'),
greaterThan('gt'),
greaterThanOrEqual('gte'),
lessThan('lt'),
lessThanOrEqual('lte'),
contains('contains'),
startsWith('startsWith'),
endsWith('endsWith'),
in_('in');
final String operator;
const FilterOperator(this.operator);
}
class FilterCriteria implements Serializable {
final String field;
final FilterOperator operator;
final Object? value;
FilterCriteria({
required this.field,
required this.operator,
required this.value,
});
@override
Map<String, Object?> toJson() => {
'field': field,
'operator': operator.operator,
'value': value,
};
}