order detail fetch

This commit is contained in:
komekh 2024-08-15 10:49:01 +05:00
parent 0ad7d44642
commit 883c196aae
25 changed files with 328 additions and 88 deletions

View File

@ -1,3 +1,6 @@
{
"dart.flutterSdkPath": ".fvm/versions/3.22.1"
"dart.flutterSdkPath": ".fvm/versions/3.22.1",
"cSpell.words": [
"dartz"
]
}

View File

@ -3,3 +3,4 @@ export 'language_bloc/language_bloc.dart';
export 'splash_cubit/splash_cubit.dart';
export 'user_bloc/user_bloc.dart';
export 'order_bloc/order_bloc.dart';
export 'order_detail_bloc/order_detail_bloc.dart';

View File

@ -27,10 +27,7 @@ class OrderBloc extends Bloc<OrderEvent, OrderState> {
on<GetMoreOrders>(_onLoadMoreOrders);
}
FutureOr<void> _onGetOrders(
GetOrders event,
Emitter<OrderState> emit,
) async {
FutureOr<void> _onGetOrders(GetOrders event, Emitter<OrderState> emit) async {
try {
emit(OrderLoading(
orders: const [],
@ -61,7 +58,7 @@ class OrderBloc extends Bloc<OrderEvent, OrderState> {
}
}
void _onLoadMoreOrders(GetMoreOrders event, Emitter<OrderState> emit) async {
Future<void> _onLoadMoreOrders(GetMoreOrders event, Emitter<OrderState> emit) async {
var state = this.state;
var limit = state.metaData.limit;
var total = state.metaData.total;

View File

@ -0,0 +1,32 @@
import 'dart:async';
import 'package:equatable/equatable.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../core/errors/failures.dart';
import '../../domain/entities/route/route.dart';
import '../../domain/usecases/order/get_routes_usecase.dart';
part 'order_detail_event.dart';
part 'order_detail_state.dart';
class OrderDetailBloc extends Bloc<OrderDetailEvent, OrderDetailState> {
final GetRoutesUseCase _getRoutesUseCase;
OrderDetailBloc(this._getRoutesUseCase) : super(OrderDetailInitial()) {
on<GetRoutes>(_onGetRoutes);
}
FutureOr<void> _onGetRoutes(GetRoutes event, Emitter<OrderDetailState> emit) async {
try {
emit(RoutesLoading());
final result = await _getRoutesUseCase(event.cargoId);
result.fold(
(failure) => emit(RoutesError(failure: failure)),
(response) => emit(RoutesLoaded(routes: response.routes)),
);
} catch (e) {
emit(RoutesError(failure: ExceptionFailure()));
}
}
}

View File

@ -0,0 +1,16 @@
part of 'order_detail_bloc.dart';
sealed class OrderDetailEvent extends Equatable {
const OrderDetailEvent();
@override
List<Object> get props => [];
}
class GetRoutes extends OrderDetailEvent {
final String cargoId;
const GetRoutes(this.cargoId);
@override
List<Object> get props => [];
}

View File

@ -0,0 +1,29 @@
part of 'order_detail_bloc.dart';
sealed class OrderDetailState extends Equatable {
const OrderDetailState();
@override
List<Object> get props => [];
}
class OrderDetailInitial extends OrderDetailState {}
class RoutesLoading extends OrderDetailState {}
class RoutesLoaded extends OrderDetailState {
final List<RouteEntity> routes;
const RoutesLoaded({required this.routes});
@override
List<Object> get props => [routes];
}
class RoutesError extends OrderDetailState {
final Failure failure;
const RoutesError({
required this.failure,
});
@override
List<Object> get props => [];
}

View File

@ -27,6 +27,9 @@ class MyApp extends StatelessWidget {
BlocProvider(
create: (context) => di.sl<OrderBloc>()..add(const GetOrders(FilterProductParams())),
),
BlocProvider(
create: (context) => di.sl<OrderDetailBloc>(),
),
],
child: MaterialApp(
debugShowCheckedModeBanner: false,

View File

@ -1,3 +1,4 @@
import 'package:cargo/domain/domain.dart';
import 'package:flutter/material.dart';
import '../../presentation/presentation.dart';
@ -22,7 +23,8 @@ sealed class AppRouter {
case root:
return MaterialPageRoute(builder: (_) => const RootScreen());
case orderDetails:
return MaterialPageRoute(builder: (_) => const OrderDetailsScreen());
OrderEntity order = routeSettings.arguments as OrderEntity;
return MaterialPageRoute(builder: (_) => OrderDetailsScreen(order: order));
default:
throw const RouteException('Route not found!');

View File

@ -3,9 +3,11 @@ import 'package:http/http.dart' as http;
import '../../../core/core.dart';
import '../../../domain/domain.dart';
import '../../data.dart';
import '../../models/route/route_response_model.dart';
abstract class OrderRemoteDataSource {
Future<OrderResponseModel> getOrders(FilterProductParams params, String token);
Future<RouteResponseModel> getRoutes(String orderId);
}
class OrderRemoteDataSourceImpl implements OrderRemoteDataSource {
@ -29,4 +31,21 @@ class OrderRemoteDataSourceImpl implements OrderRemoteDataSource {
throw ServerException();
}
}
@override
Future<RouteResponseModel> getRoutes(String orderId) async {
final response = await client.get(
Uri.parse('$baseUrl/Cargo/Route/$orderId'),
headers: {
'Content-Type': 'application/json',
'accept': '*/*',
},
);
if (response.statusCode == 200) {
return routeResponseModelFromJson(response.body);
} else {
throw ServerException();
}
}
}

View File

@ -3,3 +3,4 @@ export 'user/authentication_response_model.dart';
export 'user/user_model.dart';
export 'order/order_model.dart';
export 'order/order_response_model.dart';
export 'route/route_model.dart';

View File

@ -0,0 +1,28 @@
import 'package:cargo/domain/domain.dart';
class RouteModel extends RouteEntity {
const RouteModel({
required super.name,
required super.lat,
required super.long,
required super.isCurrent,
});
factory RouteModel.fromJson(Map<String, dynamic> json) {
return RouteModel(
name: json['Name'],
lat: json['Lat'] + .0,
long: json['Long'] + .0,
isCurrent: json['IsCurrent'],
);
}
Map<String, dynamic> toJson() {
return {
'Name': name,
'Lat': lat,
'Long': long,
'IsCurrent': isCurrent,
};
}
}

View File

@ -0,0 +1,18 @@
import 'dart:convert';
import '../../../domain/domain.dart';
import 'route_model.dart';
RouteResponseModel routeResponseModelFromJson(String str) => RouteResponseModel.fromJson(json.decode(str));
class RouteResponseModel extends RouteResponse {
RouteResponseModel({
required super.routes,
});
factory RouteResponseModel.fromJson(List<dynamic> jsonList) {
return RouteResponseModel(
routes: jsonList.map((json) => RouteModel.fromJson(json)).toList(),
);
}
}

View File

@ -1,3 +1,4 @@
import 'package:cargo/data/models/route/route_response_model.dart';
import 'package:dartz/dartz.dart';
import '../../core/core.dart';
@ -34,4 +35,18 @@ class OrderRepositoryImpl extends OrderRepository {
return Left(failure);
}
}
@override
Future<Either<Failure, RouteResponseModel>> getRoutes(String cargoId) async {
if (!await networkInfo.isConnected) {
return Left(NetworkFailure());
}
try {
final response = await remoteDataSource.getRoutes(cargoId);
return Right(response);
} on Failure catch (failure) {
return Left(failure);
}
}
}

View File

@ -9,6 +9,10 @@ void registerOrderFeature() {
sl.registerFactory(() => OrderBloc(sl()));
sl.registerLazySingleton(() => GetOrderUseCase(sl()));
// OrderDetails
sl.registerFactory(() => OrderDetailBloc(sl()));
sl.registerLazySingleton(() => GetRoutesUseCase(sl()));
// Order Repository and Data Sources
sl.registerLazySingleton<OrderRepository>(
() => OrderRepositoryImpl(

View File

@ -3,3 +3,5 @@ export 'order/order.dart';
export 'order/filter_params_model.dart';
export 'order/order_response.dart';
export 'order/pagination_meta_data.dart';
export 'route/route.dart';
export 'route/route_response.dart';

View File

@ -0,0 +1,18 @@
import 'package:equatable/equatable.dart';
class RouteEntity extends Equatable {
final String name;
final double lat;
final double long;
final bool isCurrent;
const RouteEntity({
required this.name,
required this.lat,
required this.long,
required this.isCurrent,
});
@override
List<Object?> get props => [name, lat, long, isCurrent];
}

View File

@ -0,0 +1,7 @@
import 'route.dart';
class RouteResponse {
final List<RouteEntity> routes;
RouteResponse({required this.routes});
}

View File

@ -2,8 +2,10 @@ import 'package:dartz/dartz.dart';
import '../../core/errors/failures.dart';
import '../../data/models/order/order_response_model.dart';
import '../../data/models/route/route_response_model.dart';
import '../domain.dart';
abstract class OrderRepository {
Future<Either<Failure, OrderResponseModel>> getOrders(FilterProductParams params);
Future<Either<Failure, RouteResponseModel>> getRoutes(String cargoId);
}

View File

@ -0,0 +1,15 @@
import 'package:dartz/dartz.dart';
import '../../../core/core.dart';
import '../../../data/models/route/route_response_model.dart';
import '../../domain.dart';
class GetRoutesUseCase implements UseCase<RouteResponseModel, String> {
final OrderRepository repository;
GetRoutesUseCase(this.repository);
@override
Future<Either<Failure, RouteResponseModel>> call(String cargoId) async {
return await repository.getRoutes(cargoId);
}
}

View File

@ -1 +1,2 @@
export 'get_orders_usecase.dart';
export 'get_routes_usecase.dart';

View File

@ -19,7 +19,7 @@ class _LoginScreenState extends State<LoginScreen> {
final TextEditingController _userNameController = TextEditingController();
final TextEditingController _passwordController = TextEditingController();
final _formKey = GlobalKey<FormState>();
bool _obscureText = false;
bool _obscureText = true;
void _nextScreen() {
Navigator.of(context).pushNamedAndRemoveUntil(

View File

@ -1,12 +1,27 @@
import 'package:cargo/application/application.dart';
import 'package:cargo/configs/configs.dart';
import 'package:cargo/domain/domain.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../core/core.dart';
import '../widgets/map/clustering.dart';
import '../widgets/widgets.dart';
class OrderDetailsScreen extends StatelessWidget {
const OrderDetailsScreen({super.key});
class OrderDetailsScreen extends StatefulWidget {
final OrderEntity order;
const OrderDetailsScreen({super.key, required this.order});
@override
State<OrderDetailsScreen> createState() => _OrderDetailsScreenState();
}
class _OrderDetailsScreenState extends State<OrderDetailsScreen> {
@override
void initState() {
context.read<OrderDetailBloc>().add(GetRoutes(widget.order.cargoId));
super.initState();
}
@override
Widget build(BuildContext context) {
@ -71,7 +86,7 @@ class OrderDetailsScreen extends StatelessWidget {
Space.y!,
/// location card
const LocationCard()
LocationCard(cargoId: widget.order.cargoId)
],
),
),

View File

@ -13,14 +13,10 @@ class OrdersScreen extends StatefulWidget {
State<OrdersScreen> createState() => _OrdersScreenState();
}
class _OrdersScreenState extends State<OrdersScreen> {
@override
void initState() {
super.initState();
}
class _OrdersScreenState extends State<OrdersScreen> with AutomaticKeepAliveClientMixin<OrdersScreen> {
@override
Widget build(BuildContext context) {
super.build(context);
App.init(context);
return Scaffold(
@ -106,4 +102,7 @@ class _OrdersScreenState extends State<OrdersScreen> {
),
);
}
@override
bool get wantKeepAlive => true;
}

View File

@ -1,14 +1,27 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../application/application.dart';
import '../../configs/configs.dart';
import '../../core/core.dart';
import 'dashed_line.dart';
import 'retry_widget.dart';
class LocationCard extends StatelessWidget {
const LocationCard({super.key});
final String cargoId;
const LocationCard({super.key, required this.cargoId});
@override
Widget build(BuildContext context) {
return BlocBuilder<OrderDetailBloc, OrderDetailState>(
builder: (context, state) {
if (state is RoutesLoading) {
return const Center(
child: CircularProgressIndicator(),
);
} else if (state is RoutesLoaded) {
final routes = state.routes;
return SizedBox(
width: double.infinity,
child: Card(
@ -19,15 +32,16 @@ class LocationCard extends StatelessWidget {
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) {
final route = routes[index];
return Row(
children: [
index % 2 == 0
route.isCurrent
? const Icon(
Icons.radio_button_checked,
color: AppColors.grey,
)
: Container(
margin: Space.hf(0.35), //const EdgeInsets.only(left: 6),
margin: Space.hf(0.35),
height: AppDimensions.normalize(4.5),
width: AppDimensions.normalize(4.5),
decoration: const BoxDecoration(
@ -37,18 +51,18 @@ class LocationCard extends StatelessWidget {
),
Space.x!,
Text(
routess[index].city,
routes[index].name,
style: AppText.b1b,
),
const Spacer(),
Text(
routess[index].date,
'30.02.2024', // routes[index].date,
style: AppText.b1!.copyWith(color: AppColors.grey),
),
],
);
},
itemCount: routess.length,
itemCount: routes.length,
separatorBuilder: (BuildContext context, int index) {
return Padding(
padding: Space.vf(0.5),
@ -66,20 +80,16 @@ class LocationCard extends StatelessWidget {
),
),
);
} else if (state is RoutesError) {
return Center(
child: RetryWidget(onRetry: () {
context.read<OrderDetailBloc>().add(GetRoutes(cargoId));
}),
);
} else {
return const SizedBox.shrink();
}
},
);
}
}
class Path {
final String city;
final String date;
Path(this.city, this.date);
}
List<Path> routess = [
Path('Urumchi', '10.07.2024'),
Path('Astana', '14.07.2024'),
Path('Tashkent', '16.07.2024'),
Path('Samarkant', '25.07.2024'),
Path('Mary', '30.07.2024'),
];

View File

@ -30,7 +30,10 @@ class OrderCard extends StatelessWidget {
),
GestureDetector(
onTap: () {
Navigator.of(context).pushNamed(AppRouter.orderDetails);
Navigator.of(context).pushNamed(
AppRouter.orderDetails,
arguments: order,
);
},
child: Text(
'Ginişleýin >',