Initial commit
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
import 'package:cargocalculaterapp/features/calculator/data/model/calculate_price_response.dart';
|
||||
import 'package:cargocalculaterapp/features/calculator/data/model/lead_create_response.dart';
|
||||
import 'package:cargocalculaterapp/features/calculator/data/model/lead_create_request.dart';
|
||||
import 'package:cargocalculaterapp/features/calculator/data/model/price_calculate_request.dart';
|
||||
|
||||
abstract class CalculatorRemoteDataSource {
|
||||
Future<CalculatePriceResponse> priceCalculate(PriceCalculateRequest request);
|
||||
Future<LeadCreateResponse> createLead(LeadCreateRequest request);
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import 'package:cargocalculaterapp/features/calculator/data/model/calculate_price_response.dart';
|
||||
import 'package:cargocalculaterapp/features/calculator/data/model/price_calculate_request.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import '../../../../constants/constants.dart';
|
||||
import '../../../../core/error/exceptions.dart';
|
||||
import '../../../../core/local_source/local_source.dart';
|
||||
import '../../../../injector_container.dart';
|
||||
import '../model/lead_create_response.dart';
|
||||
import '../model/lead_create_request.dart';
|
||||
import 'calculator_remote_data_source.dart';
|
||||
|
||||
class CalculatorRemoteDataSourceImpl extends CalculatorRemoteDataSource {
|
||||
final Dio dio;
|
||||
|
||||
CalculatorRemoteDataSourceImpl(this.dio);
|
||||
|
||||
@override
|
||||
Future<CalculatePriceResponse> priceCalculate(
|
||||
PriceCalculateRequest request,
|
||||
) async {
|
||||
try {
|
||||
final Response response = await dio.post(
|
||||
Constants.baseUrl + Urls.calculatePrice,
|
||||
options:
|
||||
DioConstants.options
|
||||
..headers = {
|
||||
"Authorization": "Bearer ${sl<LocalSource>().getAccessToken()}",
|
||||
},
|
||||
data: request,
|
||||
);
|
||||
if (response.statusCode == 200 || response.statusCode == 201) {
|
||||
return CalculatePriceResponse.fromJson(response.data);
|
||||
}
|
||||
throw ServerException.fromJson(response.data);
|
||||
} on DioException catch (e) {
|
||||
throw ServerException.fromJson(e.response?.data);
|
||||
} on FormatException {
|
||||
throw ServerException(message: Validations.someThingWentWrong);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<LeadCreateResponse> createLead(LeadCreateRequest request) async {
|
||||
try {
|
||||
final Response response = await dio.post(
|
||||
Constants.baseUrl + Urls.lead,
|
||||
options:
|
||||
DioConstants.options
|
||||
..headers = {
|
||||
"Authorization": "Bearer ${sl<LocalSource>().getAccessToken()}",
|
||||
},
|
||||
data: request,
|
||||
);
|
||||
if (response.statusCode == 200 || response.statusCode == 201) {
|
||||
return LeadCreateResponse.fromJson(response.data);
|
||||
}
|
||||
throw ServerException.fromJson(response.data);
|
||||
} on DioException catch (e) {
|
||||
throw ServerException.fromJson(e.response?.data);
|
||||
} on FormatException {
|
||||
throw ServerException(message: Validations.someThingWentWrong);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
class CalculatePriceResponse {
|
||||
CalculatePriceResponse({this.price});
|
||||
|
||||
CalculatePriceResponse.fromJson(dynamic json) {
|
||||
price = double.tryParse(json['price'].toString());
|
||||
}
|
||||
|
||||
double? price;
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final map = <String, dynamic>{};
|
||||
map['price'] = price;
|
||||
return map;
|
||||
}
|
||||
}
|
||||
73
lib/features/calculator/data/model/lead_create_request.dart
Normal file
73
lib/features/calculator/data/model/lead_create_request.dart
Normal file
@@ -0,0 +1,73 @@
|
||||
class LeadCreateRequest {
|
||||
LeadCreateRequest({
|
||||
this.warehouseCode,
|
||||
this.wharehouseUz,
|
||||
this.wharehouseRu,
|
||||
this.wharehouseZh,
|
||||
this.nameUz,
|
||||
this.nameRu,
|
||||
this.nameZh,
|
||||
this.userUcode,
|
||||
this.phoneNumber,
|
||||
this.gmail,
|
||||
this.weight,
|
||||
this.price,
|
||||
this.status,
|
||||
this.averageWeightKg,
|
||||
this.m3,
|
||||
});
|
||||
|
||||
LeadCreateRequest.fromJson(dynamic json) {
|
||||
warehouseCode = json['warehouse_code'];
|
||||
wharehouseUz = json['wharehouseUz'];
|
||||
wharehouseRu = json['wharehouseRu'];
|
||||
wharehouseZh = json['wharehouseZh'];
|
||||
nameUz = json['nameUz'];
|
||||
nameRu = json['nameRu'];
|
||||
nameZh = json['nameZh'];
|
||||
userUcode = json['user_ucode'];
|
||||
phoneNumber = json['phone_number'];
|
||||
gmail = json['gmail'];
|
||||
weight = json['weight'];
|
||||
price = json['price'];
|
||||
status = json['status'];
|
||||
averageWeightKg = json['average_weight_kg'];
|
||||
m3 = json['status'];
|
||||
}
|
||||
|
||||
int? warehouseCode;
|
||||
String? wharehouseUz;
|
||||
String? wharehouseRu;
|
||||
String? wharehouseZh;
|
||||
String? nameUz;
|
||||
String? nameRu;
|
||||
String? nameZh;
|
||||
String? userUcode;
|
||||
String? phoneNumber;
|
||||
String? gmail;
|
||||
String? weight;
|
||||
String? price;
|
||||
String? status;
|
||||
String? averageWeightKg;
|
||||
String? m3;
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final map = <String, dynamic>{};
|
||||
map['warehouse_code'] = warehouseCode;
|
||||
map['wharehouseUz'] = wharehouseUz;
|
||||
map['wharehouseRu'] = wharehouseRu;
|
||||
map['wharehouseZh'] = wharehouseZh;
|
||||
map['nameUz'] = nameUz;
|
||||
map['nameRu'] = nameRu;
|
||||
map['nameZh'] = nameZh;
|
||||
map['user_ucode'] = userUcode;
|
||||
map['phone_number'] = phoneNumber;
|
||||
map['gmail'] = gmail;
|
||||
map['weight'] = weight;
|
||||
map['price'] = price;
|
||||
map['status'] = status;
|
||||
map['average_weight_kg'] = averageWeightKg;
|
||||
map['m3'] = m3;
|
||||
return map;
|
||||
}
|
||||
}
|
||||
118
lib/features/calculator/data/model/lead_create_response.dart
Normal file
118
lib/features/calculator/data/model/lead_create_response.dart
Normal file
@@ -0,0 +1,118 @@
|
||||
class LeadCreateResponse {
|
||||
LeadCreateResponse({
|
||||
this.userUcode,
|
||||
this.phoneNumber,
|
||||
this.gmail,
|
||||
this.weight,
|
||||
this.status,
|
||||
this.nameUz,
|
||||
this.nameRu,
|
||||
this.nameZh,
|
||||
this.wharehouseUz,
|
||||
this.wharehouseRu,
|
||||
this.wharehouseZh,
|
||||
this.id,
|
||||
this.createdAt,
|
||||
this.updatedAt,
|
||||
});
|
||||
|
||||
LeadCreateResponse.fromJson(dynamic json) {
|
||||
userUcode = json['user_ucode'];
|
||||
phoneNumber = json['phone_number'];
|
||||
gmail = json['gmail'];
|
||||
weight = json['weight'];
|
||||
status = json['status'] != null ? Status.fromJson(json['status']) : null;
|
||||
nameUz = json['nameUz'];
|
||||
nameRu = json['nameRu'];
|
||||
nameZh = json['nameZh'];
|
||||
wharehouseUz = json['wharehouseUz'];
|
||||
wharehouseRu = json['wharehouseRu'];
|
||||
wharehouseZh = json['wharehouseZh'];
|
||||
createdAt = json['createdAt'];
|
||||
updatedAt = json['updatedAt'];
|
||||
id = json['id'];
|
||||
}
|
||||
|
||||
String? userUcode;
|
||||
String? phoneNumber;
|
||||
String? gmail;
|
||||
String? weight;
|
||||
Status? status;
|
||||
String? nameUz;
|
||||
String? nameRu;
|
||||
String? nameZh;
|
||||
String? wharehouseUz;
|
||||
String? wharehouseRu;
|
||||
String? wharehouseZh;
|
||||
String? id;
|
||||
String? createdAt;
|
||||
String? updatedAt;
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final map = <String, dynamic>{};
|
||||
map['user_ucode'] = userUcode;
|
||||
map['phone_number'] = phoneNumber;
|
||||
map['gmail'] = gmail;
|
||||
map['weight'] = weight;
|
||||
if (status != null) {
|
||||
map['status'] = status?.toJson();
|
||||
}
|
||||
map['nameUz'] = nameUz;
|
||||
map['nameRu'] = nameRu;
|
||||
map['nameZh'] = nameZh;
|
||||
map['wharehouseUz'] = wharehouseUz;
|
||||
map['wharehouseRu'] = wharehouseRu;
|
||||
map['wharehouseZh'] = wharehouseZh;
|
||||
map['createdAt'] = createdAt;
|
||||
map['updatedAt'] = updatedAt;
|
||||
|
||||
map['id'] = id;
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
||||
class Status {
|
||||
Status({this.code, this.translations});
|
||||
|
||||
Status.fromJson(dynamic json) {
|
||||
code = json['code'];
|
||||
translations =
|
||||
json['translations'] != null
|
||||
? Translations.fromJson(json['translations'])
|
||||
: null;
|
||||
}
|
||||
|
||||
String? code;
|
||||
Translations? translations;
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final map = <String, dynamic>{};
|
||||
map['code'] = code;
|
||||
if (translations != null) {
|
||||
map['translations'] = translations?.toJson();
|
||||
}
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
||||
class Translations {
|
||||
Translations({this.uz, this.ru, this.zh});
|
||||
|
||||
Translations.fromJson(dynamic json) {
|
||||
uz = json['uz'];
|
||||
ru = json['ru'];
|
||||
zh = json['zh'];
|
||||
}
|
||||
|
||||
String? uz;
|
||||
String? ru;
|
||||
String? zh;
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final map = <String, dynamic>{};
|
||||
map['uz'] = uz;
|
||||
map['ru'] = ru;
|
||||
map['zh'] = zh;
|
||||
return map;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
class PriceCalculateRequest {
|
||||
PriceCalculateRequest({
|
||||
this.avg,
|
||||
this.m3,});
|
||||
|
||||
PriceCalculateRequest.fromJson(dynamic json) {
|
||||
avg = json['avg'];
|
||||
m3 = json['m3'];
|
||||
}
|
||||
double? avg;
|
||||
double? m3;
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final map = <String, dynamic>{};
|
||||
map['avg'] = avg;
|
||||
map['m3'] = m3;
|
||||
return map;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import 'package:cargocalculaterapp/core/error/failure.dart';
|
||||
import 'package:cargocalculaterapp/features/calculator/data/model/calculate_price_response.dart';
|
||||
import 'package:cargocalculaterapp/features/calculator/data/model/lead_create_response.dart';
|
||||
import 'package:cargocalculaterapp/features/calculator/data/model/lead_create_request.dart';
|
||||
import 'package:cargocalculaterapp/features/calculator/data/model/price_calculate_request.dart';
|
||||
import 'package:cargocalculaterapp/features/calculator/domain/repository/calculator_repository.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
|
||||
import '../data_source/calculator_remote_data_source.dart';
|
||||
|
||||
class CalculatorRepositoryImpl extends CalculatorRepository {
|
||||
final CalculatorRemoteDataSource calculatorRemoteDataSource;
|
||||
|
||||
CalculatorRepositoryImpl(this.calculatorRemoteDataSource);
|
||||
|
||||
@override
|
||||
Future<Either<Failure, CalculatePriceResponse>> calculatePrice(
|
||||
PriceCalculateRequest request,
|
||||
) async {
|
||||
try {
|
||||
final response = await calculatorRemoteDataSource.priceCalculate(request);
|
||||
return Right(response);
|
||||
} catch (e) {
|
||||
return Left(ServerFailure(message: e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, LeadCreateResponse>> createLead(LeadCreateRequest request) async {
|
||||
try {
|
||||
final response = await calculatorRemoteDataSource.createLead(request);
|
||||
return Right(response);
|
||||
} catch (e) {
|
||||
return Left(ServerFailure(message: e.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import 'package:cargocalculaterapp/features/calculator/data/model/calculate_price_response.dart';
|
||||
import 'package:cargocalculaterapp/features/calculator/data/model/lead_create_response.dart';
|
||||
import 'package:cargocalculaterapp/features/calculator/data/model/lead_create_request.dart';
|
||||
import 'package:cargocalculaterapp/features/calculator/data/model/price_calculate_request.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import '../../../../core/error/failure.dart';
|
||||
|
||||
abstract class CalculatorRepository {
|
||||
Future<Either<Failure, CalculatePriceResponse>> calculatePrice(
|
||||
PriceCalculateRequest request,
|
||||
);
|
||||
Future<Either<Failure, LeadCreateResponse>> createLead(
|
||||
LeadCreateRequest request,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import 'package:cargocalculaterapp/core/error/failure.dart';
|
||||
import 'package:cargocalculaterapp/core/usecase/usecase.dart';
|
||||
import 'package:cargocalculaterapp/features/calculator/data/model/calculate_price_response.dart';
|
||||
import 'package:cargocalculaterapp/features/calculator/data/model/price_calculate_request.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
|
||||
import '../repository/calculator_repository.dart';
|
||||
|
||||
class CalculatePriceUseCase
|
||||
extends UseCase<CalculatePriceResponse, PriceCalculateRequest> {
|
||||
final CalculatorRepository repository;
|
||||
|
||||
CalculatePriceUseCase(this.repository);
|
||||
|
||||
@override
|
||||
Future<Either<Failure, CalculatePriceResponse>> call(
|
||||
PriceCalculateRequest params,
|
||||
) async {
|
||||
return await repository.calculatePrice(params);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import 'package:cargocalculaterapp/core/error/failure.dart';
|
||||
import 'package:cargocalculaterapp/core/usecase/usecase.dart';
|
||||
import 'package:cargocalculaterapp/features/calculator/data/model/lead_create_request.dart';
|
||||
import 'package:cargocalculaterapp/features/calculator/data/model/lead_create_response.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
|
||||
import '../repository/calculator_repository.dart';
|
||||
|
||||
class CreateLeadUseCase
|
||||
extends UseCase<LeadCreateResponse, LeadCreateRequest> {
|
||||
final CalculatorRepository repository;
|
||||
|
||||
CreateLeadUseCase(this.repository);
|
||||
|
||||
@override
|
||||
Future<Either<Failure, LeadCreateResponse>> call(
|
||||
LeadCreateRequest params,
|
||||
) async {
|
||||
return await repository.createLead(params);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
class CalculatorInfoArgument {
|
||||
final String productName;
|
||||
final String weraHouse;
|
||||
final double weight;
|
||||
final double size;
|
||||
final double averageWeight;
|
||||
final double deliveryPrice;
|
||||
|
||||
CalculatorInfoArgument({
|
||||
required this.productName,
|
||||
required this.weraHouse,
|
||||
required this.weight,
|
||||
required this.size,
|
||||
required this.averageWeight,
|
||||
required this.deliveryPrice,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
import 'package:cargocalculaterapp/router/app_routes.dart';
|
||||
import 'package:cargocalculaterapp/router/name_routes.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import '../../data/model/price_calculate_request.dart';
|
||||
import '../../domain/usecase/calculate_price_usecase.dart';
|
||||
import '../arguments/calculator_info_argument.dart';
|
||||
|
||||
part 'calculator_event.dart';
|
||||
|
||||
part 'calculator_state.dart';
|
||||
|
||||
class CalculatorBloc extends Bloc<CalculatorEvent, CalculatorState> {
|
||||
CalculatorBloc(this.calculatePriceUseCase)
|
||||
: super(const CalculatorState(isLoading: false)) {
|
||||
on<WeightSizeEnterEvent>(_weightSizeEntered);
|
||||
on<WareHouseSelectEvent>(_wareHouseSelect);
|
||||
on<CalculateEvent>(_calculate);
|
||||
}
|
||||
|
||||
final CalculatePriceUseCase calculatePriceUseCase;
|
||||
|
||||
|
||||
void _weightSizeEntered(
|
||||
WeightSizeEnterEvent event,
|
||||
Emitter<CalculatorState> emit,
|
||||
) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
size: double.tryParse(event.size.replaceAll(",", "")),
|
||||
weight: double.tryParse(event.weight.replaceAll(",", "")),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _wareHouseSelect(
|
||||
WareHouseSelectEvent event,
|
||||
Emitter<CalculatorState> emit,
|
||||
) {
|
||||
emit(state.copyWith(selectedWarehouse: event.wareHouse));
|
||||
}
|
||||
|
||||
Future<void> _calculate(
|
||||
CalculateEvent event,
|
||||
Emitter<CalculatorState> emit,
|
||||
) async {
|
||||
emit(state.copyWith(isLoading: true));
|
||||
final response = await calculatePriceUseCase(
|
||||
PriceCalculateRequest(
|
||||
m3: state.size,
|
||||
avg: (state.weight ?? 0) / (state.size ?? 1),
|
||||
),
|
||||
);
|
||||
response.fold(
|
||||
(l) {
|
||||
emit(state.copyWith(isLoading: false));
|
||||
},
|
||||
(r) {
|
||||
emit(state.copyWith(isLoading: false));
|
||||
Navigator.pushNamed(
|
||||
rootNavigatorKey.currentContext!,
|
||||
Routes.calculationInfo,
|
||||
arguments: CalculatorInfoArgument(
|
||||
productName: event.productName,
|
||||
deliveryPrice: r.price ?? 0,
|
||||
size: state.size ?? 0,
|
||||
averageWeight: (state.weight ?? 0) / (state.size ?? 1),
|
||||
weight: state.weight ?? 0,
|
||||
weraHouse: state.selectedWarehouse ?? "",
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
part of 'calculator_bloc.dart';
|
||||
|
||||
sealed class CalculatorEvent extends Equatable {
|
||||
const CalculatorEvent();
|
||||
}
|
||||
|
||||
final class WeightSizeEnterEvent extends CalculatorEvent {
|
||||
final String weight;
|
||||
final String size;
|
||||
|
||||
const WeightSizeEnterEvent({required this.weight, required this.size});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [weight, size];
|
||||
}
|
||||
|
||||
final class WareHouseSelectEvent extends CalculatorEvent {
|
||||
final String wareHouse;
|
||||
|
||||
const WareHouseSelectEvent({required this.wareHouse});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [wareHouse];
|
||||
}
|
||||
|
||||
final class CalculateEvent extends CalculatorEvent {
|
||||
const CalculateEvent({required this.productName});
|
||||
|
||||
final String productName;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [productName];
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
import 'package:cargocalculaterapp/core/extension/build_context_extension.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import '../../../../../constants/constants.dart';
|
||||
import '../../../../../generated/l10n.dart';
|
||||
import '../../../../../router/app_routes.dart';
|
||||
import '../../../../../router/name_routes.dart';
|
||||
import '../../../data/model/lead_create_request.dart';
|
||||
import '../../../domain/usecase/create_lead_usecase.dart';
|
||||
import '../../arguments/calculator_info_argument.dart';
|
||||
import '../../pages/calculator_info/dialog/order_dialog.dart';
|
||||
|
||||
part 'calculator_info_event.dart';
|
||||
|
||||
part 'calculator_info_state.dart';
|
||||
|
||||
class CalculatorInfoBloc
|
||||
extends Bloc<CalculatorInfoEvent, CalculatorInfoState> {
|
||||
CalculatorInfoBloc(this.createLeadUseCase)
|
||||
: super(const CalculatorInfoState(isLoading: false)) {
|
||||
on<CreateLeadEvent>(_createLead);
|
||||
on<ShowSuccessDialogEvent>(_showSuccessDialog);
|
||||
}
|
||||
|
||||
final CreateLeadUseCase createLeadUseCase;
|
||||
|
||||
Future<void> _createLead(
|
||||
CreateLeadEvent event,
|
||||
Emitter<CalculatorInfoState> emit,
|
||||
) async {
|
||||
emit(state.copyWith(isLoading: true));
|
||||
final local = Localizations.localeOf(
|
||||
rootNavigatorKey.currentContext!,
|
||||
).languageCode;
|
||||
final response = await createLeadUseCase(
|
||||
LeadCreateRequest(
|
||||
status: "NOT_CONTACTED",
|
||||
m3: "${event.argument.size}",
|
||||
averageWeightKg: "${event.argument.averageWeight}",
|
||||
weight: "${event.argument.weight}",
|
||||
nameRu: local == "ru" ? event.argument.productName : "",
|
||||
nameUz: local == "uz" ? event.argument.productName : "",
|
||||
nameZh: local == "zh" ? event.argument.productName : "",
|
||||
price: "${event.argument.deliveryPrice}",
|
||||
warehouseCode: int.tryParse(event.argument.weraHouse),
|
||||
wharehouseRu: AppConst.warehouseOptions[event.argument.weraHouse]?.ru,
|
||||
wharehouseUz: AppConst.warehouseOptions[event.argument.weraHouse]?.uz,
|
||||
wharehouseZh: AppConst.warehouseOptions[event.argument.weraHouse]?.zh,
|
||||
),
|
||||
);
|
||||
response.fold(
|
||||
(l) {
|
||||
emit(state.copyWith(isLoading: false));
|
||||
},
|
||||
(r) {
|
||||
emit(state.copyWith(isLoading: false));
|
||||
add(ShowSuccessDialogEvent(isCall: event.isCall));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _showSuccessDialog(
|
||||
ShowSuccessDialogEvent event,
|
||||
Emitter<CalculatorInfoState> emit,
|
||||
) {
|
||||
if (event.isCall) {
|
||||
showCupertinoModalPopup(
|
||||
context: rootNavigatorKey.currentContext!,
|
||||
builder: (context) => CupertinoActionSheet(
|
||||
actions: [
|
||||
CupertinoActionSheetAction(
|
||||
child: Text(
|
||||
"+998 99-110-22-22",
|
||||
style: TextStyle(color: context.color.primaryColor),
|
||||
),
|
||||
onPressed: () async {
|
||||
final Uri url = Uri(scheme: 'tel', path: "+998991102222");
|
||||
if (await canLaunchUrl(url)) {
|
||||
await launchUrl(url);
|
||||
}
|
||||
Navigator.pop(rootNavigatorKey.currentContext!, true);
|
||||
},
|
||||
),
|
||||
],
|
||||
cancelButton: CupertinoActionSheetAction(
|
||||
onPressed: () {
|
||||
Navigator.pop(rootNavigatorKey.currentContext!);
|
||||
},
|
||||
child: Text(
|
||||
AppLocalization.current.cancel,
|
||||
style: TextStyle(color: context.color.textColor),
|
||||
),
|
||||
),
|
||||
),
|
||||
).then((value) {
|
||||
if (value is bool) {
|
||||
Navigator.pushNamedAndRemoveUntil(
|
||||
rootNavigatorKey.currentContext!,
|
||||
Routes.main,
|
||||
(route) => false,
|
||||
);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
showDialog(
|
||||
context: rootNavigatorKey.currentContext!,
|
||||
builder: (context) => OrderDialog(isCall: event.isCall),
|
||||
).then((value) {
|
||||
Navigator.pushNamedAndRemoveUntil(
|
||||
rootNavigatorKey.currentContext!,
|
||||
Routes.main,
|
||||
(route) => false,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
part of 'calculator_info_bloc.dart';
|
||||
|
||||
sealed class CalculatorInfoEvent extends Equatable {
|
||||
const CalculatorInfoEvent();
|
||||
}
|
||||
|
||||
final class CreateLeadEvent extends CalculatorInfoEvent {
|
||||
const CreateLeadEvent({required this.argument, required this.isCall});
|
||||
|
||||
final bool isCall;
|
||||
final CalculatorInfoArgument argument;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [argument, isCall];
|
||||
}
|
||||
|
||||
final class ShowSuccessDialogEvent extends CalculatorInfoEvent {
|
||||
const ShowSuccessDialogEvent({required this.isCall});
|
||||
|
||||
final bool isCall;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [isCall];
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
part of 'calculator_info_bloc.dart';
|
||||
|
||||
class CalculatorInfoState extends Equatable {
|
||||
const CalculatorInfoState({required this.isLoading});
|
||||
|
||||
final bool isLoading;
|
||||
|
||||
CalculatorInfoState copyWith({bool? isLoading}) {
|
||||
return CalculatorInfoState(isLoading: isLoading ?? this.isLoading);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => [isLoading];
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
part of 'calculator_bloc.dart';
|
||||
|
||||
class CalculatorState extends Equatable {
|
||||
const CalculatorState({
|
||||
required this.isLoading,
|
||||
this.weight,
|
||||
this.size,
|
||||
this.selectedWarehouse,
|
||||
});
|
||||
|
||||
final bool isLoading;
|
||||
final double? weight;
|
||||
final double? size;
|
||||
final String? selectedWarehouse;
|
||||
|
||||
CalculatorState copyWith({
|
||||
bool? isLoading,
|
||||
double? weight,
|
||||
double? size,
|
||||
String? selectedWarehouse,
|
||||
}) {
|
||||
return CalculatorState(
|
||||
isLoading: isLoading ?? this.isLoading,
|
||||
weight: weight ?? this.weight,
|
||||
size: size ?? this.size,
|
||||
selectedWarehouse: selectedWarehouse ?? this.selectedWarehouse,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => [isLoading, weight, size, selectedWarehouse];
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
|
||||
mixin CalculatorMixin{
|
||||
late TextEditingController weightController;
|
||||
late TextEditingController sizeController;
|
||||
late TextEditingController averageWeightController;
|
||||
late TextEditingController productNameController;
|
||||
|
||||
void initControllers(){
|
||||
weightController=TextEditingController();
|
||||
sizeController=TextEditingController();
|
||||
averageWeightController=TextEditingController();
|
||||
productNameController=TextEditingController();
|
||||
}
|
||||
|
||||
void disposeController(){
|
||||
weightController.dispose();
|
||||
sizeController.dispose();
|
||||
averageWeightController.dispose();
|
||||
productNameController.dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
import 'package:cargocalculaterapp/core/extension/build_context_extension.dart';
|
||||
import 'package:cargocalculaterapp/core/functions/base_finctions.dart';
|
||||
import 'package:cargocalculaterapp/core/utils/app_utils.dart';
|
||||
import 'package:cargocalculaterapp/core/widgets/loading/progress_hud.dart';
|
||||
import 'package:cargocalculaterapp/features/calculator/presentation/pages/calculator_info/widget/calculator_info_widget.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
|
||||
import '../../../../../constants/constants.dart';
|
||||
import '../../../../../generated/l10n.dart';
|
||||
import '../../arguments/calculator_info_argument.dart';
|
||||
import '../../bloc/calculator_info/calculator_info_bloc.dart';
|
||||
|
||||
class CalculatorInfoPage extends StatelessWidget {
|
||||
const CalculatorInfoPage({super.key, required this.argument});
|
||||
|
||||
final CalculatorInfoArgument? argument;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<CalculatorInfoBloc, CalculatorInfoState>(
|
||||
builder: (context, state) {
|
||||
return Scaffold(
|
||||
backgroundColor: context.color.grayBackground,
|
||||
appBar: AppBar(title: Text(AppLocalization.current.calculator)),
|
||||
body: ModalProgressHUD(
|
||||
inAsyncCall: state.isLoading,
|
||||
child: ListView(
|
||||
padding: AppUtils.kPaddingAll16,
|
||||
children: [
|
||||
Container(
|
||||
margin: const EdgeInsets.only(bottom: 32),
|
||||
padding: AppUtils.kPaddingAll16,
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: AppUtils.kBorderRadius16,
|
||||
color: context.color.statusBackground,
|
||||
border: Border.all(color: context.color.lightBorder),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
CalculatorInfoWidget(
|
||||
title: AppLocalization.current.product_name,
|
||||
name: argument?.productName ?? "-",
|
||||
),
|
||||
CalculatorInfoWidget(
|
||||
title: AppLocalization.current.warehouse,
|
||||
name: Functions.getTranslatedItem(
|
||||
AppConst.warehouseOptions[argument?.weraHouse],
|
||||
context,
|
||||
),
|
||||
),
|
||||
CalculatorInfoWidget(
|
||||
title: AppLocalization.current.weight,
|
||||
name: "${argument?.weight} kg",
|
||||
),
|
||||
CalculatorInfoWidget(
|
||||
title: AppLocalization.current.size,
|
||||
name: "${argument?.size} m³",
|
||||
),
|
||||
CalculatorInfoWidget(
|
||||
title: AppLocalization.current.average_weight,
|
||||
name: "${argument?.averageWeight}",
|
||||
),
|
||||
CalculatorInfoWidget(
|
||||
title: AppLocalization.current.delivery_price,
|
||||
name: "${argument?.deliveryPrice}",
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
if (argument != null) {
|
||||
context.read<CalculatorInfoBloc>().add(
|
||||
CreateLeadEvent(
|
||||
isCall: true,
|
||||
argument: argument!,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
borderRadius: AppUtils.kBorderRadius24,
|
||||
child: Ink(
|
||||
decoration: BoxDecoration(
|
||||
color: context.color.scaffoldBackgroundColor,
|
||||
borderRadius: AppUtils.kBorderRadius24,
|
||||
border: Border.all(
|
||||
color: context.color.primaryColor,
|
||||
),
|
||||
),
|
||||
height: 56,
|
||||
padding: AppUtils.kPaddingHor16,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
"assets/svg/ic_phone.svg",
|
||||
colorFilter: ColorFilter.mode(
|
||||
context.color.textColor,
|
||||
BlendMode.srcIn,
|
||||
),
|
||||
),
|
||||
AppUtils.kBoxWidth4,
|
||||
Text(
|
||||
AppLocalization.current.phone_call,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: context.color.textColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
AppUtils.kBoxWidth16,
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
if (argument != null) {
|
||||
context.read<CalculatorInfoBloc>().add(
|
||||
CreateLeadEvent(
|
||||
isCall: false,
|
||||
argument: argument!,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
SvgPicture.asset("assets/svg/ic_list_1.svg"),
|
||||
AppUtils.kBoxWidth4,
|
||||
Text(AppLocalization.current.send),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import 'package:cargocalculaterapp/core/extension/build_context_extension.dart';
|
||||
import 'package:cargocalculaterapp/core/utils/app_utils.dart';
|
||||
import 'package:cargocalculaterapp/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
|
||||
class OrderDialog extends StatelessWidget {
|
||||
const OrderDialog({super.key, required this.isCall});
|
||||
|
||||
final bool isCall;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Dialog(
|
||||
backgroundColor: context.color.scaffoldBackgroundColor,
|
||||
insetPadding: AppUtils.kPaddingAll16,
|
||||
child: Padding(
|
||||
padding: AppUtils.kPaddingVer24Hor16,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SvgPicture.asset("assets/svg/ic_done.svg"),
|
||||
AppUtils.kBoxHeight20,
|
||||
Text(
|
||||
isCall
|
||||
? AppLocalization.current.done_phone
|
||||
: AppLocalization.current.done_order,
|
||||
style: context.text.authDesc.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
AppUtils.kBoxHeight20,
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context, true);
|
||||
},
|
||||
child: Text(AppLocalization.current.done_ready),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import 'package:cargocalculaterapp/core/extension/build_context_extension.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../../../core/utils/app_utils.dart';
|
||||
|
||||
class CalculatorInfoWidget extends StatelessWidget {
|
||||
const CalculatorInfoWidget({super.key, required this.title, required this.name});
|
||||
|
||||
final String title;
|
||||
final String name;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 16),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(child: Text(title, style: context.text.orderListTitle)),
|
||||
AppUtils.kBoxWidth8,
|
||||
Expanded(
|
||||
child: Text(
|
||||
name,
|
||||
style: context.text.profileCategory,
|
||||
textAlign: TextAlign.end,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
146
lib/features/calculator/presentation/pages/calculator_page.dart
Normal file
146
lib/features/calculator/presentation/pages/calculator_page.dart
Normal file
@@ -0,0 +1,146 @@
|
||||
import 'package:cargocalculaterapp/core/extension/build_context_extension.dart';
|
||||
import 'package:cargocalculaterapp/core/utils/app_utils.dart';
|
||||
import 'package:cargocalculaterapp/core/widgets/loading/progress_hud.dart';
|
||||
import 'package:cargocalculaterapp/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
|
||||
import '../../../../core/widgets/drop_down/custom_drop_down.dart';
|
||||
import '../../../../core/widgets/text_filds/custom_text_field_name.dart';
|
||||
import '../bloc/calculator_bloc.dart';
|
||||
import '../mixin/calculator_mixin.dart';
|
||||
|
||||
class CalculatorPage extends StatefulWidget {
|
||||
const CalculatorPage({super.key});
|
||||
|
||||
@override
|
||||
State<CalculatorPage> createState() => _CalculatorPageState();
|
||||
}
|
||||
|
||||
class _CalculatorPageState extends State<CalculatorPage> with CalculatorMixin {
|
||||
@override
|
||||
void initState() {
|
||||
initControllers();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocConsumer<CalculatorBloc, CalculatorState>(
|
||||
listener: (context, state) {
|
||||
if ((state.weight ?? 0) > 0 && (state.size ?? 0) > 0) {
|
||||
averageWeightController.text =
|
||||
"${(state.weight ?? 0) / (state.size ?? 1)}";
|
||||
}
|
||||
},
|
||||
builder: (context, state) {
|
||||
return Scaffold(
|
||||
backgroundColor: context.color.grayBackground,
|
||||
appBar: AppBar(title: Text(AppLocalization.current.calculator)),
|
||||
body: ModalProgressHUD(
|
||||
inAsyncCall: state.isLoading,
|
||||
child: ListView(
|
||||
padding: AppUtils.kPaddingAll16,
|
||||
children: [
|
||||
CustomTextFieldName(
|
||||
hint: AppLocalization.current.weight,
|
||||
name: AppLocalization.current.weight_in_kg,
|
||||
isRequired: true,
|
||||
format: [
|
||||
FilteringTextInputFormatter.allow(
|
||||
RegExp(r'^\d*[.,]?\d{0,}$'), // accepts both "." and ","
|
||||
),
|
||||
],
|
||||
controller: weightController,
|
||||
inputType: const TextInputType.numberWithOptions(
|
||||
decimal: true,
|
||||
),
|
||||
onchange: (value) {
|
||||
context.read<CalculatorBloc>().add(
|
||||
WeightSizeEnterEvent(
|
||||
weight: value,
|
||||
size: sizeController.text,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
AppUtils.kBoxHeight16,
|
||||
CustomTextFieldName(
|
||||
hint: AppLocalization.current.size,
|
||||
name: AppLocalization.current.size_in_m3,
|
||||
isRequired: true,
|
||||
controller: sizeController,
|
||||
format: [
|
||||
FilteringTextInputFormatter.allow(
|
||||
RegExp(r'^\d*[.,]?\d{0,}$'), // accepts both "." and ","
|
||||
),
|
||||
],
|
||||
inputType: const TextInputType.numberWithOptions(
|
||||
decimal: true,
|
||||
),
|
||||
onchange: (value) {
|
||||
context.read<CalculatorBloc>().add(
|
||||
WeightSizeEnterEvent(
|
||||
weight: weightController.text,
|
||||
size: value,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
AppUtils.kBoxHeight16,
|
||||
CustomTextFieldName(
|
||||
hint: AppLocalization.current.average_weight,
|
||||
name: AppLocalization.current.average_weight_in,
|
||||
controller: averageWeightController,
|
||||
readOnly: true,
|
||||
),
|
||||
AppUtils.kBoxHeight16,
|
||||
CustomTextFieldName(
|
||||
hint: AppLocalization.current.product_name,
|
||||
isRequired: true,
|
||||
name: AppLocalization.current.product_name,
|
||||
controller: productNameController,
|
||||
inputType: TextInputType.name,
|
||||
),
|
||||
AppUtils.kBoxHeight16,
|
||||
CustomDropDown(
|
||||
isRequired: false,
|
||||
value: state.selectedWarehouse,
|
||||
|
||||
onChange: (value) {
|
||||
context.read<CalculatorBloc>().add(
|
||||
WareHouseSelectEvent(wareHouse: value ?? ""),
|
||||
);
|
||||
},
|
||||
name: AppLocalization.current.select_warehouse,
|
||||
),
|
||||
AppUtils.kBoxHeight16,
|
||||
ElevatedButton(
|
||||
onPressed: ((state.weight ?? 0) > 0 && (state.size ?? 0) > 0)
|
||||
? () {
|
||||
context.read<CalculatorBloc>().add(
|
||||
CalculateEvent(
|
||||
productName: productNameController.text,
|
||||
),
|
||||
);
|
||||
}
|
||||
: null,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
SvgPicture.asset("assets/svg/ic_calculator_1.svg"),
|
||||
AppUtils.kBoxWith8,
|
||||
Text(AppLocalization.current.calculate),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import 'package:cargocalculaterapp/core/extension/build_context_extension.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../../core/utils/app_utils.dart';
|
||||
|
||||
class OrderDataWidget extends StatelessWidget {
|
||||
const OrderDataWidget({super.key, required this.title, required this.name});
|
||||
|
||||
final String title;
|
||||
final String name;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(child: Text(title, style: context.text.profileCategory)),
|
||||
AppUtils.kBoxWidth8,
|
||||
Expanded(
|
||||
child: Text(
|
||||
name,
|
||||
style: context.text.profileCategory,
|
||||
textAlign: TextAlign.end,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const Padding(padding: AppUtils.kPaddingVer8, child: AppUtils.kDivider),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user