diff --git a/.vscode/settings.json b/.vscode/settings.json index 91d33fd..7d1562f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,6 @@ { - "dart.flutterSdkPath": ".fvm/versions/3.22.1" + "dart.flutterSdkPath": ".fvm/versions/3.22.1", + "cSpell.words": [ + "dartz" + ] } \ No newline at end of file diff --git a/lib/application/application.dart b/lib/application/application.dart index 915b50a..e317cd4 100644 --- a/lib/application/application.dart +++ b/lib/application/application.dart @@ -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'; diff --git a/lib/application/order_bloc/order_bloc.dart b/lib/application/order_bloc/order_bloc.dart index 5f90509..7c7ead6 100644 --- a/lib/application/order_bloc/order_bloc.dart +++ b/lib/application/order_bloc/order_bloc.dart @@ -27,10 +27,7 @@ class OrderBloc extends Bloc { on(_onLoadMoreOrders); } - FutureOr _onGetOrders( - GetOrders event, - Emitter emit, - ) async { + FutureOr _onGetOrders(GetOrders event, Emitter emit) async { try { emit(OrderLoading( orders: const [], @@ -61,7 +58,7 @@ class OrderBloc extends Bloc { } } - void _onLoadMoreOrders(GetMoreOrders event, Emitter emit) async { + Future _onLoadMoreOrders(GetMoreOrders event, Emitter emit) async { var state = this.state; var limit = state.metaData.limit; var total = state.metaData.total; diff --git a/lib/application/order_detail_bloc/order_detail_bloc.dart b/lib/application/order_detail_bloc/order_detail_bloc.dart new file mode 100644 index 0000000..6f76d3a --- /dev/null +++ b/lib/application/order_detail_bloc/order_detail_bloc.dart @@ -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 { + final GetRoutesUseCase _getRoutesUseCase; + + OrderDetailBloc(this._getRoutesUseCase) : super(OrderDetailInitial()) { + on(_onGetRoutes); + } + + FutureOr _onGetRoutes(GetRoutes event, Emitter 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())); + } + } +} diff --git a/lib/application/order_detail_bloc/order_detail_event.dart b/lib/application/order_detail_bloc/order_detail_event.dart new file mode 100644 index 0000000..e175a98 --- /dev/null +++ b/lib/application/order_detail_bloc/order_detail_event.dart @@ -0,0 +1,16 @@ +part of 'order_detail_bloc.dart'; + +sealed class OrderDetailEvent extends Equatable { + const OrderDetailEvent(); + + @override + List get props => []; +} + +class GetRoutes extends OrderDetailEvent { + final String cargoId; + const GetRoutes(this.cargoId); + + @override + List get props => []; +} diff --git a/lib/application/order_detail_bloc/order_detail_state.dart b/lib/application/order_detail_bloc/order_detail_state.dart new file mode 100644 index 0000000..e3eeaf6 --- /dev/null +++ b/lib/application/order_detail_bloc/order_detail_state.dart @@ -0,0 +1,29 @@ +part of 'order_detail_bloc.dart'; + +sealed class OrderDetailState extends Equatable { + const OrderDetailState(); + + @override + List get props => []; +} + +class OrderDetailInitial extends OrderDetailState {} + +class RoutesLoading extends OrderDetailState {} + +class RoutesLoaded extends OrderDetailState { + final List routes; + const RoutesLoaded({required this.routes}); + @override + List get props => [routes]; +} + +class RoutesError extends OrderDetailState { + final Failure failure; + const RoutesError({ + required this.failure, + }); + + @override + List get props => []; +} diff --git a/lib/core/app/app.dart b/lib/core/app/app.dart index 7bf0431..77eb684 100644 --- a/lib/core/app/app.dart +++ b/lib/core/app/app.dart @@ -27,6 +27,9 @@ class MyApp extends StatelessWidget { BlocProvider( create: (context) => di.sl()..add(const GetOrders(FilterProductParams())), ), + BlocProvider( + create: (context) => di.sl(), + ), ], child: MaterialApp( debugShowCheckedModeBanner: false, diff --git a/lib/core/router/app_router.dart b/lib/core/router/app_router.dart index ca500bd..c42f5ac 100644 --- a/lib/core/router/app_router.dart +++ b/lib/core/router/app_router.dart @@ -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!'); diff --git a/lib/data/data_sources/remote/order_remote_data_source.dart b/lib/data/data_sources/remote/order_remote_data_source.dart index 5a2618c..9041d7f 100644 --- a/lib/data/data_sources/remote/order_remote_data_source.dart +++ b/lib/data/data_sources/remote/order_remote_data_source.dart @@ -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 getOrders(FilterProductParams params, String token); + Future getRoutes(String orderId); } class OrderRemoteDataSourceImpl implements OrderRemoteDataSource { @@ -29,4 +31,21 @@ class OrderRemoteDataSourceImpl implements OrderRemoteDataSource { throw ServerException(); } } + + @override + Future 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(); + } + } } diff --git a/lib/data/models/models.dart b/lib/data/models/models.dart index c612787..27ac758 100644 --- a/lib/data/models/models.dart +++ b/lib/data/models/models.dart @@ -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'; diff --git a/lib/data/models/route/route_model.dart b/lib/data/models/route/route_model.dart new file mode 100644 index 0000000..eec9a1f --- /dev/null +++ b/lib/data/models/route/route_model.dart @@ -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 json) { + return RouteModel( + name: json['Name'], + lat: json['Lat'] + .0, + long: json['Long'] + .0, + isCurrent: json['IsCurrent'], + ); + } + + Map toJson() { + return { + 'Name': name, + 'Lat': lat, + 'Long': long, + 'IsCurrent': isCurrent, + }; + } +} diff --git a/lib/data/models/route/route_response_model.dart b/lib/data/models/route/route_response_model.dart new file mode 100644 index 0000000..1bb92a4 --- /dev/null +++ b/lib/data/models/route/route_response_model.dart @@ -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 jsonList) { + return RouteResponseModel( + routes: jsonList.map((json) => RouteModel.fromJson(json)).toList(), + ); + } +} diff --git a/lib/data/repositories/order_repository_impl.dart b/lib/data/repositories/order_repository_impl.dart index 3410bc3..0c0d4a7 100644 --- a/lib/data/repositories/order_repository_impl.dart +++ b/lib/data/repositories/order_repository_impl.dart @@ -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> 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); + } + } } diff --git a/lib/di/order.dart b/lib/di/order.dart index 68203ef..385dda0 100644 --- a/lib/di/order.dart +++ b/lib/di/order.dart @@ -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( () => OrderRepositoryImpl( diff --git a/lib/domain/entities/entities.dart b/lib/domain/entities/entities.dart index b554eae..fa01f45 100644 --- a/lib/domain/entities/entities.dart +++ b/lib/domain/entities/entities.dart @@ -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'; diff --git a/lib/domain/entities/route/route.dart b/lib/domain/entities/route/route.dart new file mode 100644 index 0000000..b3fcc00 --- /dev/null +++ b/lib/domain/entities/route/route.dart @@ -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 get props => [name, lat, long, isCurrent]; +} diff --git a/lib/domain/entities/route/route_response.dart b/lib/domain/entities/route/route_response.dart new file mode 100644 index 0000000..1baf19d --- /dev/null +++ b/lib/domain/entities/route/route_response.dart @@ -0,0 +1,7 @@ +import 'route.dart'; + +class RouteResponse { + final List routes; + + RouteResponse({required this.routes}); +} diff --git a/lib/domain/repositories/order_repository.dart b/lib/domain/repositories/order_repository.dart index 861043d..f469b96 100644 --- a/lib/domain/repositories/order_repository.dart +++ b/lib/domain/repositories/order_repository.dart @@ -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> getOrders(FilterProductParams params); + Future> getRoutes(String cargoId); } diff --git a/lib/domain/usecases/order/get_routes_usecase.dart b/lib/domain/usecases/order/get_routes_usecase.dart new file mode 100644 index 0000000..ebf96f0 --- /dev/null +++ b/lib/domain/usecases/order/get_routes_usecase.dart @@ -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 { + final OrderRepository repository; + GetRoutesUseCase(this.repository); + + @override + Future> call(String cargoId) async { + return await repository.getRoutes(cargoId); + } +} diff --git a/lib/domain/usecases/order/order.dart b/lib/domain/usecases/order/order.dart index 814da3b..4f0a226 100644 --- a/lib/domain/usecases/order/order.dart +++ b/lib/domain/usecases/order/order.dart @@ -1 +1,2 @@ export 'get_orders_usecase.dart'; +export 'get_routes_usecase.dart'; diff --git a/lib/presentation/screens/login.dart b/lib/presentation/screens/login.dart index 2e43fff..c48f7af 100644 --- a/lib/presentation/screens/login.dart +++ b/lib/presentation/screens/login.dart @@ -19,7 +19,7 @@ class _LoginScreenState extends State { final TextEditingController _userNameController = TextEditingController(); final TextEditingController _passwordController = TextEditingController(); final _formKey = GlobalKey(); - bool _obscureText = false; + bool _obscureText = true; void _nextScreen() { Navigator.of(context).pushNamedAndRemoveUntil( diff --git a/lib/presentation/screens/order_details.dart b/lib/presentation/screens/order_details.dart index 1596043..2776872 100644 --- a/lib/presentation/screens/order_details.dart +++ b/lib/presentation/screens/order_details.dart @@ -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 createState() => _OrderDetailsScreenState(); +} + +class _OrderDetailsScreenState extends State { + @override + void initState() { + context.read().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) ], ), ), diff --git a/lib/presentation/screens/orders.dart b/lib/presentation/screens/orders.dart index eb85748..a319b69 100644 --- a/lib/presentation/screens/orders.dart +++ b/lib/presentation/screens/orders.dart @@ -13,14 +13,10 @@ class OrdersScreen extends StatefulWidget { State createState() => _OrdersScreenState(); } -class _OrdersScreenState extends State { - @override - void initState() { - super.initState(); - } - +class _OrdersScreenState extends State with AutomaticKeepAliveClientMixin { @override Widget build(BuildContext context) { + super.build(context); App.init(context); return Scaffold( @@ -106,4 +102,7 @@ class _OrdersScreenState extends State { ), ); } + + @override + bool get wantKeepAlive => true; } diff --git a/lib/presentation/widgets/location_card.dart b/lib/presentation/widgets/location_card.dart index 620edf3..0794fbd 100644 --- a/lib/presentation/widgets/location_card.dart +++ b/lib/presentation/widgets/location_card.dart @@ -1,85 +1,95 @@ 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 SizedBox( - width: double.infinity, - child: Card( - color: Colors.white, - child: Padding( - padding: const EdgeInsets.all(16.0), - child: ListView.separated( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemBuilder: (context, index) { - return Row( - children: [ - index % 2 == 0 - ? const Icon( - Icons.radio_button_checked, - color: AppColors.grey, - ) - : Container( - margin: Space.hf(0.35), //const EdgeInsets.only(left: 6), - height: AppDimensions.normalize(4.5), - width: AppDimensions.normalize(4.5), - decoration: const BoxDecoration( - color: AppColors.grey, - shape: BoxShape.circle, - ), + return BlocBuilder( + 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( + color: Colors.white, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: ListView.separated( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemBuilder: (context, index) { + final route = routes[index]; + return Row( + children: [ + route.isCurrent + ? const Icon( + Icons.radio_button_checked, + color: AppColors.grey, + ) + : Container( + margin: Space.hf(0.35), + height: AppDimensions.normalize(4.5), + width: AppDimensions.normalize(4.5), + decoration: const BoxDecoration( + color: AppColors.grey, + shape: BoxShape.circle, + ), + ), + Space.x!, + Text( + routes[index].name, + style: AppText.b1b, ), - Space.x!, - Text( - routess[index].city, - style: AppText.b1b, - ), - const Spacer(), - Text( - routess[index].date, - style: AppText.b1!.copyWith(color: AppColors.grey), - ), - ], - ); - }, - itemCount: routess.length, - separatorBuilder: (BuildContext context, int index) { - return Padding( - padding: Space.vf(0.5), - child: DashedLine( - height: 1, - width: AppDimensions.normalize(8), - color: AppColors.lightGrey, - strokeWidth: 1, - dashWidth: 5, - dashSpace: AppDimensions.normalize(2), + const Spacer(), + Text( + '30.02.2024', // routes[index].date, + style: AppText.b1!.copyWith(color: AppColors.grey), + ), + ], + ); + }, + itemCount: routes.length, + separatorBuilder: (BuildContext context, int index) { + return Padding( + padding: Space.vf(0.5), + child: DashedLine( + height: 1, + width: AppDimensions.normalize(8), + color: AppColors.lightGrey, + strokeWidth: 1, + dashWidth: 5, + dashSpace: AppDimensions.normalize(2), + ), + ); + }, ), - ); - }, - ), - ), - ), + ), + ), + ); + } else if (state is RoutesError) { + return Center( + child: RetryWidget(onRetry: () { + context.read().add(GetRoutes(cargoId)); + }), + ); + } else { + return const SizedBox.shrink(); + } + }, ); } } - -class Path { - final String city; - final String date; - - Path(this.city, this.date); -} - -List 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'), -]; diff --git a/lib/presentation/widgets/order_card.dart b/lib/presentation/widgets/order_card.dart index db06c71..3d4d79a 100644 --- a/lib/presentation/widgets/order_card.dart +++ b/lib/presentation/widgets/order_card.dart @@ -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 >',