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 'splash_cubit/splash_cubit.dart';
export 'user_bloc/user_bloc.dart'; export 'user_bloc/user_bloc.dart';
export 'order_bloc/order_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); on<GetMoreOrders>(_onLoadMoreOrders);
} }
FutureOr<void> _onGetOrders( FutureOr<void> _onGetOrders(GetOrders event, Emitter<OrderState> emit) async {
GetOrders event,
Emitter<OrderState> emit,
) async {
try { try {
emit(OrderLoading( emit(OrderLoading(
orders: const [], 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 state = this.state;
var limit = state.metaData.limit; var limit = state.metaData.limit;
var total = state.metaData.total; 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( BlocProvider(
create: (context) => di.sl<OrderBloc>()..add(const GetOrders(FilterProductParams())), create: (context) => di.sl<OrderBloc>()..add(const GetOrders(FilterProductParams())),
), ),
BlocProvider(
create: (context) => di.sl<OrderDetailBloc>(),
),
], ],
child: MaterialApp( child: MaterialApp(
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,

View File

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

View File

@ -3,9 +3,11 @@ import 'package:http/http.dart' as http;
import '../../../core/core.dart'; import '../../../core/core.dart';
import '../../../domain/domain.dart'; import '../../../domain/domain.dart';
import '../../data.dart'; import '../../data.dart';
import '../../models/route/route_response_model.dart';
abstract class OrderRemoteDataSource { abstract class OrderRemoteDataSource {
Future<OrderResponseModel> getOrders(FilterProductParams params, String token); Future<OrderResponseModel> getOrders(FilterProductParams params, String token);
Future<RouteResponseModel> getRoutes(String orderId);
} }
class OrderRemoteDataSourceImpl implements OrderRemoteDataSource { class OrderRemoteDataSourceImpl implements OrderRemoteDataSource {
@ -29,4 +31,21 @@ class OrderRemoteDataSourceImpl implements OrderRemoteDataSource {
throw ServerException(); 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 'user/user_model.dart';
export 'order/order_model.dart'; export 'order/order_model.dart';
export 'order/order_response_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 'package:dartz/dartz.dart';
import '../../core/core.dart'; import '../../core/core.dart';
@ -34,4 +35,18 @@ class OrderRepositoryImpl extends OrderRepository {
return Left(failure); 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.registerFactory(() => OrderBloc(sl()));
sl.registerLazySingleton(() => GetOrderUseCase(sl())); sl.registerLazySingleton(() => GetOrderUseCase(sl()));
// OrderDetails
sl.registerFactory(() => OrderDetailBloc(sl()));
sl.registerLazySingleton(() => GetRoutesUseCase(sl()));
// Order Repository and Data Sources // Order Repository and Data Sources
sl.registerLazySingleton<OrderRepository>( sl.registerLazySingleton<OrderRepository>(
() => OrderRepositoryImpl( () => OrderRepositoryImpl(

View File

@ -3,3 +3,5 @@ export 'order/order.dart';
export 'order/filter_params_model.dart'; export 'order/filter_params_model.dart';
export 'order/order_response.dart'; export 'order/order_response.dart';
export 'order/pagination_meta_data.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 '../../core/errors/failures.dart';
import '../../data/models/order/order_response_model.dart'; import '../../data/models/order/order_response_model.dart';
import '../../data/models/route/route_response_model.dart';
import '../domain.dart'; import '../domain.dart';
abstract class OrderRepository { abstract class OrderRepository {
Future<Either<Failure, OrderResponseModel>> getOrders(FilterProductParams params); 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_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 _userNameController = TextEditingController();
final TextEditingController _passwordController = TextEditingController(); final TextEditingController _passwordController = TextEditingController();
final _formKey = GlobalKey<FormState>(); final _formKey = GlobalKey<FormState>();
bool _obscureText = false; bool _obscureText = true;
void _nextScreen() { void _nextScreen() {
Navigator.of(context).pushNamedAndRemoveUntil( 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/configs/configs.dart';
import 'package:cargo/domain/domain.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../core/core.dart'; import '../../core/core.dart';
import '../widgets/map/clustering.dart'; import '../widgets/map/clustering.dart';
import '../widgets/widgets.dart'; import '../widgets/widgets.dart';
class OrderDetailsScreen extends StatelessWidget { class OrderDetailsScreen extends StatefulWidget {
const OrderDetailsScreen({super.key}); 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -71,7 +86,7 @@ class OrderDetailsScreen extends StatelessWidget {
Space.y!, Space.y!,
/// location card /// location card
const LocationCard() LocationCard(cargoId: widget.order.cargoId)
], ],
), ),
), ),

View File

@ -13,14 +13,10 @@ class OrdersScreen extends StatefulWidget {
State<OrdersScreen> createState() => _OrdersScreenState(); State<OrdersScreen> createState() => _OrdersScreenState();
} }
class _OrdersScreenState extends State<OrdersScreen> { class _OrdersScreenState extends State<OrdersScreen> with AutomaticKeepAliveClientMixin<OrdersScreen> {
@override
void initState() {
super.initState();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
super.build(context);
App.init(context); App.init(context);
return Scaffold( 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/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../application/application.dart';
import '../../configs/configs.dart'; import '../../configs/configs.dart';
import '../../core/core.dart'; import '../../core/core.dart';
import 'dashed_line.dart'; import 'dashed_line.dart';
import 'retry_widget.dart';
class LocationCard extends StatelessWidget { class LocationCard extends StatelessWidget {
const LocationCard({super.key}); final String cargoId;
const LocationCard({super.key, required this.cargoId});
@override @override
Widget build(BuildContext context) { 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( return SizedBox(
width: double.infinity, width: double.infinity,
child: Card( child: Card(
@ -19,15 +32,16 @@ class LocationCard extends StatelessWidget {
shrinkWrap: true, shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) { itemBuilder: (context, index) {
final route = routes[index];
return Row( return Row(
children: [ children: [
index % 2 == 0 route.isCurrent
? const Icon( ? const Icon(
Icons.radio_button_checked, Icons.radio_button_checked,
color: AppColors.grey, color: AppColors.grey,
) )
: Container( : Container(
margin: Space.hf(0.35), //const EdgeInsets.only(left: 6), margin: Space.hf(0.35),
height: AppDimensions.normalize(4.5), height: AppDimensions.normalize(4.5),
width: AppDimensions.normalize(4.5), width: AppDimensions.normalize(4.5),
decoration: const BoxDecoration( decoration: const BoxDecoration(
@ -37,18 +51,18 @@ class LocationCard extends StatelessWidget {
), ),
Space.x!, Space.x!,
Text( Text(
routess[index].city, routes[index].name,
style: AppText.b1b, style: AppText.b1b,
), ),
const Spacer(), const Spacer(),
Text( Text(
routess[index].date, '30.02.2024', // routes[index].date,
style: AppText.b1!.copyWith(color: AppColors.grey), style: AppText.b1!.copyWith(color: AppColors.grey),
), ),
], ],
); );
}, },
itemCount: routess.length, itemCount: routes.length,
separatorBuilder: (BuildContext context, int index) { separatorBuilder: (BuildContext context, int index) {
return Padding( return Padding(
padding: Space.vf(0.5), 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( GestureDetector(
onTap: () { onTap: () {
Navigator.of(context).pushNamed(AppRouter.orderDetails); Navigator.of(context).pushNamed(
AppRouter.orderDetails,
arguments: order,
);
}, },
child: Text( child: Text(
'Ginişleýin >', 'Ginişleýin >',