From 98a8f873d83dbc14d354855591a956447d3202b9 Mon Sep 17 00:00:00 2001 From: Moaz Osama Date: Mon, 22 Sep 2025 09:43:31 +0300 Subject: [PATCH 1/6] feat: complete unit test --- .vscode/settings.json | 5 + lib/api/client/api_client.dart | 5 +- .../auth_remote_data_source_impl.dart | 25 +++- lib/api/mapper/apply_mapper.dart | 46 +++++++ .../models/requests/apply_request_dto.dart | 74 +++++++++++ .../models/responses/apply_response_dto.dart | 27 ++++ lib/api/models/responses/driver_dto.dart | 67 ++++++++++ lib/core/constants/end_points.dart | 2 +- .../data_source/auth_remote_data_source.dart | 10 +- lib/data/repo/auth_repo_impl.dart | 16 ++- lib/domain/entites/apply_response_entity.dart | 61 +++++++++ .../entites/request/apply_request_entity.dart | 51 ++++++++ lib/domain/repo/auth_repo.dart | 10 +- lib/domain/use_cases/apply_use_case.dart | 17 +++ pubspec.yaml | 2 + test/api/client/api_client_test.dart | 41 ++++++ test/api/mapper/apply_mapper_test.dart | 46 +++++++ .../requests/apply_request_dto_test.dart | 24 ++++ .../auth_remote_data_source_test.dart | 80 ++++++++++++ test/data/repo/auth_repo_impl_test.dart | 92 ++++++++++++++ .../domain/use_cases/apply_use_case_test.dart | 86 +++++++++++++ test/fixture/apply_fixture.dart | 117 ++++++++++++++++++ test/fixture/fake_image_file.dart | 9 ++ test/widget_test.dart | 30 ----- 24 files changed, 903 insertions(+), 40 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 lib/api/mapper/apply_mapper.dart create mode 100644 lib/api/models/requests/apply_request_dto.dart create mode 100644 lib/api/models/responses/apply_response_dto.dart create mode 100644 lib/api/models/responses/driver_dto.dart create mode 100644 lib/domain/entites/apply_response_entity.dart create mode 100644 lib/domain/entites/request/apply_request_entity.dart create mode 100644 lib/domain/use_cases/apply_use_case.dart create mode 100644 test/api/client/api_client_test.dart create mode 100644 test/api/mapper/apply_mapper_test.dart create mode 100644 test/api/models/requests/apply_request_dto_test.dart create mode 100644 test/data/data_source/auth_remote_data_source_test.dart create mode 100644 test/data/repo/auth_repo_impl_test.dart create mode 100644 test/domain/use_cases/apply_use_case_test.dart create mode 100644 test/fixture/apply_fixture.dart create mode 100644 test/fixture/fake_image_file.dart delete mode 100644 test/widget_test.dart diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..9658d04 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "cSpell.words": [ + "entites" + ] +} \ No newline at end of file diff --git a/lib/api/client/api_client.dart b/lib/api/client/api_client.dart index f03b776..3f9a148 100644 --- a/lib/api/client/api_client.dart +++ b/lib/api/client/api_client.dart @@ -1,4 +1,6 @@ import 'package:dio/dio.dart'; +import 'package:elevate_tracking_app/api/models/responses/apply_response_dto.dart'; +import 'package:elevate_tracking_app/core/constants/end_points.dart'; import 'package:injectable/injectable.dart'; import 'package:retrofit/retrofit.dart'; @@ -9,5 +11,6 @@ part 'api_client.g.dart'; abstract class ApiClient { @factoryMethod factory ApiClient(Dio dio) = _ApiClient; - + @POST(Endpoints.apply) + Future apply(@Body() FormData request); } diff --git a/lib/api/data_source/auth_remote_data_source_impl.dart b/lib/api/data_source/auth_remote_data_source_impl.dart index d676f66..87242cd 100644 --- a/lib/api/data_source/auth_remote_data_source_impl.dart +++ b/lib/api/data_source/auth_remote_data_source_impl.dart @@ -1,7 +1,28 @@ +import 'package:elevate_tracking_app/api/client/api_client.dart'; +import 'package:elevate_tracking_app/api/mapper/apply_mapper.dart'; +import 'package:elevate_tracking_app/api/models/requests/apply_request_dto.dart'; +import 'package:elevate_tracking_app/api/models/responses/apply_response_dto.dart'; +import 'package:elevate_tracking_app/core/api_result/api_result.dart'; +import 'package:elevate_tracking_app/core/api_result/safe_api_call.dart'; import 'package:elevate_tracking_app/data/data_source/auth_remote_data_source.dart'; +import 'package:elevate_tracking_app/domain/entites/apply_response_entity.dart'; +import 'package:elevate_tracking_app/domain/entites/request/apply_request_entity.dart'; import 'package:injectable/injectable.dart'; @Injectable(as: AuthRemoteDataSource) -class AuthRemoteDataSourceImpl implements AuthRemoteDataSource{ +class AuthRemoteDataSourceImpl implements AuthRemoteDataSource { + final ApiClient _apiClient; -} \ No newline at end of file + AuthRemoteDataSourceImpl(this._apiClient); + + @override + Future> apply({ + required ApplyRequestEntity request, + }) async { + return safeApiCall(() async { + final ApplyRequestDto dto = request.toDto(); + final formData = await dto.toFormData(); + return await _apiClient.apply(formData); + }, (dto) => dto.toEntity()); + } +} diff --git a/lib/api/mapper/apply_mapper.dart b/lib/api/mapper/apply_mapper.dart new file mode 100644 index 0000000..f5c4d41 --- /dev/null +++ b/lib/api/mapper/apply_mapper.dart @@ -0,0 +1,46 @@ +import 'package:elevate_tracking_app/api/models/responses/apply_response_dto.dart'; +import 'package:elevate_tracking_app/domain/entites/apply_response_entity.dart'; +import 'package:elevate_tracking_app/api/models/requests/apply_request_dto.dart'; +import 'package:elevate_tracking_app/domain/entites/request/apply_request_entity.dart'; +extension ApplyResponseMapper on ApplyResponseDto { + ApplyResponseEntity toEntity() { + return ApplyResponseEntity( + country: driver.country, + message: message, + token: token, + createdAt: driver.createdAt, + email: driver.email, + firstName: driver.firstName, + lastName: driver.lastName, + gender: driver.gender, + id: driver.id, + photo: driver.photo, + phone: driver.phone, + vehicleNumber: driver.vehicleNumber, + nid: driver.nid, + nidImg: driver.nidImg, + role: driver.role, + vehicleLicense: driver.vehicleLicense, + vehicleType: driver.vehicleType, + ); + } +} +extension ApplyRequestMapper on ApplyRequestEntity { + ApplyRequestDto toDto() { + return ApplyRequestDto( + country: country, + firstName: firstName, + lastName: lastName, + vehicleType: vehicleType, + vehicleNumber: vehicleNumber, + nid: nid, + email: email, + password: password, + rePassword: rePassword, + gender: gender, + phone: phone, + vehicleLicense: vehicleLicense, + nidImg: nidImg, + ); + } +} diff --git a/lib/api/models/requests/apply_request_dto.dart b/lib/api/models/requests/apply_request_dto.dart new file mode 100644 index 0000000..06f4a9d --- /dev/null +++ b/lib/api/models/requests/apply_request_dto.dart @@ -0,0 +1,74 @@ +import 'dart:io'; +import 'package:dio/dio.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:equatable/equatable.dart'; + +part 'apply_request_dto.g.dart'; + +@JsonSerializable() +class ApplyRequestDto extends Equatable { + final String country; + final String firstName; + final String lastName; + final String vehicleType; + final String vehicleNumber; + final String nid; + final String email; + final String password; + final String rePassword; + final String gender; + final String phone; + + @JsonKey(includeToJson: false, includeFromJson: false) + final File? vehicleLicense; + + @JsonKey(includeToJson: false, includeFromJson: false) + final File? nidImg; + + const ApplyRequestDto({ + required this.country, + required this.firstName, + required this.lastName, + required this.vehicleType, + required this.vehicleNumber, + required this.nid, + required this.email, + required this.password, + required this.rePassword, + required this.gender, + required this.phone, + this.vehicleLicense, + this.nidImg, + }); + + factory ApplyRequestDto.fromJson(Map json) => + _$ApplyRequestDtoFromJson(json); + + Map toJson() => _$ApplyRequestDtoToJson(this); + + Future toFormData() async { + return FormData.fromMap({ + ...toJson(), + if (vehicleLicense != null) + "vehicleLicense": await MultipartFile.fromFile(vehicleLicense!.path), + if (nidImg != null) "NIDImg": await MultipartFile.fromFile(nidImg!.path), + }); + } + + @override + List get props => [ + country, + firstName, + lastName, + vehicleType, + vehicleNumber, + nid, + email, + password, + rePassword, + gender, + phone, + vehicleLicense, + nidImg, + ]; +} diff --git a/lib/api/models/responses/apply_response_dto.dart b/lib/api/models/responses/apply_response_dto.dart new file mode 100644 index 0000000..e2bf74b --- /dev/null +++ b/lib/api/models/responses/apply_response_dto.dart @@ -0,0 +1,27 @@ +import 'package:elevate_tracking_app/api/models/responses/driver_dto.dart'; +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'apply_response_dto.g.dart'; + + +@JsonSerializable() +class ApplyResponseDto extends Equatable { + final String message; + final Driver driver; + final String token; + + const ApplyResponseDto({ + required this.message, + required this.driver, + required this.token, + }); + + factory ApplyResponseDto.fromJson(Map json) => + _$ApplyResponseDtoFromJson(json); + + Map toJson() => _$ApplyResponseDtoToJson(this); + + @override + List get props => [message, driver, token]; +} diff --git a/lib/api/models/responses/driver_dto.dart b/lib/api/models/responses/driver_dto.dart new file mode 100644 index 0000000..6888131 --- /dev/null +++ b/lib/api/models/responses/driver_dto.dart @@ -0,0 +1,67 @@ +import 'package:json_annotation/json_annotation.dart'; +import 'package:equatable/equatable.dart'; + +part 'driver_dto.g.dart'; + +@JsonSerializable() +class Driver extends Equatable { + final String country; + final String firstName; + final String lastName; + final String vehicleType; + final String vehicleNumber; + final String vehicleLicense; + @JsonKey(name: "NID") + final String nid; + @JsonKey(name: "NIDImg") + final String nidImg; + final String email; + final String gender; + final String phone; + final String photo; + final String role; + @JsonKey(name: "_id") + final String id; + final String createdAt; + + const Driver({ + required this.country, + required this.firstName, + required this.lastName, + required this.vehicleType, + required this.vehicleNumber, + required this.vehicleLicense, + required this.nid, + required this.nidImg, + required this.email, + required this.gender, + required this.phone, + required this.photo, + required this.role, + required this.id, + required this.createdAt, + }); + + factory Driver.fromJson(Map json) => _$DriverFromJson(json); + + Map toJson() => _$DriverToJson(this); + + @override + List get props => [ + country, + firstName, + lastName, + vehicleType, + vehicleNumber, + vehicleLicense, + nid, + nidImg, + email, + gender, + phone, + photo, + role, + id, + createdAt, + ]; +} diff --git a/lib/core/constants/end_points.dart b/lib/core/constants/end_points.dart index 801b604..65c8f3d 100644 --- a/lib/core/constants/end_points.dart +++ b/lib/core/constants/end_points.dart @@ -1,3 +1,3 @@ abstract class Endpoints { - + static const String apply = "v1/drivers/apply"; } diff --git a/lib/data/data_source/auth_remote_data_source.dart b/lib/data/data_source/auth_remote_data_source.dart index 8d354cb..68b38c8 100644 --- a/lib/data/data_source/auth_remote_data_source.dart +++ b/lib/data/data_source/auth_remote_data_source.dart @@ -1,3 +1,9 @@ +import 'package:elevate_tracking_app/core/api_result/api_result.dart'; +import 'package:elevate_tracking_app/domain/entites/apply_response_entity.dart'; +import 'package:elevate_tracking_app/domain/entites/request/apply_request_entity.dart'; + abstract interface class AuthRemoteDataSource { - -} \ No newline at end of file + Future> apply({ + required ApplyRequestEntity request, + }); +} diff --git a/lib/data/repo/auth_repo_impl.dart b/lib/data/repo/auth_repo_impl.dart index de70bf5..076ea3f 100644 --- a/lib/data/repo/auth_repo_impl.dart +++ b/lib/data/repo/auth_repo_impl.dart @@ -1,7 +1,19 @@ +import 'package:elevate_tracking_app/core/api_result/api_result.dart'; +import 'package:elevate_tracking_app/data/data_source/auth_remote_data_source.dart'; +import 'package:elevate_tracking_app/domain/entites/apply_response_entity.dart'; +import 'package:elevate_tracking_app/domain/entites/request/apply_request_entity.dart'; import 'package:elevate_tracking_app/domain/repo/auth_repo.dart'; import 'package:injectable/injectable.dart'; @Injectable(as: AuthRepo) class AuthRepoImpl implements AuthRepo { - -} \ No newline at end of file + final AuthRemoteDataSource _authRemoteDataSource; + + AuthRepoImpl(this._authRemoteDataSource); + @override + Future> apply({ + required ApplyRequestEntity request, + }) async { + return await _authRemoteDataSource.apply(request: request); + } +} diff --git a/lib/domain/entites/apply_response_entity.dart b/lib/domain/entites/apply_response_entity.dart new file mode 100644 index 0000000..4db9e28 --- /dev/null +++ b/lib/domain/entites/apply_response_entity.dart @@ -0,0 +1,61 @@ +import 'package:equatable/equatable.dart'; + +class ApplyResponseEntity extends Equatable { + final String? message; + final String? token; + final String? country; + final String? firstName; + final String? lastName; + final String? vehicleType; + final String? vehicleNumber; + final String? vehicleLicense; + final String? nid; + final String? nidImg; + final String? email; + final String? gender; + final String? phone; + final String? photo; + final String? role; + final String? id; + final String? createdAt; + const ApplyResponseEntity({ + this.message = '', + this.token = '', + this.country = '', + this.firstName = '', + this.lastName = '', + this.vehicleType = '', + this.vehicleNumber = '', + this.vehicleLicense = '', + this.nid = '', + this.nidImg = '', + this.email = '', + this.gender = '', + this.phone = '', + this.photo = '', + this.role = '', + this.id = '', + this.createdAt = '', + }); + + @override + List get props => [ + message, + token, + country, + firstName, + lastName, + vehicleType, + vehicleNumber, + vehicleLicense, + nid, + nidImg, + email, + gender, + phone, + photo, + role, + id, + createdAt, + ]; +} diff --git a/lib/domain/entites/request/apply_request_entity.dart b/lib/domain/entites/request/apply_request_entity.dart new file mode 100644 index 0000000..fdf70a8 --- /dev/null +++ b/lib/domain/entites/request/apply_request_entity.dart @@ -0,0 +1,51 @@ +import 'dart:io'; +import 'package:equatable/equatable.dart'; + +class ApplyRequestEntity extends Equatable { + final String country; + final String firstName; + final String lastName; + final String vehicleType; + final String vehicleNumber; + final String nid; + final String email; + final String password; + final String rePassword; + final String gender; + final String phone; + final File? vehicleLicense; + final File? nidImg; + + const ApplyRequestEntity({ + required this.country, + required this.firstName, + required this.lastName, + required this.vehicleType, + required this.vehicleNumber, + required this.nid, + required this.email, + required this.password, + required this.rePassword, + required this.gender, + required this.phone, + this.vehicleLicense, + this.nidImg, + }); + + @override + List get props => [ + country, + firstName, + lastName, + vehicleType, + vehicleNumber, + nid, + email, + password, + rePassword, + gender, + phone, + vehicleLicense, + nidImg, + ]; +} diff --git a/lib/domain/repo/auth_repo.dart b/lib/domain/repo/auth_repo.dart index 8836b76..b31c13b 100644 --- a/lib/domain/repo/auth_repo.dart +++ b/lib/domain/repo/auth_repo.dart @@ -1,3 +1,9 @@ +import 'package:elevate_tracking_app/core/api_result/api_result.dart'; +import 'package:elevate_tracking_app/domain/entites/apply_response_entity.dart'; +import 'package:elevate_tracking_app/domain/entites/request/apply_request_entity.dart'; + abstract interface class AuthRepo { - -} \ No newline at end of file + Future> apply({ + required ApplyRequestEntity request, + }); +} diff --git a/lib/domain/use_cases/apply_use_case.dart b/lib/domain/use_cases/apply_use_case.dart new file mode 100644 index 0000000..27dc96d --- /dev/null +++ b/lib/domain/use_cases/apply_use_case.dart @@ -0,0 +1,17 @@ +import 'package:elevate_tracking_app/core/api_result/api_result.dart'; +import 'package:elevate_tracking_app/domain/entites/apply_response_entity.dart'; +import 'package:elevate_tracking_app/domain/entites/request/apply_request_entity.dart'; +import 'package:elevate_tracking_app/domain/repo/auth_repo.dart'; +import 'package:injectable/injectable.dart'; + +@injectable +class ApplyUseCase { + final AuthRepo _authRepo; + + ApplyUseCase(this._authRepo); + Future> call( + ApplyRequestEntity request, + ) async { + return await _authRepo.apply(request: request); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 89c1e5e..ca51754 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -53,6 +53,7 @@ dependencies: shared_preferences: ^2.5.3 image_picker: ^1.2.0 go_router: ^16.2.2 + path: ^1.9.1 dev_dependencies: flutter_test: @@ -64,6 +65,7 @@ dev_dependencies: injectable_generator: ^2.8.1 bloc_test: ^10.0.0 mockito: ^5.5.0 + http_mock_adapter: ^0.6.1 # The "flutter_lints" package below contains a set of recommended lints to # encourage good coding practices. The lint set provided by the package is diff --git a/test/api/client/api_client_test.dart b/test/api/client/api_client_test.dart new file mode 100644 index 0000000..60f8b7d --- /dev/null +++ b/test/api/client/api_client_test.dart @@ -0,0 +1,41 @@ +import 'package:dio/dio.dart'; +import 'package:elevate_tracking_app/api/client/api_client.dart'; +import 'package:elevate_tracking_app/api/models/requests/apply_request_dto.dart'; +import 'package:elevate_tracking_app/api/models/responses/apply_response_dto.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:http_mock_adapter/http_mock_adapter.dart' + show DioAdapter, Matchers; + +import '../../fixture/apply_fixture.dart'; + +void main() { + group("Group ApplyResponseDto", () { + //ARRANGE + late Dio dio; + late DioAdapter dioAdapter; + late ApiClient apiClient; + + setUp(() { + dio = Dio(BaseOptions(baseUrl: "https://flower.elevateegy.com/api")); + dioAdapter = DioAdapter(dio: dio); + apiClient = ApiClient(dio); + }); + test("test should be return ApplyResponseDto", () async { + //ARRANGE + final ApplyRequestDto requestDto = + await ApplyFixture.fakeApiClintRequestDto(); + final ApplyResponseDto responseDto = ApplyFixture.fakeResponseDto(); + dioAdapter.onPost( + "v1/drivers/apply", + (server) => server.reply(200, responseDto.toJson()), + data: Matchers.any, + ); + //ACT + final result = await apiClient.apply(await requestDto.toFormData()); + //ASSERT + expect(result, isA()); + expect(result.message, equals(responseDto.message)); + expect(result.driver.firstName, equals(responseDto.driver.firstName)); + }); + }); +} diff --git a/test/api/mapper/apply_mapper_test.dart b/test/api/mapper/apply_mapper_test.dart new file mode 100644 index 0000000..f5d2c4b --- /dev/null +++ b/test/api/mapper/apply_mapper_test.dart @@ -0,0 +1,46 @@ +import 'package:elevate_tracking_app/api/mapper/apply_mapper.dart'; +import 'package:elevate_tracking_app/api/models/responses/apply_response_dto.dart'; +import 'package:elevate_tracking_app/domain/entites/request/apply_request_entity.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../../fixture/apply_fixture.dart'; + +void main() { + group("Apply Mapper", () { + test("toDto should map correctly", () async { + // ARRANGE: Fake DTO + final ApplyRequestEntity entity = await ApplyFixture.fakeRequestEntity(); + // ACT: + final dto = entity.toDto(); + // ASSERT: + expect(dto.country, equals(entity.country)); + expect(dto.email, equals(entity.email)); + expect(dto.firstName, equals(entity.firstName)); + expect(dto.lastName, equals(entity.lastName)); + expect(dto.vehicleNumber, equals(entity.vehicleNumber)); + expect(dto.nid, equals(entity.nid)); + expect(dto.nidImg, equals(entity.nidImg)); + expect(dto.password, equals(entity.password)); + expect(dto.phone, equals(entity.phone)); + expect(dto.vehicleType, equals(entity.vehicleType)); + }); + test("toEntity should map correctly", () { + // ARRANGE: Fake DTO + final ApplyResponseDto dto = ApplyFixture.fakeResponseDto(); + + // ACT: + final entity = dto.toEntity(); + // ASSERT: + expect(entity.message, equals(dto.message)); + expect(entity.token, equals(dto.token)); + expect(entity.firstName, equals(dto.driver.firstName)); + expect(entity.lastName, equals(dto.driver.lastName)); + expect(entity.vehicleNumber, equals(dto.driver.vehicleNumber)); + expect(entity.nid, equals(dto.driver.nid)); + expect(entity.nidImg, equals(dto.driver.nidImg)); + expect(entity.role, equals(dto.driver.role)); + expect(entity.vehicleLicense, equals(dto.driver.vehicleLicense)); + expect(entity.vehicleType, equals(dto.driver.vehicleType)); + }); + }); +} diff --git a/test/api/models/requests/apply_request_dto_test.dart b/test/api/models/requests/apply_request_dto_test.dart new file mode 100644 index 0000000..c904e34 --- /dev/null +++ b/test/api/models/requests/apply_request_dto_test.dart @@ -0,0 +1,24 @@ +import 'package:flutter_test/flutter_test.dart'; +import '../../../fixture/apply_fixture.dart'; + +void main() { + group('ApplyRequestDto.toFormData', () { + test('should convert dto to FormData without files', () async { + final dto = await ApplyFixture.fakeApiClintRequestDto(); + + final formData = await dto.toFormData(); + expect( + formData.fields, + anyElement((e) => e.key == "country" && e.value == "Egypt"), + ); + expect( + formData.fields, + anyElement((e) => e.key == "firstName" && e.value == "Ahmed"), + ); + expect( + formData.fields, + anyElement((e) => e.key == "lastName" && e.value == "Ali"), + ); + }); + }); +} diff --git a/test/data/data_source/auth_remote_data_source_test.dart b/test/data/data_source/auth_remote_data_source_test.dart new file mode 100644 index 0000000..c320ee5 --- /dev/null +++ b/test/data/data_source/auth_remote_data_source_test.dart @@ -0,0 +1,80 @@ +import 'package:dio/dio.dart'; +import 'package:elevate_tracking_app/api/client/api_client.dart'; +import 'package:elevate_tracking_app/api/data_source/auth_remote_data_source_impl.dart'; +import 'package:elevate_tracking_app/api/models/responses/apply_response_dto.dart'; +import 'package:elevate_tracking_app/core/api_result/api_result.dart'; +import 'package:elevate_tracking_app/domain/entites/apply_response_entity.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; + +import '../../fixture/apply_fixture.dart'; +import 'auth_remote_data_source_test.mocks.dart'; + +@GenerateMocks([ApiClient]) +void main() { + group("Apply remote data Source test", () { + final fakeRequestEntity = ApplyFixture.fakeRequestEntity(); + late ApplyResponseDto fakeResponseDto; + final DioException fakeDioException = DioException( + requestOptions: RequestOptions(), + message: "fake_message", + ); + final Exception fakeException = Exception(); + late MockApiClient mockApiClient; + late AuthRemoteDataSourceImpl authRemoteDataSourceImpl; + setUp(() { + fakeResponseDto = ApplyFixture.fakeResponseDto(); + mockApiClient = MockApiClient(); + authRemoteDataSourceImpl = AuthRemoteDataSourceImpl(mockApiClient); + }); + test("apply success", () async { + final fakeRequestEn = await fakeRequestEntity; + //ARRANGE + when(mockApiClient.apply(any)).thenAnswer((_) async => fakeResponseDto); + //ACT + final result = await authRemoteDataSourceImpl.apply( + request: fakeRequestEn, + ); + //ASSERT + expect(result, isA>()); + expect( + (result as ApiSuccessResult).data.message, + equals(fakeResponseDto.message), + ); + verify(mockApiClient.apply(any)).called(1); + }); + test("apply dio exception", () async { + final fakeRequestEn = await fakeRequestEntity; + //ARRANGE + when(mockApiClient.apply(any)).thenThrow(fakeDioException); + //ACT + final result = await authRemoteDataSourceImpl.apply( + request: fakeRequestEn, + ); + //ASSERT + expect(result, isA>()); + expect( + (result as ApiErrorResult).errorMessage, + equals(contains(fakeDioException.message)), + ); + verify(mockApiClient.apply(any)).called(1); + }); + test("apply exception", () async { + final fakeRequestEn = await fakeRequestEntity; + //ARRANGE + when(mockApiClient.apply(any)).thenThrow(fakeException); + //ACT + final result = await authRemoteDataSourceImpl.apply( + request: fakeRequestEn, + ); + //ASSERT + expect(result, isA>()); + expect( + (result as ApiErrorResult).error, + equals(fakeException), + ); + verify(mockApiClient.apply(any)).called(1); + }); + }); +} diff --git a/test/data/repo/auth_repo_impl_test.dart b/test/data/repo/auth_repo_impl_test.dart new file mode 100644 index 0000000..7b8e84d --- /dev/null +++ b/test/data/repo/auth_repo_impl_test.dart @@ -0,0 +1,92 @@ +import 'package:dio/dio.dart'; +import 'package:elevate_tracking_app/api/data_source/auth_remote_data_source_impl.dart'; +import 'package:elevate_tracking_app/core/api_result/api_result.dart'; +import 'package:elevate_tracking_app/data/repo/auth_repo_impl.dart'; +import 'package:elevate_tracking_app/domain/entites/apply_response_entity.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; + +import '../../fixture/apply_fixture.dart'; +import 'auth_repo_impl_test.mocks.dart'; + +@GenerateMocks([AuthRemoteDataSourceImpl]) +void main() { + group("Test Apply", () { + final fakeRequestEntity = ApplyFixture.fakeRequestEntity(); + final fakeResponseEntity = ApplyFixture.fakeResponseEntity(); + late MockAuthRemoteDataSourceImpl mockAuthRemoteDataSourceImpl; + late AuthRepoImpl authRepoImpl; + final DioException dioException = DioException( + requestOptions: RequestOptions(), + message: "fake_dio_message", + ); + final Exception fakeException = Exception(); + setUp(() { + mockAuthRemoteDataSourceImpl = MockAuthRemoteDataSourceImpl(); + authRepoImpl = AuthRepoImpl(mockAuthRemoteDataSourceImpl); + provideDummy>( + ApiSuccessResult(fakeResponseEntity), + ); + provideDummy>( + ApiErrorResult(fakeException), + ); + }); + test("apply success ApiResult ApplyResponseEntity", () async { + final fakeRequestEn = await fakeRequestEntity; + final expectResult = ApiSuccessResult( + fakeResponseEntity, + ); + when( + mockAuthRemoteDataSourceImpl.apply(request: fakeRequestEn), + ).thenAnswer((_) async => expectResult); + + final result = await authRepoImpl.apply(request: fakeRequestEn); + expect(result, isA>()); + expect( + (result as ApiSuccessResult).data.id, + equals(fakeResponseEntity.id), + ); + + verify( + mockAuthRemoteDataSourceImpl.apply(request: fakeRequestEn), + ).called(1); + }); + test("apply failure ApiResult DioError", () async { + final fakeRequestEn = await fakeRequestEntity; + final expectResult = ApiErrorResult(dioException); + when( + mockAuthRemoteDataSourceImpl.apply(request: fakeRequestEn), + ).thenAnswer((_) async => expectResult); + + final result = await authRepoImpl.apply(request: fakeRequestEn); + expect(result, isA>()); + expect( + (result as ApiErrorResult).errorMessage, + contains(dioException.message), + ); + + verify( + mockAuthRemoteDataSourceImpl.apply(request: fakeRequestEn), + ).called(1); + }); + test("apply failure ApiResult Exception", () async { + final fakeRequestEn = await fakeRequestEntity; + final expectResult = ApiErrorResult(fakeException); + when( + mockAuthRemoteDataSourceImpl.apply(request: fakeRequestEn), + ).thenAnswer((_) async => expectResult); + + final result = await authRepoImpl.apply(request: fakeRequestEn); + expect(result, isA>()); + expect( + (result as ApiErrorResult).error, + equals(fakeException), + ); + + verify( + mockAuthRemoteDataSourceImpl.apply(request: fakeRequestEn), + ).called(1); + }); + }); +} diff --git a/test/domain/use_cases/apply_use_case_test.dart b/test/domain/use_cases/apply_use_case_test.dart new file mode 100644 index 0000000..4cd62f3 --- /dev/null +++ b/test/domain/use_cases/apply_use_case_test.dart @@ -0,0 +1,86 @@ +import 'package:dio/dio.dart'; +import 'package:elevate_tracking_app/core/api_result/api_result.dart'; +import 'package:elevate_tracking_app/data/repo/auth_repo_impl.dart'; +import 'package:elevate_tracking_app/domain/entites/apply_response_entity.dart'; +import 'package:elevate_tracking_app/domain/use_cases/apply_use_case.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; + +import '../../fixture/apply_fixture.dart'; +import 'apply_use_case_test.mocks.dart'; + +@GenerateMocks([AuthRepoImpl]) +void main() { + group("Test Apply use case", () { + final fakeRequestEntity = ApplyFixture.fakeRequestEntity(); + final fakeResponseEntity = ApplyFixture.fakeResponseEntity(); + late MockAuthRepoImpl mockAuthRepoImpl; + late ApplyUseCase useCase; + final DioException dioException = DioException( + requestOptions: RequestOptions(), + message: "fake_dio_message", + ); + final Exception fakeException = Exception(); + setUp(() { + mockAuthRepoImpl = MockAuthRepoImpl(); + useCase = ApplyUseCase(mockAuthRepoImpl); + provideDummy>( + ApiSuccessResult(fakeResponseEntity), + ); + provideDummy>( + ApiErrorResult(fakeException), + ); + }); + test("apply use case success ApiResult ApplyResponseEntity", () async { + final fakeRequestEn = await fakeRequestEntity; + final expectResult = ApiSuccessResult( + fakeResponseEntity, + ); + when( + mockAuthRepoImpl.apply(request: fakeRequestEn), + ).thenAnswer((_) async => expectResult); + + final result = await useCase.call(fakeRequestEn); + expect(result, isA>()); + expect( + (result as ApiSuccessResult).data.id, + equals(fakeResponseEntity.id), + ); + + verify(mockAuthRepoImpl.apply(request: fakeRequestEn)).called(1); + }); + test("apply use case failure ApiResult DioError", () async { + final fakeRequestEn = await fakeRequestEntity; + final expectResult = ApiErrorResult(dioException); + when( + mockAuthRepoImpl.apply(request: fakeRequestEn), + ).thenAnswer((_) async => expectResult); + + final result = await useCase.call(fakeRequestEn); + expect(result, isA>()); + expect( + (result as ApiErrorResult).errorMessage, + contains(dioException.message), + ); + + verify(mockAuthRepoImpl.apply(request: fakeRequestEn)).called(1); + }); + test("apply use case failure ApiResult Exception", () async { + final fakeRequestEn = await fakeRequestEntity; + final expectResult = ApiErrorResult(fakeException); + when( + mockAuthRepoImpl.apply(request: fakeRequestEn), + ).thenAnswer((_) async => expectResult); + + final result = await useCase.call(fakeRequestEn); + expect(result, isA>()); + expect( + (result as ApiErrorResult).error, + equals(fakeException), + ); + + verify(mockAuthRepoImpl.apply(request: fakeRequestEn)).called(1); + }); + }); +} diff --git a/test/fixture/apply_fixture.dart b/test/fixture/apply_fixture.dart new file mode 100644 index 0000000..c28f625 --- /dev/null +++ b/test/fixture/apply_fixture.dart @@ -0,0 +1,117 @@ +import 'dart:io'; + +import 'package:elevate_tracking_app/api/models/requests/apply_request_dto.dart'; +import 'package:elevate_tracking_app/api/models/responses/apply_response_dto.dart'; +import 'package:elevate_tracking_app/api/models/responses/driver_dto.dart'; +import 'package:elevate_tracking_app/domain/entites/apply_response_entity.dart'; +import 'package:elevate_tracking_app/domain/entites/request/apply_request_entity.dart'; + +import 'fake_image_file.dart'; + +class ApplyFixture { + static Future fakeApiClintRequestDto() async { + final license = await createTempFile("license.png"); + final nid = await createTempFile("nid.png"); + return ApplyRequestDto( + country: "Egypt", + firstName: "Ahmed", + lastName: "Ali", + vehicleType: "676b31a45d05310ca82657ac", + vehicleNumber: "12221", + nid: "12345678912345", + email: "ahmedutti@gmail.com", + password: "Ahmed@123", + rePassword: "Ahmed@123", + gender: "male", + phone: "+201010700884", + vehicleLicense: license, + nidImg: nid, + ); + } + + static ApplyRequestDto fakeRequestDto() { + return ApplyRequestDto( + country: "Egypt", + firstName: "Ahmed", + lastName: "Ali", + vehicleType: "676b31a45d05310ca82657ac", + vehicleNumber: "12221", + nid: "12345678912345", + email: "ahmedutti@gmail.com", + password: "Ahmed@123", + rePassword: "Ahmed@123", + gender: "male", + phone: "+201010700884", + vehicleLicense: File("test_resources/fake_license.png"), + nidImg: File("test_resources/fake_nid.png"), + ); + } + + /// Fake response DTO + static ApplyResponseDto fakeResponseDto() { + return const ApplyResponseDto( + message: "success", + token: "eyFakeTokenForTesting123456789", + driver: Driver( + country: "Egypt", + firstName: "Ahmed", + lastName: "Ali", + vehicleType: "676b31a45d05310ca82657ac", + vehicleNumber: "12221", + vehicleLicense: "df8ce8dd-70f9-4819-9607-992fcb90b279-Vector.png", + nid: "12345678912345", + nidImg: "8a6de7c2-4fb8-4eef-b015-a17070a8cf7a-kTestFlower.png", + email: "ahmedutti@gmail.com", + gender: "male", + phone: "+201010700884", + photo: "default-profile.png", + role: "driver", + id: "68cf8c7ddd8937e0573ed395", + createdAt: "2025-09-21T05:26:21.346Z", + ), + ); + } + + static Future fakeRequestEntity() async { + final license = await createTempFile("fake_vehicle_license.png"); + final nid = await createTempFile("fake_nid.png"); + + return ApplyRequestEntity( + country: "Egypt", + firstName: "Ahmed", + lastName: "Ali", + vehicleType: "Car", + vehicleNumber: "12221", + nid: "12345678912345", + email: "ahmed@test.com", + password: "Ahmed@123", + rePassword: "Ahmed@123", + gender: "male", + phone: "+201010700884", + vehicleLicense: license, + nidImg: nid, + ); + } + + static ApplyResponseEntity fakeResponseEntity() { + return const ApplyResponseEntity( + message: "success", + token: "fake_token_123", + country: "Egypt", + firstName: "Ahmed", + lastName: "Ali", + vehicleType: "676b31a45d05310ca82657ac", + vehicleNumber: "12221", + vehicleLicense: "df8ce8dd-70f9-4819-9607-992fcb90b279-Vector.png", + nid: "12345678912345", + nidImg: "8a6de7c2-4fb8-4eef-b015-a17070a8cf7a-kTestFlower.png", + email: "ahmedutti@gmail.com", + gender: "male", + phone: "+201010700884", + photo: "default-profile.png", + role: "driver", + id: "68cf8c7ddd8937e0573ed395", + createdAt: "2025-09-21T05:26:21.346Z", + ); + } +} diff --git a/test/fixture/fake_image_file.dart b/test/fixture/fake_image_file.dart new file mode 100644 index 0000000..32ff384 --- /dev/null +++ b/test/fixture/fake_image_file.dart @@ -0,0 +1,9 @@ +import 'dart:io'; +import 'package:path/path.dart' as p; + +Future createTempFile(String name) async { + final dir = Directory.systemTemp.createTempSync(); + final file = File(p.join(dir.path, name)); + await file.writeAsString("fake image content"); + return file; +} diff --git a/test/widget_test.dart b/test/widget_test.dart deleted file mode 100644 index ddb016c..0000000 --- a/test/widget_test.dart +++ /dev/null @@ -1,30 +0,0 @@ -// This is a basic Flutter widget test. -// -// To perform an interaction with a widget in your test, use the WidgetTester -// utility in the flutter_test package. For example, you can send tap and scroll -// gestures. You can also use WidgetTester to find child widgets in the widget -// tree, read text, and verify that the values of widget properties are correct. - -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -import 'package:elevate_tracking_app/main.dart'; - -void main() { - testWidgets('Counter increments smoke test', (WidgetTester tester) async { - // Build our app and trigger a frame. - await tester.pumpWidget(const MyApp()); - - // Verify that our counter starts at 0. - expect(find.text('0'), findsOneWidget); - expect(find.text('1'), findsNothing); - - // Tap the '+' icon and trigger a frame. - await tester.tap(find.byIcon(Icons.add)); - await tester.pump(); - - // Verify that our counter has incremented. - expect(find.text('0'), findsNothing); - expect(find.text('1'), findsOneWidget); - }); -} From 822c6d935139e89ed566b19204bb59fc7194e975 Mon Sep 17 00:00:00 2001 From: Moaz Osama Date: Fri, 26 Sep 2025 09:54:44 +0300 Subject: [PATCH 2/6] feat: finish UI and connect with logic --- .../.kotlin/errors/errors-1758560907485.log | 200 ++++++++++++++ .../.kotlin/errors/errors-1758560907636.log | 200 ++++++++++++++ .../.kotlin/errors/errors-1758634868199.log | 200 ++++++++++++++ .../.kotlin/errors/errors-1758634868227.log | 200 ++++++++++++++ android/app/src/main/AndroidManifest.xml | 21 +- assets/data/country.json | 232 +++++++++++++++++ coverage/build/.last_build_id | 1 + ios/Runner/Info.plist | 4 + lib/api/client/api_client.dart | 3 + .../auth_local_data_source_impl.dart | 28 +- .../auth_remote_data_source_impl.dart | 10 + lib/api/mapper/apply_mapper.dart | 23 ++ .../models/requests/apply_request_dto.dart | 3 +- lib/api/models/responses/country_dto.dart | 78 ++++++ lib/api/models/responses/vehicle.dart | 28 ++ .../models/responses/vehicles_response.dart | 43 +++ lib/core/base_state/base_state.dart | 14 + lib/core/constants/app_theme.dart | 1 - lib/core/constants/const_keys.dart | 2 + lib/core/constants/end_points.dart | 4 +- lib/core/constants/widgets_keys.dart | 42 +++ lib/core/module/asset_bundle_module.dart | 8 + lib/core/router/app_router.dart | 7 +- lib/core/utils/validations.dart | 14 +- .../data_source/auth_local_data_source.dart | 7 +- .../data_source/auth_remote_data_source.dart | 2 + lib/data/repo/auth_repo_impl.dart | 17 +- lib/domain/entites/country_entity.dart | 17 ++ lib/domain/entites/vehicles_entity.dart | 11 + lib/domain/repo/auth_repo.dart | 4 + lib/domain/use_cases/apply_use_case.dart | 1 - .../use_cases/get_all_country_use_case.dart | 14 + .../use_cases/get_all_vehicles_use_case.dart | 14 + lib/l10n/intl_en.arb | 32 ++- .../apply/view/screen/apply_view.dart | 42 +++ .../apply/view/widgets/apply_body.dart | 244 ++++++++++++++++++ .../view/widgets/custom_apply_radio.dart | 59 +++++ .../widgets/custom_country_text_field.dart | 103 ++++++++ .../view/widgets/custom_password_field.dart | 70 +++++ .../custom_vehicle_type_text_filed.dart | 117 +++++++++ .../apply/view_model/apply_event.dart | 11 + .../apply/view_model/apply_view_model.dart | 210 +++++++++++++++ .../view_model/apply_view_model_state.dart | 40 +++ pubspec.yaml | 1 + test/api/client/api_client_test.dart | 19 +- .../auth_local_data_source_impl_test.dart | 43 +++ test/api/mapper/apply_mapper_test.dart | 25 +- .../auth_remote_data_source_test.dart | 65 ++++- test/data/repo/auth_repo_impl_test.dart | 122 ++++++++- .../get_all_country_use_case_test.dart | 63 +++++ .../use_cases/get_all_vehicles_test.dart | 85 ++++++ test/fixture/apply_fixture.dart | 133 ++++++++++ test/fixture/fake_file_json.dart | 25 ++ 53 files changed, 2918 insertions(+), 44 deletions(-) create mode 100644 android/.kotlin/errors/errors-1758560907485.log create mode 100644 android/.kotlin/errors/errors-1758560907636.log create mode 100644 android/.kotlin/errors/errors-1758634868199.log create mode 100644 android/.kotlin/errors/errors-1758634868227.log create mode 100644 assets/data/country.json create mode 100644 coverage/build/.last_build_id create mode 100644 lib/api/models/responses/country_dto.dart create mode 100644 lib/api/models/responses/vehicle.dart create mode 100644 lib/api/models/responses/vehicles_response.dart create mode 100644 lib/core/base_state/base_state.dart create mode 100644 lib/core/constants/widgets_keys.dart create mode 100644 lib/core/module/asset_bundle_module.dart create mode 100644 lib/domain/entites/country_entity.dart create mode 100644 lib/domain/entites/vehicles_entity.dart create mode 100644 lib/domain/use_cases/get_all_country_use_case.dart create mode 100644 lib/domain/use_cases/get_all_vehicles_use_case.dart create mode 100644 lib/presentation/apply/view/screen/apply_view.dart create mode 100644 lib/presentation/apply/view/widgets/apply_body.dart create mode 100644 lib/presentation/apply/view/widgets/custom_apply_radio.dart create mode 100644 lib/presentation/apply/view/widgets/custom_country_text_field.dart create mode 100644 lib/presentation/apply/view/widgets/custom_password_field.dart create mode 100644 lib/presentation/apply/view/widgets/custom_vehicle_type_text_filed.dart create mode 100644 lib/presentation/apply/view_model/apply_event.dart create mode 100644 lib/presentation/apply/view_model/apply_view_model.dart create mode 100644 lib/presentation/apply/view_model/apply_view_model_state.dart create mode 100644 test/api/data_source/auth_local_data_source_impl_test.dart create mode 100644 test/domain/use_cases/get_all_country_use_case_test.dart create mode 100644 test/domain/use_cases/get_all_vehicles_test.dart create mode 100644 test/fixture/fake_file_json.dart diff --git a/android/.kotlin/errors/errors-1758560907485.log b/android/.kotlin/errors/errors-1758560907485.log new file mode 100644 index 0000000..6047329 --- /dev/null +++ b/android/.kotlin/errors/errors-1758560907485.log @@ -0,0 +1,200 @@ +kotlin version: 2.1.0 +error message: Daemon compilation failed: null +java.lang.Exception + at org.jetbrains.kotlin.daemon.common.CompileService$CallResult$Error.get(CompileService.kt:69) + at org.jetbrains.kotlin.daemon.common.CompileService$CallResult$Error.get(CompileService.kt:65) + at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemon(GradleKotlinCompilerWork.kt:240) + at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemonOrFallbackImpl(GradleKotlinCompilerWork.kt:159) + at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.run(GradleKotlinCompilerWork.kt:111) + at org.jetbrains.kotlin.compilerRunner.GradleCompilerRunnerWithWorkers$GradleKotlinCompilerWorkAction.execute(GradleCompilerRunnerWithWorkers.kt:76) + at org.gradle.workers.internal.DefaultWorkerServer.execute(DefaultWorkerServer.java:63) + at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:66) + at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:62) + at org.gradle.internal.classloader.ClassLoaderUtils.executeInClassloader(ClassLoaderUtils.java:100) + at org.gradle.workers.internal.NoIsolationWorkerFactory$1.lambda$execute$0(NoIsolationWorkerFactory.java:62) + at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:44) + at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:41) + at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:209) + at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204) + at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66) + at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59) + at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:166) + at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59) + at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53) + at org.gradle.workers.internal.AbstractWorker.executeWrappedInBuildOperation(AbstractWorker.java:41) + at org.gradle.workers.internal.NoIsolationWorkerFactory$1.execute(NoIsolationWorkerFactory.java:59) + at org.gradle.workers.internal.DefaultWorkerExecutor.lambda$submitWork$0(DefaultWorkerExecutor.java:174) + at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runExecution(DefaultConditionalExecutionQueue.java:194) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.access$700(DefaultConditionalExecutionQueue.java:127) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner$1.run(DefaultConditionalExecutionQueue.java:169) + at org.gradle.internal.Factories$1.create(Factories.java:31) + at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:263) + at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:127) + at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:132) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runBatch(DefaultConditionalExecutionQueue.java:164) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.run(DefaultConditionalExecutionQueue.java:133) + at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:539) + at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264) + at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64) + at org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:48) + at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136) + at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635) + at java.base/java.lang.Thread.run(Thread.java:842) +Caused by: java.lang.AssertionError: java.lang.Exception: Could not close incremental caches in F:\Project\Elevate-Tracking-App\build\package_info_plus\kotlin\compileDebugKotlin\cacheable\caches-jvm\jvm\kotlin: class-fq-name-to-source.tab, source-to-classes.tab, internal-name-to-source.tab + at org.jetbrains.kotlin.com.google.common.io.Closer.close(Closer.java:236) + at org.jetbrains.kotlin.incremental.IncrementalCachesManager.close(IncrementalCachesManager.kt:55) + at kotlin.io.CloseableKt.closeFinally(Closeable.kt:56) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileNonIncrementally(IncrementalCompilerRunner.kt:293) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:129) + at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:674) + at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:91) + at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1659) + at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) + at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) + at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) + at java.base/java.lang.reflect.Method.invoke(Method.java:568) + at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:360) + at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:200) + at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:197) + at java.base/java.security.AccessController.doPrivileged(AccessController.java:712) + at java.rmi/sun.rmi.transport.Transport.serviceCall(Transport.java:196) + at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:587) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:828) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:705) + at java.base/java.security.AccessController.doPrivileged(AccessController.java:399) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:704) + ... 3 more +Caused by: java.lang.Exception: Could not close incremental caches in F:\Project\Elevate-Tracking-App\build\package_info_plus\kotlin\compileDebugKotlin\cacheable\caches-jvm\jvm\kotlin: class-fq-name-to-source.tab, source-to-classes.tab, internal-name-to-source.tab + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner.forEachMapSafe(BasicMapsOwner.kt:95) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner.close(BasicMapsOwner.kt:53) + at org.jetbrains.kotlin.com.google.common.io.Closer.close(Closer.java:223) + ... 24 more + Suppressed: java.lang.IllegalArgumentException: this and base files have different roots: C:\Users\wwwmo\AppData\Local\Pub\Cache\hosted\pub.dev\package_info_plus-8.3.1\android\src\main\kotlin\dev\fluttercommunity\plus\packageinfo\PackageInfoPlugin.kt and F:\Project\Elevate-Tracking-App\android. + at kotlin.io.FilesKt__UtilsKt.toRelativeString(Utils.kt:117) + at kotlin.io.FilesKt__UtilsKt.relativeTo(Utils.kt:128) + at org.jetbrains.kotlin.incremental.storage.RelocatableFileToPathConverter.toPath(RelocatableFileToPathConverter.kt:24) + at org.jetbrains.kotlin.incremental.storage.FileDescriptor.save(FileToPathConverter.kt:33) + at org.jetbrains.kotlin.incremental.storage.FileDescriptor.save(FileToPathConverter.kt:30) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.doPut(PersistentMapImpl.java:443) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.put(PersistentMapImpl.java:422) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentHashMap.put(PersistentHashMap.java:105) + at org.jetbrains.kotlin.incremental.storage.LazyStorage.set(LazyStorage.kt:80) + at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.applyChanges(InMemoryStorage.kt:108) + at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.close(InMemoryStorage.kt:136) + at org.jetbrains.kotlin.incremental.storage.PersistentStorageWrapper.close(PersistentStorage.kt:124) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner.forEachMapSafe(BasicMapsOwner.kt:87) + ... 26 more + Suppressed: java.lang.IllegalArgumentException: this and base files have different roots: C:\Users\wwwmo\AppData\Local\Pub\Cache\hosted\pub.dev\package_info_plus-8.3.1\android\src\main\kotlin\dev\fluttercommunity\plus\packageinfo\PackageInfoPlugin.kt and F:\Project\Elevate-Tracking-App\android. + at kotlin.io.FilesKt__UtilsKt.toRelativeString(Utils.kt:117) + at kotlin.io.FilesKt__UtilsKt.relativeTo(Utils.kt:128) + at org.jetbrains.kotlin.incremental.storage.RelocatableFileToPathConverter.toPath(RelocatableFileToPathConverter.kt:24) + at org.jetbrains.kotlin.incremental.storage.FileDescriptor.getHashCode(FileToPathConverter.kt:50) + at org.jetbrains.kotlin.incremental.storage.FileDescriptor.getHashCode(FileToPathConverter.kt:30) + at org.jetbrains.kotlin.com.intellij.util.containers.LinkedCustomHashMap.hashKey(LinkedCustomHashMap.java:109) + at org.jetbrains.kotlin.com.intellij.util.containers.LinkedCustomHashMap.remove(LinkedCustomHashMap.java:153) + at org.jetbrains.kotlin.com.intellij.util.containers.SLRUMap.remove(SLRUMap.java:89) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.flushAppendCache(PersistentMapImpl.java:999) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.doPut(PersistentMapImpl.java:451) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.put(PersistentMapImpl.java:422) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentHashMap.put(PersistentHashMap.java:105) + at org.jetbrains.kotlin.incremental.storage.LazyStorage.set(LazyStorage.kt:80) + at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.applyChanges(InMemoryStorage.kt:108) + at org.jetbrains.kotlin.incremental.storage.AppendableInMemoryStorage.applyChanges(InMemoryStorage.kt:179) + at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.close(InMemoryStorage.kt:136) + at org.jetbrains.kotlin.incremental.storage.AppendableSetBasicMap.close(BasicMap.kt:157) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner.forEachMapSafe(BasicMapsOwner.kt:87) + ... 26 more + Suppressed: java.lang.IllegalArgumentException: this and base files have different roots: C:\Users\wwwmo\AppData\Local\Pub\Cache\hosted\pub.dev\package_info_plus-8.3.1\android\src\main\kotlin\dev\fluttercommunity\plus\packageinfo\PackageInfoPlugin.kt and F:\Project\Elevate-Tracking-App\android. + at kotlin.io.FilesKt__UtilsKt.toRelativeString(Utils.kt:117) + at kotlin.io.FilesKt__UtilsKt.relativeTo(Utils.kt:128) + at org.jetbrains.kotlin.incremental.storage.RelocatableFileToPathConverter.toPath(RelocatableFileToPathConverter.kt:24) + at org.jetbrains.kotlin.incremental.storage.FileDescriptor.save(FileToPathConverter.kt:33) + at org.jetbrains.kotlin.incremental.storage.FileDescriptor.save(FileToPathConverter.kt:30) + at org.jetbrains.kotlin.incremental.storage.AppendableCollectionExternalizer.save(LazyStorage.kt:151) + at org.jetbrains.kotlin.incremental.storage.AppendableCollectionExternalizer.save(LazyStorage.kt:142) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.doPut(PersistentMapImpl.java:443) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.put(PersistentMapImpl.java:422) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentHashMap.put(PersistentHashMap.java:105) + at org.jetbrains.kotlin.incremental.storage.LazyStorage.set(LazyStorage.kt:80) + at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.applyChanges(InMemoryStorage.kt:108) + at org.jetbrains.kotlin.incremental.storage.AppendableInMemoryStorage.applyChanges(InMemoryStorage.kt:179) + at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.close(InMemoryStorage.kt:136) + at org.jetbrains.kotlin.incremental.storage.PersistentStorageWrapper.close(PersistentStorage.kt:124) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner.forEachMapSafe(BasicMapsOwner.kt:87) + ... 26 more + Suppressed: java.lang.Exception: Could not close incremental caches in F:\Project\Elevate-Tracking-App\build\package_info_plus\kotlin\compileDebugKotlin\cacheable\caches-jvm\lookups: id-to-file.tab, file-to-id.tab + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner.forEachMapSafe(BasicMapsOwner.kt:95) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner.close(BasicMapsOwner.kt:53) + at org.jetbrains.kotlin.incremental.LookupStorage.close(LookupStorage.kt:155) + ... 25 more + Suppressed: java.lang.IllegalArgumentException: this and base files have different roots: C:\Users\wwwmo\AppData\Local\Pub\Cache\hosted\pub.dev\package_info_plus-8.3.1\android\src\main\kotlin\dev\fluttercommunity\plus\packageinfo\PackageInfoPlugin.kt and F:\Project\Elevate-Tracking-App\android. + at kotlin.io.FilesKt__UtilsKt.toRelativeString(Utils.kt:117) + at kotlin.io.FilesKt__UtilsKt.relativeTo(Utils.kt:128) + at org.jetbrains.kotlin.incremental.storage.RelocatableFileToPathConverter.toPath(RelocatableFileToPathConverter.kt:24) + at org.jetbrains.kotlin.incremental.storage.LegacyFileExternalizer.save(IdToFileMap.kt:51) + at org.jetbrains.kotlin.incremental.storage.LegacyFileExternalizer.save(IdToFileMap.kt:48) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.doPut(PersistentMapImpl.java:443) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.put(PersistentMapImpl.java:422) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentHashMap.put(PersistentHashMap.java:105) + at org.jetbrains.kotlin.incremental.storage.LazyStorage.set(LazyStorage.kt:80) + at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.applyChanges(InMemoryStorage.kt:108) + at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.close(InMemoryStorage.kt:136) + at org.jetbrains.kotlin.incremental.storage.PersistentStorageWrapper.close(PersistentStorage.kt:124) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner.forEachMapSafe(BasicMapsOwner.kt:87) + ... 27 more + Suppressed: java.lang.IllegalArgumentException: this and base files have different roots: C:\Users\wwwmo\AppData\Local\Pub\Cache\hosted\pub.dev\package_info_plus-8.3.1\android\src\main\kotlin\dev\fluttercommunity\plus\packageinfo\PackageInfoPlugin.kt and F:\Project\Elevate-Tracking-App\android. + at kotlin.io.FilesKt__UtilsKt.toRelativeString(Utils.kt:117) + at kotlin.io.FilesKt__UtilsKt.relativeTo(Utils.kt:128) + at org.jetbrains.kotlin.incremental.storage.RelocatableFileToPathConverter.toPath(RelocatableFileToPathConverter.kt:24) + at org.jetbrains.kotlin.incremental.storage.FileDescriptor.getHashCode(FileToPathConverter.kt:50) + at org.jetbrains.kotlin.incremental.storage.FileDescriptor.getHashCode(FileToPathConverter.kt:30) + at org.jetbrains.kotlin.com.intellij.util.containers.LinkedCustomHashMap.hashKey(LinkedCustomHashMap.java:109) + at org.jetbrains.kotlin.com.intellij.util.containers.LinkedCustomHashMap.remove(LinkedCustomHashMap.java:153) + at org.jetbrains.kotlin.com.intellij.util.containers.SLRUMap.remove(SLRUMap.java:89) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.flushAppendCache(PersistentMapImpl.java:999) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.doPut(PersistentMapImpl.java:451) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.put(PersistentMapImpl.java:422) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentHashMap.put(PersistentHashMap.java:105) + at org.jetbrains.kotlin.incremental.storage.LazyStorage.set(LazyStorage.kt:80) + at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.applyChanges(InMemoryStorage.kt:108) + at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.close(InMemoryStorage.kt:136) + at org.jetbrains.kotlin.incremental.storage.PersistentStorageWrapper.close(PersistentStorage.kt:124) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner.forEachMapSafe(BasicMapsOwner.kt:87) + ... 27 more + Suppressed: java.lang.Exception: Could not close incremental caches in F:\Project\Elevate-Tracking-App\build\package_info_plus\kotlin\compileDebugKotlin\cacheable\caches-jvm\inputs: source-to-output.tab + ... 27 more + Suppressed: java.lang.IllegalArgumentException: this and base files have different roots: C:\Users\wwwmo\AppData\Local\Pub\Cache\hosted\pub.dev\package_info_plus-8.3.1\android\src\main\kotlin\dev\fluttercommunity\plus\packageinfo\PackageInfoPlugin.kt and F:\Project\Elevate-Tracking-App\android. + at kotlin.io.FilesKt__UtilsKt.toRelativeString(Utils.kt:117) + at kotlin.io.FilesKt__UtilsKt.relativeTo(Utils.kt:128) + at org.jetbrains.kotlin.incremental.storage.RelocatableFileToPathConverter.toPath(RelocatableFileToPathConverter.kt:24) + at org.jetbrains.kotlin.incremental.storage.FileDescriptor.getHashCode(FileToPathConverter.kt:50) + at org.jetbrains.kotlin.incremental.storage.FileDescriptor.getHashCode(FileToPathConverter.kt:30) + at org.jetbrains.kotlin.com.intellij.util.containers.LinkedCustomHashMap.hashKey(LinkedCustomHashMap.java:109) + at org.jetbrains.kotlin.com.intellij.util.containers.LinkedCustomHashMap.remove(LinkedCustomHashMap.java:153) + at org.jetbrains.kotlin.com.intellij.util.containers.SLRUMap.remove(SLRUMap.java:89) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.flushAppendCache(PersistentMapImpl.java:999) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.doPut(PersistentMapImpl.java:451) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.put(PersistentMapImpl.java:422) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentHashMap.put(PersistentHashMap.java:105) + at org.jetbrains.kotlin.incremental.storage.LazyStorage.set(LazyStorage.kt:80) + at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.applyChanges(InMemoryStorage.kt:108) + at org.jetbrains.kotlin.incremental.storage.AppendableInMemoryStorage.applyChanges(InMemoryStorage.kt:179) + at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.close(InMemoryStorage.kt:136) + at org.jetbrains.kotlin.incremental.storage.AppendableSetBasicMap.close(BasicMap.kt:157) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner.forEachMapSafe(BasicMapsOwner.kt:87) + ... 26 more + + diff --git a/android/.kotlin/errors/errors-1758560907636.log b/android/.kotlin/errors/errors-1758560907636.log new file mode 100644 index 0000000..643233e --- /dev/null +++ b/android/.kotlin/errors/errors-1758560907636.log @@ -0,0 +1,200 @@ +kotlin version: 2.1.0 +error message: Daemon compilation failed: null +java.lang.Exception + at org.jetbrains.kotlin.daemon.common.CompileService$CallResult$Error.get(CompileService.kt:69) + at org.jetbrains.kotlin.daemon.common.CompileService$CallResult$Error.get(CompileService.kt:65) + at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemon(GradleKotlinCompilerWork.kt:240) + at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemonOrFallbackImpl(GradleKotlinCompilerWork.kt:159) + at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.run(GradleKotlinCompilerWork.kt:111) + at org.jetbrains.kotlin.compilerRunner.GradleCompilerRunnerWithWorkers$GradleKotlinCompilerWorkAction.execute(GradleCompilerRunnerWithWorkers.kt:76) + at org.gradle.workers.internal.DefaultWorkerServer.execute(DefaultWorkerServer.java:63) + at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:66) + at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:62) + at org.gradle.internal.classloader.ClassLoaderUtils.executeInClassloader(ClassLoaderUtils.java:100) + at org.gradle.workers.internal.NoIsolationWorkerFactory$1.lambda$execute$0(NoIsolationWorkerFactory.java:62) + at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:44) + at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:41) + at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:209) + at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204) + at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66) + at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59) + at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:166) + at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59) + at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53) + at org.gradle.workers.internal.AbstractWorker.executeWrappedInBuildOperation(AbstractWorker.java:41) + at org.gradle.workers.internal.NoIsolationWorkerFactory$1.execute(NoIsolationWorkerFactory.java:59) + at org.gradle.workers.internal.DefaultWorkerExecutor.lambda$submitWork$0(DefaultWorkerExecutor.java:174) + at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runExecution(DefaultConditionalExecutionQueue.java:194) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.access$700(DefaultConditionalExecutionQueue.java:127) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner$1.run(DefaultConditionalExecutionQueue.java:169) + at org.gradle.internal.Factories$1.create(Factories.java:31) + at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:263) + at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:127) + at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:132) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runBatch(DefaultConditionalExecutionQueue.java:164) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.run(DefaultConditionalExecutionQueue.java:133) + at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:539) + at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264) + at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64) + at org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:48) + at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136) + at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635) + at java.base/java.lang.Thread.run(Thread.java:842) +Caused by: java.lang.AssertionError: java.lang.Exception: Could not close incremental caches in F:\Project\Elevate-Tracking-App\build\shared_preferences_android\kotlin\compileDebugKotlin\cacheable\caches-jvm\jvm\kotlin: class-fq-name-to-source.tab, source-to-classes.tab, internal-name-to-source.tab + at org.jetbrains.kotlin.com.google.common.io.Closer.close(Closer.java:236) + at org.jetbrains.kotlin.incremental.IncrementalCachesManager.close(IncrementalCachesManager.kt:55) + at kotlin.io.CloseableKt.closeFinally(Closeable.kt:56) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileNonIncrementally(IncrementalCompilerRunner.kt:293) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:129) + at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:674) + at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:91) + at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1659) + at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) + at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) + at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) + at java.base/java.lang.reflect.Method.invoke(Method.java:568) + at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:360) + at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:200) + at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:197) + at java.base/java.security.AccessController.doPrivileged(AccessController.java:712) + at java.rmi/sun.rmi.transport.Transport.serviceCall(Transport.java:196) + at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:587) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:828) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:705) + at java.base/java.security.AccessController.doPrivileged(AccessController.java:399) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:704) + ... 3 more +Caused by: java.lang.Exception: Could not close incremental caches in F:\Project\Elevate-Tracking-App\build\shared_preferences_android\kotlin\compileDebugKotlin\cacheable\caches-jvm\jvm\kotlin: class-fq-name-to-source.tab, source-to-classes.tab, internal-name-to-source.tab + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner.forEachMapSafe(BasicMapsOwner.kt:95) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner.close(BasicMapsOwner.kt:53) + at org.jetbrains.kotlin.com.google.common.io.Closer.close(Closer.java:223) + ... 24 more + Suppressed: java.lang.IllegalArgumentException: this and base files have different roots: C:\Users\wwwmo\AppData\Local\Pub\Cache\hosted\pub.dev\shared_preferences_android-2.4.12\android\src\main\kotlin\io\flutter\plugins\sharedpreferences\MessagesAsync.g.kt and F:\Project\Elevate-Tracking-App\android. + at kotlin.io.FilesKt__UtilsKt.toRelativeString(Utils.kt:117) + at kotlin.io.FilesKt__UtilsKt.relativeTo(Utils.kt:128) + at org.jetbrains.kotlin.incremental.storage.RelocatableFileToPathConverter.toPath(RelocatableFileToPathConverter.kt:24) + at org.jetbrains.kotlin.incremental.storage.FileDescriptor.save(FileToPathConverter.kt:33) + at org.jetbrains.kotlin.incremental.storage.FileDescriptor.save(FileToPathConverter.kt:30) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.doPut(PersistentMapImpl.java:443) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.put(PersistentMapImpl.java:422) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentHashMap.put(PersistentHashMap.java:105) + at org.jetbrains.kotlin.incremental.storage.LazyStorage.set(LazyStorage.kt:80) + at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.applyChanges(InMemoryStorage.kt:108) + at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.close(InMemoryStorage.kt:136) + at org.jetbrains.kotlin.incremental.storage.PersistentStorageWrapper.close(PersistentStorage.kt:124) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner.forEachMapSafe(BasicMapsOwner.kt:87) + ... 26 more + Suppressed: java.lang.IllegalArgumentException: this and base files have different roots: C:\Users\wwwmo\AppData\Local\Pub\Cache\hosted\pub.dev\shared_preferences_android-2.4.12\android\src\main\kotlin\io\flutter\plugins\sharedpreferences\MessagesAsync.g.kt and F:\Project\Elevate-Tracking-App\android. + at kotlin.io.FilesKt__UtilsKt.toRelativeString(Utils.kt:117) + at kotlin.io.FilesKt__UtilsKt.relativeTo(Utils.kt:128) + at org.jetbrains.kotlin.incremental.storage.RelocatableFileToPathConverter.toPath(RelocatableFileToPathConverter.kt:24) + at org.jetbrains.kotlin.incremental.storage.FileDescriptor.getHashCode(FileToPathConverter.kt:50) + at org.jetbrains.kotlin.incremental.storage.FileDescriptor.getHashCode(FileToPathConverter.kt:30) + at org.jetbrains.kotlin.com.intellij.util.containers.LinkedCustomHashMap.hashKey(LinkedCustomHashMap.java:109) + at org.jetbrains.kotlin.com.intellij.util.containers.LinkedCustomHashMap.remove(LinkedCustomHashMap.java:153) + at org.jetbrains.kotlin.com.intellij.util.containers.SLRUMap.remove(SLRUMap.java:89) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.flushAppendCache(PersistentMapImpl.java:999) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.doPut(PersistentMapImpl.java:451) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.put(PersistentMapImpl.java:422) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentHashMap.put(PersistentHashMap.java:105) + at org.jetbrains.kotlin.incremental.storage.LazyStorage.set(LazyStorage.kt:80) + at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.applyChanges(InMemoryStorage.kt:108) + at org.jetbrains.kotlin.incremental.storage.AppendableInMemoryStorage.applyChanges(InMemoryStorage.kt:179) + at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.close(InMemoryStorage.kt:136) + at org.jetbrains.kotlin.incremental.storage.AppendableSetBasicMap.close(BasicMap.kt:157) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner.forEachMapSafe(BasicMapsOwner.kt:87) + ... 26 more + Suppressed: java.lang.IllegalArgumentException: this and base files have different roots: C:\Users\wwwmo\AppData\Local\Pub\Cache\hosted\pub.dev\shared_preferences_android-2.4.12\android\src\main\kotlin\io\flutter\plugins\sharedpreferences\MessagesAsync.g.kt and F:\Project\Elevate-Tracking-App\android. + at kotlin.io.FilesKt__UtilsKt.toRelativeString(Utils.kt:117) + at kotlin.io.FilesKt__UtilsKt.relativeTo(Utils.kt:128) + at org.jetbrains.kotlin.incremental.storage.RelocatableFileToPathConverter.toPath(RelocatableFileToPathConverter.kt:24) + at org.jetbrains.kotlin.incremental.storage.FileDescriptor.save(FileToPathConverter.kt:33) + at org.jetbrains.kotlin.incremental.storage.FileDescriptor.save(FileToPathConverter.kt:30) + at org.jetbrains.kotlin.incremental.storage.AppendableCollectionExternalizer.save(LazyStorage.kt:151) + at org.jetbrains.kotlin.incremental.storage.AppendableCollectionExternalizer.save(LazyStorage.kt:142) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.doPut(PersistentMapImpl.java:443) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.put(PersistentMapImpl.java:422) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentHashMap.put(PersistentHashMap.java:105) + at org.jetbrains.kotlin.incremental.storage.LazyStorage.set(LazyStorage.kt:80) + at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.applyChanges(InMemoryStorage.kt:108) + at org.jetbrains.kotlin.incremental.storage.AppendableInMemoryStorage.applyChanges(InMemoryStorage.kt:179) + at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.close(InMemoryStorage.kt:136) + at org.jetbrains.kotlin.incremental.storage.PersistentStorageWrapper.close(PersistentStorage.kt:124) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner.forEachMapSafe(BasicMapsOwner.kt:87) + ... 26 more + Suppressed: java.lang.Exception: Could not close incremental caches in F:\Project\Elevate-Tracking-App\build\shared_preferences_android\kotlin\compileDebugKotlin\cacheable\caches-jvm\lookups: id-to-file.tab, file-to-id.tab + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner.forEachMapSafe(BasicMapsOwner.kt:95) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner.close(BasicMapsOwner.kt:53) + at org.jetbrains.kotlin.incremental.LookupStorage.close(LookupStorage.kt:155) + ... 25 more + Suppressed: java.lang.IllegalArgumentException: this and base files have different roots: C:\Users\wwwmo\AppData\Local\Pub\Cache\hosted\pub.dev\shared_preferences_android-2.4.12\android\src\main\kotlin\io\flutter\plugins\sharedpreferences\MessagesAsync.g.kt and F:\Project\Elevate-Tracking-App\android. + at kotlin.io.FilesKt__UtilsKt.toRelativeString(Utils.kt:117) + at kotlin.io.FilesKt__UtilsKt.relativeTo(Utils.kt:128) + at org.jetbrains.kotlin.incremental.storage.RelocatableFileToPathConverter.toPath(RelocatableFileToPathConverter.kt:24) + at org.jetbrains.kotlin.incremental.storage.LegacyFileExternalizer.save(IdToFileMap.kt:51) + at org.jetbrains.kotlin.incremental.storage.LegacyFileExternalizer.save(IdToFileMap.kt:48) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.doPut(PersistentMapImpl.java:443) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.put(PersistentMapImpl.java:422) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentHashMap.put(PersistentHashMap.java:105) + at org.jetbrains.kotlin.incremental.storage.LazyStorage.set(LazyStorage.kt:80) + at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.applyChanges(InMemoryStorage.kt:108) + at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.close(InMemoryStorage.kt:136) + at org.jetbrains.kotlin.incremental.storage.PersistentStorageWrapper.close(PersistentStorage.kt:124) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner.forEachMapSafe(BasicMapsOwner.kt:87) + ... 27 more + Suppressed: java.lang.IllegalArgumentException: this and base files have different roots: C:\Users\wwwmo\AppData\Local\Pub\Cache\hosted\pub.dev\shared_preferences_android-2.4.12\android\src\main\kotlin\io\flutter\plugins\sharedpreferences\MessagesAsync.g.kt and F:\Project\Elevate-Tracking-App\android. + at kotlin.io.FilesKt__UtilsKt.toRelativeString(Utils.kt:117) + at kotlin.io.FilesKt__UtilsKt.relativeTo(Utils.kt:128) + at org.jetbrains.kotlin.incremental.storage.RelocatableFileToPathConverter.toPath(RelocatableFileToPathConverter.kt:24) + at org.jetbrains.kotlin.incremental.storage.FileDescriptor.getHashCode(FileToPathConverter.kt:50) + at org.jetbrains.kotlin.incremental.storage.FileDescriptor.getHashCode(FileToPathConverter.kt:30) + at org.jetbrains.kotlin.com.intellij.util.containers.LinkedCustomHashMap.hashKey(LinkedCustomHashMap.java:109) + at org.jetbrains.kotlin.com.intellij.util.containers.LinkedCustomHashMap.remove(LinkedCustomHashMap.java:153) + at org.jetbrains.kotlin.com.intellij.util.containers.SLRUMap.remove(SLRUMap.java:89) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.flushAppendCache(PersistentMapImpl.java:999) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.doPut(PersistentMapImpl.java:451) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.put(PersistentMapImpl.java:422) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentHashMap.put(PersistentHashMap.java:105) + at org.jetbrains.kotlin.incremental.storage.LazyStorage.set(LazyStorage.kt:80) + at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.applyChanges(InMemoryStorage.kt:108) + at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.close(InMemoryStorage.kt:136) + at org.jetbrains.kotlin.incremental.storage.PersistentStorageWrapper.close(PersistentStorage.kt:124) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner.forEachMapSafe(BasicMapsOwner.kt:87) + ... 27 more + Suppressed: java.lang.Exception: Could not close incremental caches in F:\Project\Elevate-Tracking-App\build\shared_preferences_android\kotlin\compileDebugKotlin\cacheable\caches-jvm\inputs: source-to-output.tab + ... 27 more + Suppressed: java.lang.IllegalArgumentException: this and base files have different roots: C:\Users\wwwmo\AppData\Local\Pub\Cache\hosted\pub.dev\shared_preferences_android-2.4.12\android\src\main\kotlin\io\flutter\plugins\sharedpreferences\MessagesAsync.g.kt and F:\Project\Elevate-Tracking-App\android. + at kotlin.io.FilesKt__UtilsKt.toRelativeString(Utils.kt:117) + at kotlin.io.FilesKt__UtilsKt.relativeTo(Utils.kt:128) + at org.jetbrains.kotlin.incremental.storage.RelocatableFileToPathConverter.toPath(RelocatableFileToPathConverter.kt:24) + at org.jetbrains.kotlin.incremental.storage.FileDescriptor.getHashCode(FileToPathConverter.kt:50) + at org.jetbrains.kotlin.incremental.storage.FileDescriptor.getHashCode(FileToPathConverter.kt:30) + at org.jetbrains.kotlin.com.intellij.util.containers.LinkedCustomHashMap.hashKey(LinkedCustomHashMap.java:109) + at org.jetbrains.kotlin.com.intellij.util.containers.LinkedCustomHashMap.remove(LinkedCustomHashMap.java:153) + at org.jetbrains.kotlin.com.intellij.util.containers.SLRUMap.remove(SLRUMap.java:89) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.flushAppendCache(PersistentMapImpl.java:999) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.doPut(PersistentMapImpl.java:451) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.put(PersistentMapImpl.java:422) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentHashMap.put(PersistentHashMap.java:105) + at org.jetbrains.kotlin.incremental.storage.LazyStorage.set(LazyStorage.kt:80) + at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.applyChanges(InMemoryStorage.kt:108) + at org.jetbrains.kotlin.incremental.storage.AppendableInMemoryStorage.applyChanges(InMemoryStorage.kt:179) + at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.close(InMemoryStorage.kt:136) + at org.jetbrains.kotlin.incremental.storage.AppendableSetBasicMap.close(BasicMap.kt:157) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner.forEachMapSafe(BasicMapsOwner.kt:87) + ... 26 more + + diff --git a/android/.kotlin/errors/errors-1758634868199.log b/android/.kotlin/errors/errors-1758634868199.log new file mode 100644 index 0000000..6047329 --- /dev/null +++ b/android/.kotlin/errors/errors-1758634868199.log @@ -0,0 +1,200 @@ +kotlin version: 2.1.0 +error message: Daemon compilation failed: null +java.lang.Exception + at org.jetbrains.kotlin.daemon.common.CompileService$CallResult$Error.get(CompileService.kt:69) + at org.jetbrains.kotlin.daemon.common.CompileService$CallResult$Error.get(CompileService.kt:65) + at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemon(GradleKotlinCompilerWork.kt:240) + at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemonOrFallbackImpl(GradleKotlinCompilerWork.kt:159) + at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.run(GradleKotlinCompilerWork.kt:111) + at org.jetbrains.kotlin.compilerRunner.GradleCompilerRunnerWithWorkers$GradleKotlinCompilerWorkAction.execute(GradleCompilerRunnerWithWorkers.kt:76) + at org.gradle.workers.internal.DefaultWorkerServer.execute(DefaultWorkerServer.java:63) + at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:66) + at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:62) + at org.gradle.internal.classloader.ClassLoaderUtils.executeInClassloader(ClassLoaderUtils.java:100) + at org.gradle.workers.internal.NoIsolationWorkerFactory$1.lambda$execute$0(NoIsolationWorkerFactory.java:62) + at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:44) + at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:41) + at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:209) + at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204) + at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66) + at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59) + at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:166) + at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59) + at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53) + at org.gradle.workers.internal.AbstractWorker.executeWrappedInBuildOperation(AbstractWorker.java:41) + at org.gradle.workers.internal.NoIsolationWorkerFactory$1.execute(NoIsolationWorkerFactory.java:59) + at org.gradle.workers.internal.DefaultWorkerExecutor.lambda$submitWork$0(DefaultWorkerExecutor.java:174) + at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runExecution(DefaultConditionalExecutionQueue.java:194) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.access$700(DefaultConditionalExecutionQueue.java:127) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner$1.run(DefaultConditionalExecutionQueue.java:169) + at org.gradle.internal.Factories$1.create(Factories.java:31) + at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:263) + at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:127) + at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:132) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runBatch(DefaultConditionalExecutionQueue.java:164) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.run(DefaultConditionalExecutionQueue.java:133) + at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:539) + at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264) + at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64) + at org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:48) + at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136) + at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635) + at java.base/java.lang.Thread.run(Thread.java:842) +Caused by: java.lang.AssertionError: java.lang.Exception: Could not close incremental caches in F:\Project\Elevate-Tracking-App\build\package_info_plus\kotlin\compileDebugKotlin\cacheable\caches-jvm\jvm\kotlin: class-fq-name-to-source.tab, source-to-classes.tab, internal-name-to-source.tab + at org.jetbrains.kotlin.com.google.common.io.Closer.close(Closer.java:236) + at org.jetbrains.kotlin.incremental.IncrementalCachesManager.close(IncrementalCachesManager.kt:55) + at kotlin.io.CloseableKt.closeFinally(Closeable.kt:56) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileNonIncrementally(IncrementalCompilerRunner.kt:293) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:129) + at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:674) + at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:91) + at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1659) + at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) + at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) + at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) + at java.base/java.lang.reflect.Method.invoke(Method.java:568) + at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:360) + at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:200) + at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:197) + at java.base/java.security.AccessController.doPrivileged(AccessController.java:712) + at java.rmi/sun.rmi.transport.Transport.serviceCall(Transport.java:196) + at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:587) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:828) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:705) + at java.base/java.security.AccessController.doPrivileged(AccessController.java:399) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:704) + ... 3 more +Caused by: java.lang.Exception: Could not close incremental caches in F:\Project\Elevate-Tracking-App\build\package_info_plus\kotlin\compileDebugKotlin\cacheable\caches-jvm\jvm\kotlin: class-fq-name-to-source.tab, source-to-classes.tab, internal-name-to-source.tab + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner.forEachMapSafe(BasicMapsOwner.kt:95) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner.close(BasicMapsOwner.kt:53) + at org.jetbrains.kotlin.com.google.common.io.Closer.close(Closer.java:223) + ... 24 more + Suppressed: java.lang.IllegalArgumentException: this and base files have different roots: C:\Users\wwwmo\AppData\Local\Pub\Cache\hosted\pub.dev\package_info_plus-8.3.1\android\src\main\kotlin\dev\fluttercommunity\plus\packageinfo\PackageInfoPlugin.kt and F:\Project\Elevate-Tracking-App\android. + at kotlin.io.FilesKt__UtilsKt.toRelativeString(Utils.kt:117) + at kotlin.io.FilesKt__UtilsKt.relativeTo(Utils.kt:128) + at org.jetbrains.kotlin.incremental.storage.RelocatableFileToPathConverter.toPath(RelocatableFileToPathConverter.kt:24) + at org.jetbrains.kotlin.incremental.storage.FileDescriptor.save(FileToPathConverter.kt:33) + at org.jetbrains.kotlin.incremental.storage.FileDescriptor.save(FileToPathConverter.kt:30) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.doPut(PersistentMapImpl.java:443) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.put(PersistentMapImpl.java:422) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentHashMap.put(PersistentHashMap.java:105) + at org.jetbrains.kotlin.incremental.storage.LazyStorage.set(LazyStorage.kt:80) + at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.applyChanges(InMemoryStorage.kt:108) + at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.close(InMemoryStorage.kt:136) + at org.jetbrains.kotlin.incremental.storage.PersistentStorageWrapper.close(PersistentStorage.kt:124) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner.forEachMapSafe(BasicMapsOwner.kt:87) + ... 26 more + Suppressed: java.lang.IllegalArgumentException: this and base files have different roots: C:\Users\wwwmo\AppData\Local\Pub\Cache\hosted\pub.dev\package_info_plus-8.3.1\android\src\main\kotlin\dev\fluttercommunity\plus\packageinfo\PackageInfoPlugin.kt and F:\Project\Elevate-Tracking-App\android. + at kotlin.io.FilesKt__UtilsKt.toRelativeString(Utils.kt:117) + at kotlin.io.FilesKt__UtilsKt.relativeTo(Utils.kt:128) + at org.jetbrains.kotlin.incremental.storage.RelocatableFileToPathConverter.toPath(RelocatableFileToPathConverter.kt:24) + at org.jetbrains.kotlin.incremental.storage.FileDescriptor.getHashCode(FileToPathConverter.kt:50) + at org.jetbrains.kotlin.incremental.storage.FileDescriptor.getHashCode(FileToPathConverter.kt:30) + at org.jetbrains.kotlin.com.intellij.util.containers.LinkedCustomHashMap.hashKey(LinkedCustomHashMap.java:109) + at org.jetbrains.kotlin.com.intellij.util.containers.LinkedCustomHashMap.remove(LinkedCustomHashMap.java:153) + at org.jetbrains.kotlin.com.intellij.util.containers.SLRUMap.remove(SLRUMap.java:89) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.flushAppendCache(PersistentMapImpl.java:999) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.doPut(PersistentMapImpl.java:451) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.put(PersistentMapImpl.java:422) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentHashMap.put(PersistentHashMap.java:105) + at org.jetbrains.kotlin.incremental.storage.LazyStorage.set(LazyStorage.kt:80) + at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.applyChanges(InMemoryStorage.kt:108) + at org.jetbrains.kotlin.incremental.storage.AppendableInMemoryStorage.applyChanges(InMemoryStorage.kt:179) + at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.close(InMemoryStorage.kt:136) + at org.jetbrains.kotlin.incremental.storage.AppendableSetBasicMap.close(BasicMap.kt:157) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner.forEachMapSafe(BasicMapsOwner.kt:87) + ... 26 more + Suppressed: java.lang.IllegalArgumentException: this and base files have different roots: C:\Users\wwwmo\AppData\Local\Pub\Cache\hosted\pub.dev\package_info_plus-8.3.1\android\src\main\kotlin\dev\fluttercommunity\plus\packageinfo\PackageInfoPlugin.kt and F:\Project\Elevate-Tracking-App\android. + at kotlin.io.FilesKt__UtilsKt.toRelativeString(Utils.kt:117) + at kotlin.io.FilesKt__UtilsKt.relativeTo(Utils.kt:128) + at org.jetbrains.kotlin.incremental.storage.RelocatableFileToPathConverter.toPath(RelocatableFileToPathConverter.kt:24) + at org.jetbrains.kotlin.incremental.storage.FileDescriptor.save(FileToPathConverter.kt:33) + at org.jetbrains.kotlin.incremental.storage.FileDescriptor.save(FileToPathConverter.kt:30) + at org.jetbrains.kotlin.incremental.storage.AppendableCollectionExternalizer.save(LazyStorage.kt:151) + at org.jetbrains.kotlin.incremental.storage.AppendableCollectionExternalizer.save(LazyStorage.kt:142) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.doPut(PersistentMapImpl.java:443) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.put(PersistentMapImpl.java:422) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentHashMap.put(PersistentHashMap.java:105) + at org.jetbrains.kotlin.incremental.storage.LazyStorage.set(LazyStorage.kt:80) + at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.applyChanges(InMemoryStorage.kt:108) + at org.jetbrains.kotlin.incremental.storage.AppendableInMemoryStorage.applyChanges(InMemoryStorage.kt:179) + at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.close(InMemoryStorage.kt:136) + at org.jetbrains.kotlin.incremental.storage.PersistentStorageWrapper.close(PersistentStorage.kt:124) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner.forEachMapSafe(BasicMapsOwner.kt:87) + ... 26 more + Suppressed: java.lang.Exception: Could not close incremental caches in F:\Project\Elevate-Tracking-App\build\package_info_plus\kotlin\compileDebugKotlin\cacheable\caches-jvm\lookups: id-to-file.tab, file-to-id.tab + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner.forEachMapSafe(BasicMapsOwner.kt:95) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner.close(BasicMapsOwner.kt:53) + at org.jetbrains.kotlin.incremental.LookupStorage.close(LookupStorage.kt:155) + ... 25 more + Suppressed: java.lang.IllegalArgumentException: this and base files have different roots: C:\Users\wwwmo\AppData\Local\Pub\Cache\hosted\pub.dev\package_info_plus-8.3.1\android\src\main\kotlin\dev\fluttercommunity\plus\packageinfo\PackageInfoPlugin.kt and F:\Project\Elevate-Tracking-App\android. + at kotlin.io.FilesKt__UtilsKt.toRelativeString(Utils.kt:117) + at kotlin.io.FilesKt__UtilsKt.relativeTo(Utils.kt:128) + at org.jetbrains.kotlin.incremental.storage.RelocatableFileToPathConverter.toPath(RelocatableFileToPathConverter.kt:24) + at org.jetbrains.kotlin.incremental.storage.LegacyFileExternalizer.save(IdToFileMap.kt:51) + at org.jetbrains.kotlin.incremental.storage.LegacyFileExternalizer.save(IdToFileMap.kt:48) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.doPut(PersistentMapImpl.java:443) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.put(PersistentMapImpl.java:422) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentHashMap.put(PersistentHashMap.java:105) + at org.jetbrains.kotlin.incremental.storage.LazyStorage.set(LazyStorage.kt:80) + at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.applyChanges(InMemoryStorage.kt:108) + at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.close(InMemoryStorage.kt:136) + at org.jetbrains.kotlin.incremental.storage.PersistentStorageWrapper.close(PersistentStorage.kt:124) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner.forEachMapSafe(BasicMapsOwner.kt:87) + ... 27 more + Suppressed: java.lang.IllegalArgumentException: this and base files have different roots: C:\Users\wwwmo\AppData\Local\Pub\Cache\hosted\pub.dev\package_info_plus-8.3.1\android\src\main\kotlin\dev\fluttercommunity\plus\packageinfo\PackageInfoPlugin.kt and F:\Project\Elevate-Tracking-App\android. + at kotlin.io.FilesKt__UtilsKt.toRelativeString(Utils.kt:117) + at kotlin.io.FilesKt__UtilsKt.relativeTo(Utils.kt:128) + at org.jetbrains.kotlin.incremental.storage.RelocatableFileToPathConverter.toPath(RelocatableFileToPathConverter.kt:24) + at org.jetbrains.kotlin.incremental.storage.FileDescriptor.getHashCode(FileToPathConverter.kt:50) + at org.jetbrains.kotlin.incremental.storage.FileDescriptor.getHashCode(FileToPathConverter.kt:30) + at org.jetbrains.kotlin.com.intellij.util.containers.LinkedCustomHashMap.hashKey(LinkedCustomHashMap.java:109) + at org.jetbrains.kotlin.com.intellij.util.containers.LinkedCustomHashMap.remove(LinkedCustomHashMap.java:153) + at org.jetbrains.kotlin.com.intellij.util.containers.SLRUMap.remove(SLRUMap.java:89) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.flushAppendCache(PersistentMapImpl.java:999) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.doPut(PersistentMapImpl.java:451) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.put(PersistentMapImpl.java:422) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentHashMap.put(PersistentHashMap.java:105) + at org.jetbrains.kotlin.incremental.storage.LazyStorage.set(LazyStorage.kt:80) + at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.applyChanges(InMemoryStorage.kt:108) + at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.close(InMemoryStorage.kt:136) + at org.jetbrains.kotlin.incremental.storage.PersistentStorageWrapper.close(PersistentStorage.kt:124) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner.forEachMapSafe(BasicMapsOwner.kt:87) + ... 27 more + Suppressed: java.lang.Exception: Could not close incremental caches in F:\Project\Elevate-Tracking-App\build\package_info_plus\kotlin\compileDebugKotlin\cacheable\caches-jvm\inputs: source-to-output.tab + ... 27 more + Suppressed: java.lang.IllegalArgumentException: this and base files have different roots: C:\Users\wwwmo\AppData\Local\Pub\Cache\hosted\pub.dev\package_info_plus-8.3.1\android\src\main\kotlin\dev\fluttercommunity\plus\packageinfo\PackageInfoPlugin.kt and F:\Project\Elevate-Tracking-App\android. + at kotlin.io.FilesKt__UtilsKt.toRelativeString(Utils.kt:117) + at kotlin.io.FilesKt__UtilsKt.relativeTo(Utils.kt:128) + at org.jetbrains.kotlin.incremental.storage.RelocatableFileToPathConverter.toPath(RelocatableFileToPathConverter.kt:24) + at org.jetbrains.kotlin.incremental.storage.FileDescriptor.getHashCode(FileToPathConverter.kt:50) + at org.jetbrains.kotlin.incremental.storage.FileDescriptor.getHashCode(FileToPathConverter.kt:30) + at org.jetbrains.kotlin.com.intellij.util.containers.LinkedCustomHashMap.hashKey(LinkedCustomHashMap.java:109) + at org.jetbrains.kotlin.com.intellij.util.containers.LinkedCustomHashMap.remove(LinkedCustomHashMap.java:153) + at org.jetbrains.kotlin.com.intellij.util.containers.SLRUMap.remove(SLRUMap.java:89) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.flushAppendCache(PersistentMapImpl.java:999) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.doPut(PersistentMapImpl.java:451) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.put(PersistentMapImpl.java:422) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentHashMap.put(PersistentHashMap.java:105) + at org.jetbrains.kotlin.incremental.storage.LazyStorage.set(LazyStorage.kt:80) + at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.applyChanges(InMemoryStorage.kt:108) + at org.jetbrains.kotlin.incremental.storage.AppendableInMemoryStorage.applyChanges(InMemoryStorage.kt:179) + at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.close(InMemoryStorage.kt:136) + at org.jetbrains.kotlin.incremental.storage.AppendableSetBasicMap.close(BasicMap.kt:157) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner.forEachMapSafe(BasicMapsOwner.kt:87) + ... 26 more + + diff --git a/android/.kotlin/errors/errors-1758634868227.log b/android/.kotlin/errors/errors-1758634868227.log new file mode 100644 index 0000000..643233e --- /dev/null +++ b/android/.kotlin/errors/errors-1758634868227.log @@ -0,0 +1,200 @@ +kotlin version: 2.1.0 +error message: Daemon compilation failed: null +java.lang.Exception + at org.jetbrains.kotlin.daemon.common.CompileService$CallResult$Error.get(CompileService.kt:69) + at org.jetbrains.kotlin.daemon.common.CompileService$CallResult$Error.get(CompileService.kt:65) + at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemon(GradleKotlinCompilerWork.kt:240) + at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemonOrFallbackImpl(GradleKotlinCompilerWork.kt:159) + at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.run(GradleKotlinCompilerWork.kt:111) + at org.jetbrains.kotlin.compilerRunner.GradleCompilerRunnerWithWorkers$GradleKotlinCompilerWorkAction.execute(GradleCompilerRunnerWithWorkers.kt:76) + at org.gradle.workers.internal.DefaultWorkerServer.execute(DefaultWorkerServer.java:63) + at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:66) + at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:62) + at org.gradle.internal.classloader.ClassLoaderUtils.executeInClassloader(ClassLoaderUtils.java:100) + at org.gradle.workers.internal.NoIsolationWorkerFactory$1.lambda$execute$0(NoIsolationWorkerFactory.java:62) + at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:44) + at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:41) + at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:209) + at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204) + at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66) + at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59) + at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:166) + at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59) + at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53) + at org.gradle.workers.internal.AbstractWorker.executeWrappedInBuildOperation(AbstractWorker.java:41) + at org.gradle.workers.internal.NoIsolationWorkerFactory$1.execute(NoIsolationWorkerFactory.java:59) + at org.gradle.workers.internal.DefaultWorkerExecutor.lambda$submitWork$0(DefaultWorkerExecutor.java:174) + at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runExecution(DefaultConditionalExecutionQueue.java:194) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.access$700(DefaultConditionalExecutionQueue.java:127) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner$1.run(DefaultConditionalExecutionQueue.java:169) + at org.gradle.internal.Factories$1.create(Factories.java:31) + at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:263) + at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:127) + at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:132) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runBatch(DefaultConditionalExecutionQueue.java:164) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.run(DefaultConditionalExecutionQueue.java:133) + at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:539) + at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264) + at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64) + at org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:48) + at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136) + at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635) + at java.base/java.lang.Thread.run(Thread.java:842) +Caused by: java.lang.AssertionError: java.lang.Exception: Could not close incremental caches in F:\Project\Elevate-Tracking-App\build\shared_preferences_android\kotlin\compileDebugKotlin\cacheable\caches-jvm\jvm\kotlin: class-fq-name-to-source.tab, source-to-classes.tab, internal-name-to-source.tab + at org.jetbrains.kotlin.com.google.common.io.Closer.close(Closer.java:236) + at org.jetbrains.kotlin.incremental.IncrementalCachesManager.close(IncrementalCachesManager.kt:55) + at kotlin.io.CloseableKt.closeFinally(Closeable.kt:56) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileNonIncrementally(IncrementalCompilerRunner.kt:293) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:129) + at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:674) + at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:91) + at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1659) + at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) + at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) + at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) + at java.base/java.lang.reflect.Method.invoke(Method.java:568) + at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:360) + at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:200) + at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:197) + at java.base/java.security.AccessController.doPrivileged(AccessController.java:712) + at java.rmi/sun.rmi.transport.Transport.serviceCall(Transport.java:196) + at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:587) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:828) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:705) + at java.base/java.security.AccessController.doPrivileged(AccessController.java:399) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:704) + ... 3 more +Caused by: java.lang.Exception: Could not close incremental caches in F:\Project\Elevate-Tracking-App\build\shared_preferences_android\kotlin\compileDebugKotlin\cacheable\caches-jvm\jvm\kotlin: class-fq-name-to-source.tab, source-to-classes.tab, internal-name-to-source.tab + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner.forEachMapSafe(BasicMapsOwner.kt:95) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner.close(BasicMapsOwner.kt:53) + at org.jetbrains.kotlin.com.google.common.io.Closer.close(Closer.java:223) + ... 24 more + Suppressed: java.lang.IllegalArgumentException: this and base files have different roots: C:\Users\wwwmo\AppData\Local\Pub\Cache\hosted\pub.dev\shared_preferences_android-2.4.12\android\src\main\kotlin\io\flutter\plugins\sharedpreferences\MessagesAsync.g.kt and F:\Project\Elevate-Tracking-App\android. + at kotlin.io.FilesKt__UtilsKt.toRelativeString(Utils.kt:117) + at kotlin.io.FilesKt__UtilsKt.relativeTo(Utils.kt:128) + at org.jetbrains.kotlin.incremental.storage.RelocatableFileToPathConverter.toPath(RelocatableFileToPathConverter.kt:24) + at org.jetbrains.kotlin.incremental.storage.FileDescriptor.save(FileToPathConverter.kt:33) + at org.jetbrains.kotlin.incremental.storage.FileDescriptor.save(FileToPathConverter.kt:30) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.doPut(PersistentMapImpl.java:443) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.put(PersistentMapImpl.java:422) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentHashMap.put(PersistentHashMap.java:105) + at org.jetbrains.kotlin.incremental.storage.LazyStorage.set(LazyStorage.kt:80) + at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.applyChanges(InMemoryStorage.kt:108) + at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.close(InMemoryStorage.kt:136) + at org.jetbrains.kotlin.incremental.storage.PersistentStorageWrapper.close(PersistentStorage.kt:124) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner.forEachMapSafe(BasicMapsOwner.kt:87) + ... 26 more + Suppressed: java.lang.IllegalArgumentException: this and base files have different roots: C:\Users\wwwmo\AppData\Local\Pub\Cache\hosted\pub.dev\shared_preferences_android-2.4.12\android\src\main\kotlin\io\flutter\plugins\sharedpreferences\MessagesAsync.g.kt and F:\Project\Elevate-Tracking-App\android. + at kotlin.io.FilesKt__UtilsKt.toRelativeString(Utils.kt:117) + at kotlin.io.FilesKt__UtilsKt.relativeTo(Utils.kt:128) + at org.jetbrains.kotlin.incremental.storage.RelocatableFileToPathConverter.toPath(RelocatableFileToPathConverter.kt:24) + at org.jetbrains.kotlin.incremental.storage.FileDescriptor.getHashCode(FileToPathConverter.kt:50) + at org.jetbrains.kotlin.incremental.storage.FileDescriptor.getHashCode(FileToPathConverter.kt:30) + at org.jetbrains.kotlin.com.intellij.util.containers.LinkedCustomHashMap.hashKey(LinkedCustomHashMap.java:109) + at org.jetbrains.kotlin.com.intellij.util.containers.LinkedCustomHashMap.remove(LinkedCustomHashMap.java:153) + at org.jetbrains.kotlin.com.intellij.util.containers.SLRUMap.remove(SLRUMap.java:89) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.flushAppendCache(PersistentMapImpl.java:999) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.doPut(PersistentMapImpl.java:451) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.put(PersistentMapImpl.java:422) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentHashMap.put(PersistentHashMap.java:105) + at org.jetbrains.kotlin.incremental.storage.LazyStorage.set(LazyStorage.kt:80) + at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.applyChanges(InMemoryStorage.kt:108) + at org.jetbrains.kotlin.incremental.storage.AppendableInMemoryStorage.applyChanges(InMemoryStorage.kt:179) + at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.close(InMemoryStorage.kt:136) + at org.jetbrains.kotlin.incremental.storage.AppendableSetBasicMap.close(BasicMap.kt:157) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner.forEachMapSafe(BasicMapsOwner.kt:87) + ... 26 more + Suppressed: java.lang.IllegalArgumentException: this and base files have different roots: C:\Users\wwwmo\AppData\Local\Pub\Cache\hosted\pub.dev\shared_preferences_android-2.4.12\android\src\main\kotlin\io\flutter\plugins\sharedpreferences\MessagesAsync.g.kt and F:\Project\Elevate-Tracking-App\android. + at kotlin.io.FilesKt__UtilsKt.toRelativeString(Utils.kt:117) + at kotlin.io.FilesKt__UtilsKt.relativeTo(Utils.kt:128) + at org.jetbrains.kotlin.incremental.storage.RelocatableFileToPathConverter.toPath(RelocatableFileToPathConverter.kt:24) + at org.jetbrains.kotlin.incremental.storage.FileDescriptor.save(FileToPathConverter.kt:33) + at org.jetbrains.kotlin.incremental.storage.FileDescriptor.save(FileToPathConverter.kt:30) + at org.jetbrains.kotlin.incremental.storage.AppendableCollectionExternalizer.save(LazyStorage.kt:151) + at org.jetbrains.kotlin.incremental.storage.AppendableCollectionExternalizer.save(LazyStorage.kt:142) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.doPut(PersistentMapImpl.java:443) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.put(PersistentMapImpl.java:422) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentHashMap.put(PersistentHashMap.java:105) + at org.jetbrains.kotlin.incremental.storage.LazyStorage.set(LazyStorage.kt:80) + at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.applyChanges(InMemoryStorage.kt:108) + at org.jetbrains.kotlin.incremental.storage.AppendableInMemoryStorage.applyChanges(InMemoryStorage.kt:179) + at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.close(InMemoryStorage.kt:136) + at org.jetbrains.kotlin.incremental.storage.PersistentStorageWrapper.close(PersistentStorage.kt:124) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner.forEachMapSafe(BasicMapsOwner.kt:87) + ... 26 more + Suppressed: java.lang.Exception: Could not close incremental caches in F:\Project\Elevate-Tracking-App\build\shared_preferences_android\kotlin\compileDebugKotlin\cacheable\caches-jvm\lookups: id-to-file.tab, file-to-id.tab + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner.forEachMapSafe(BasicMapsOwner.kt:95) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner.close(BasicMapsOwner.kt:53) + at org.jetbrains.kotlin.incremental.LookupStorage.close(LookupStorage.kt:155) + ... 25 more + Suppressed: java.lang.IllegalArgumentException: this and base files have different roots: C:\Users\wwwmo\AppData\Local\Pub\Cache\hosted\pub.dev\shared_preferences_android-2.4.12\android\src\main\kotlin\io\flutter\plugins\sharedpreferences\MessagesAsync.g.kt and F:\Project\Elevate-Tracking-App\android. + at kotlin.io.FilesKt__UtilsKt.toRelativeString(Utils.kt:117) + at kotlin.io.FilesKt__UtilsKt.relativeTo(Utils.kt:128) + at org.jetbrains.kotlin.incremental.storage.RelocatableFileToPathConverter.toPath(RelocatableFileToPathConverter.kt:24) + at org.jetbrains.kotlin.incremental.storage.LegacyFileExternalizer.save(IdToFileMap.kt:51) + at org.jetbrains.kotlin.incremental.storage.LegacyFileExternalizer.save(IdToFileMap.kt:48) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.doPut(PersistentMapImpl.java:443) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.put(PersistentMapImpl.java:422) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentHashMap.put(PersistentHashMap.java:105) + at org.jetbrains.kotlin.incremental.storage.LazyStorage.set(LazyStorage.kt:80) + at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.applyChanges(InMemoryStorage.kt:108) + at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.close(InMemoryStorage.kt:136) + at org.jetbrains.kotlin.incremental.storage.PersistentStorageWrapper.close(PersistentStorage.kt:124) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner.forEachMapSafe(BasicMapsOwner.kt:87) + ... 27 more + Suppressed: java.lang.IllegalArgumentException: this and base files have different roots: C:\Users\wwwmo\AppData\Local\Pub\Cache\hosted\pub.dev\shared_preferences_android-2.4.12\android\src\main\kotlin\io\flutter\plugins\sharedpreferences\MessagesAsync.g.kt and F:\Project\Elevate-Tracking-App\android. + at kotlin.io.FilesKt__UtilsKt.toRelativeString(Utils.kt:117) + at kotlin.io.FilesKt__UtilsKt.relativeTo(Utils.kt:128) + at org.jetbrains.kotlin.incremental.storage.RelocatableFileToPathConverter.toPath(RelocatableFileToPathConverter.kt:24) + at org.jetbrains.kotlin.incremental.storage.FileDescriptor.getHashCode(FileToPathConverter.kt:50) + at org.jetbrains.kotlin.incremental.storage.FileDescriptor.getHashCode(FileToPathConverter.kt:30) + at org.jetbrains.kotlin.com.intellij.util.containers.LinkedCustomHashMap.hashKey(LinkedCustomHashMap.java:109) + at org.jetbrains.kotlin.com.intellij.util.containers.LinkedCustomHashMap.remove(LinkedCustomHashMap.java:153) + at org.jetbrains.kotlin.com.intellij.util.containers.SLRUMap.remove(SLRUMap.java:89) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.flushAppendCache(PersistentMapImpl.java:999) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.doPut(PersistentMapImpl.java:451) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.put(PersistentMapImpl.java:422) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentHashMap.put(PersistentHashMap.java:105) + at org.jetbrains.kotlin.incremental.storage.LazyStorage.set(LazyStorage.kt:80) + at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.applyChanges(InMemoryStorage.kt:108) + at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.close(InMemoryStorage.kt:136) + at org.jetbrains.kotlin.incremental.storage.PersistentStorageWrapper.close(PersistentStorage.kt:124) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner.forEachMapSafe(BasicMapsOwner.kt:87) + ... 27 more + Suppressed: java.lang.Exception: Could not close incremental caches in F:\Project\Elevate-Tracking-App\build\shared_preferences_android\kotlin\compileDebugKotlin\cacheable\caches-jvm\inputs: source-to-output.tab + ... 27 more + Suppressed: java.lang.IllegalArgumentException: this and base files have different roots: C:\Users\wwwmo\AppData\Local\Pub\Cache\hosted\pub.dev\shared_preferences_android-2.4.12\android\src\main\kotlin\io\flutter\plugins\sharedpreferences\MessagesAsync.g.kt and F:\Project\Elevate-Tracking-App\android. + at kotlin.io.FilesKt__UtilsKt.toRelativeString(Utils.kt:117) + at kotlin.io.FilesKt__UtilsKt.relativeTo(Utils.kt:128) + at org.jetbrains.kotlin.incremental.storage.RelocatableFileToPathConverter.toPath(RelocatableFileToPathConverter.kt:24) + at org.jetbrains.kotlin.incremental.storage.FileDescriptor.getHashCode(FileToPathConverter.kt:50) + at org.jetbrains.kotlin.incremental.storage.FileDescriptor.getHashCode(FileToPathConverter.kt:30) + at org.jetbrains.kotlin.com.intellij.util.containers.LinkedCustomHashMap.hashKey(LinkedCustomHashMap.java:109) + at org.jetbrains.kotlin.com.intellij.util.containers.LinkedCustomHashMap.remove(LinkedCustomHashMap.java:153) + at org.jetbrains.kotlin.com.intellij.util.containers.SLRUMap.remove(SLRUMap.java:89) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.flushAppendCache(PersistentMapImpl.java:999) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.doPut(PersistentMapImpl.java:451) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.put(PersistentMapImpl.java:422) + at org.jetbrains.kotlin.com.intellij.util.io.PersistentHashMap.put(PersistentHashMap.java:105) + at org.jetbrains.kotlin.incremental.storage.LazyStorage.set(LazyStorage.kt:80) + at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.applyChanges(InMemoryStorage.kt:108) + at org.jetbrains.kotlin.incremental.storage.AppendableInMemoryStorage.applyChanges(InMemoryStorage.kt:179) + at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.close(InMemoryStorage.kt:136) + at org.jetbrains.kotlin.incremental.storage.AppendableSetBasicMap.close(BasicMap.kt:157) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53) + at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner.forEachMapSafe(BasicMapsOwner.kt:87) + ... 26 more + + diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index cef2e02..e2864c7 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,4 +1,9 @@ + + + + + + android:name="io.flutter.embedding.android.NormalTheme" + android:resource="@style/NormalTheme" + /> - - + + - - + + - + \ No newline at end of file diff --git a/assets/data/country.json b/assets/data/country.json new file mode 100644 index 0000000..b5f56a0 --- /dev/null +++ b/assets/data/country.json @@ -0,0 +1,232 @@ +[ + { + "isoCode": "EG", + "name": "Egypt", + "phoneCode": "20", + "flag": "🇪🇬", + "flagUrl": "https://flagcdn.com/w320/eg.png", + "currency": "EGP", + "latitude": "27.00000000", + "longitude": "30.00000000" + }, + { + "isoCode": "SA", + "name": "Saudi Arabia", + "phoneCode": "966", + "flag": "🇸🇦", + "flagUrl": "https://flagcdn.com/w320/sa.png", + "currency": "SAR", + "latitude": "25.00000000", + "longitude": "45.00000000" + }, + { + "isoCode": "AE", + "name": "United Arab Emirates", + "phoneCode": "971", + "flag": "🇦🇪", + "flagUrl": "https://flagcdn.com/w320/ae.png", + "currency": "AED", + "latitude": "24.00000000", + "longitude": "54.00000000" + }, + { + "isoCode": "QA", + "name": "Qatar", + "phoneCode": "974", + "flag": "🇶🇦", + "flagUrl": "https://flagcdn.com/w320/qa.png", + "currency": "QAR", + "latitude": "25.50000000", + "longitude": "51.25000000" + }, + { + "isoCode": "KW", + "name": "Kuwait", + "phoneCode": "965", + "flag": "🇰🇼", + "flagUrl": "https://flagcdn.com/w320/kw.png", + "currency": "KWD", + "latitude": "29.50000000", + "longitude": "47.75000000" + }, + { + "isoCode": "BH", + "name": "Bahrain", + "phoneCode": "973", + "flag": "🇧🇭", + "flagUrl": "https://flagcdn.com/w320/bh.png", + "currency": "BHD", + "latitude": "26.00000000", + "longitude": "50.55000000" + }, + { + "isoCode": "OM", + "name": "Oman", + "phoneCode": "968", + "flag": "🇴🇲", + "flagUrl": "https://flagcdn.com/w320/om.png", + "currency": "OMR", + "latitude": "21.00000000", + "longitude": "57.00000000" + }, + { + "isoCode": "JO", + "name": "Jordan", + "phoneCode": "962", + "flag": "🇯🇴", + "flagUrl": "https://flagcdn.com/w320/jo.png", + "currency": "JOD", + "latitude": "31.00000000", + "longitude": "36.00000000" + }, + { + "isoCode": "LB", + "name": "Lebanon", + "phoneCode": "961", + "flag": "🇱🇧", + "flagUrl": "https://flagcdn.com/w320/lb.png", + "currency": "LBP", + "latitude": "33.83333333", + "longitude": "35.83333333" + }, + { + "isoCode": "SY", + "name": "Syria", + "phoneCode": "963", + "flag": "🇸🇾", + "flagUrl": "https://flagcdn.com/w320/sy.png", + "currency": "SYP", + "latitude": "35.00000000", + "longitude": "38.00000000" + }, + { + "isoCode": "IQ", + "name": "Iraq", + "phoneCode": "964", + "flag": "🇮🇶", + "flagUrl": "https://flagcdn.com/w320/iq.png", + "currency": "IQD", + "latitude": "33.00000000", + "longitude": "44.00000000" + }, + { + "isoCode": "YE", + "name": "Yemen", + "phoneCode": "967", + "flag": "🇾🇪", + "flagUrl": "https://flagcdn.com/w320/ye.png", + "currency": "YER", + "latitude": "15.00000000", + "longitude": "48.00000000" + }, + { + "isoCode": "SD", + "name": "Sudan", + "phoneCode": "249", + "flag": "🇸🇩", + "flagUrl": "https://flagcdn.com/w320/sd.png", + "currency": "SDG", + "latitude": "15.00000000", + "longitude": "30.00000000" + }, + { + "isoCode": "MA", + "name": "Morocco", + "phoneCode": "212", + "flag": "🇲🇦", + "flagUrl": "https://flagcdn.com/w320/ma.png", + "currency": "MAD", + "latitude": "32.00000000", + "longitude": "-5.00000000" + }, + { + "isoCode": "DZ", + "name": "Algeria", + "phoneCode": "213", + "flag": "🇩🇿", + "flagUrl": "https://flagcdn.com/w320/dz.png", + "currency": "DZD", + "latitude": "28.00000000", + "longitude": "3.00000000" + }, + { + "isoCode": "TN", + "name": "Tunisia", + "phoneCode": "216", + "flag": "🇹🇳", + "flagUrl": "https://flagcdn.com/w320/tn.png", + "currency": "TND", + "latitude": "34.00000000", + "longitude": "9.00000000" + }, + { + "isoCode": "LY", + "name": "Libya", + "phoneCode": "218", + "flag": "🇱🇾", + "flagUrl": "https://flagcdn.com/w320/ly.png", + "currency": "LYD", + "latitude": "25.00000000", + "longitude": "17.00000000" + }, + { + "isoCode": "PS", + "name": "Palestine", + "phoneCode": "970", + "flag": "🇵🇸", + "flagUrl": "https://flagcdn.com/w320/ps.png", + "currency": "ILS", + "latitude": "31.90000000", + "longitude": "35.20000000" + }, + { + "isoCode": "US", + "name": "United States", + "phoneCode": "1", + "flag": "🇺🇸", + "flagUrl": "https://flagcdn.com/w320/us.png", + "currency": "USD", + "latitude": "38.00000000", + "longitude": "-97.00000000" + }, + { + "isoCode": "GB", + "name": "United Kingdom", + "phoneCode": "44", + "flag": "🇬🇧", + "flagUrl": "https://flagcdn.com/w320/gb.png", + "currency": "GBP", + "latitude": "54.00000000", + "longitude": "-2.00000000" + }, + { + "isoCode": "FR", + "name": "France", + "phoneCode": "33", + "flag": "🇫🇷", + "flagUrl": "https://flagcdn.com/w320/fr.png", + "currency": "EUR", + "latitude": "46.00000000", + "longitude": "2.00000000" + }, + { + "isoCode": "DE", + "name": "Germany", + "phoneCode": "49", + "flag": "🇩🇪", + "flagUrl": "https://flagcdn.com/w320/de.png", + "currency": "EUR", + "latitude": "51.00000000", + "longitude": "9.00000000" + }, + { + "isoCode": "TR", + "name": "Turkey", + "phoneCode": "90", + "flag": "🇹🇷", + "flagUrl": "https://flagcdn.com/w320/tr.png", + "currency": "TRY", + "latitude": "39.00000000", + "longitude": "35.00000000" + } +] diff --git a/coverage/build/.last_build_id b/coverage/build/.last_build_id new file mode 100644 index 0000000..4c67020 --- /dev/null +++ b/coverage/build/.last_build_id @@ -0,0 +1 @@ +5de0e0f9d69168653226eee8e3dae383 \ No newline at end of file diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 0c32daf..1ee3f7e 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -2,6 +2,10 @@ + NSPhotoLibraryUsageDescription + We need access to your photo library to upload images + NSCameraUsageDescription + We need access to your camera to take pictures CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName diff --git a/lib/api/client/api_client.dart b/lib/api/client/api_client.dart index 3f9a148..4612305 100644 --- a/lib/api/client/api_client.dart +++ b/lib/api/client/api_client.dart @@ -1,5 +1,6 @@ import 'package:dio/dio.dart'; import 'package:elevate_tracking_app/api/models/responses/apply_response_dto.dart'; +import 'package:elevate_tracking_app/api/models/responses/vehicles_response.dart'; import 'package:elevate_tracking_app/core/constants/end_points.dart'; import 'package:injectable/injectable.dart'; import 'package:retrofit/retrofit.dart'; @@ -13,4 +14,6 @@ abstract class ApiClient { factory ApiClient(Dio dio) = _ApiClient; @POST(Endpoints.apply) Future apply(@Body() FormData request); + @GET(Endpoints.vehicle) + Future getAllVehicles(); } diff --git a/lib/api/data_source/auth_local_data_source_impl.dart b/lib/api/data_source/auth_local_data_source_impl.dart index 914adc4..e7c36a3 100644 --- a/lib/api/data_source/auth_local_data_source_impl.dart +++ b/lib/api/data_source/auth_local_data_source_impl.dart @@ -1,7 +1,31 @@ +import 'dart:convert'; +import 'package:elevate_tracking_app/api/mapper/apply_mapper.dart'; +import 'package:elevate_tracking_app/api/models/responses/country_dto.dart'; +import 'package:elevate_tracking_app/core/api_result/api_result.dart'; +import 'package:elevate_tracking_app/core/constants/end_points.dart'; import 'package:elevate_tracking_app/data/data_source/auth_local_data_source.dart'; +import 'package:elevate_tracking_app/domain/entites/country_entity.dart'; +import 'package:flutter/services.dart'; import 'package:injectable/injectable.dart'; @Injectable(as: AuthLocalDataSource) class AuthLocalDataSourceImpl implements AuthLocalDataSource { - -} \ No newline at end of file + final AssetBundle bundle; + AuthLocalDataSourceImpl({AssetBundle? testBundle}) + : bundle = testBundle ?? rootBundle; + + @override + Future>> getAllCountry() async { + try { + final jsonString = await bundle.loadString(Endpoints.countryLocalData); + final List decoded = jsonDecode(jsonString); + final dtoList = decoded + .map((e) => CountryDto.fromJson(e as Map)) + .toList(); + final entities = dtoList.map((c) => c.toEntity()).toList(); + return ApiSuccessResult>(entities); + } catch (e) { + return ApiErrorResult(e.toString()); + } + } +} diff --git a/lib/api/data_source/auth_remote_data_source_impl.dart b/lib/api/data_source/auth_remote_data_source_impl.dart index 87242cd..72f9b6b 100644 --- a/lib/api/data_source/auth_remote_data_source_impl.dart +++ b/lib/api/data_source/auth_remote_data_source_impl.dart @@ -2,11 +2,13 @@ import 'package:elevate_tracking_app/api/client/api_client.dart'; import 'package:elevate_tracking_app/api/mapper/apply_mapper.dart'; import 'package:elevate_tracking_app/api/models/requests/apply_request_dto.dart'; import 'package:elevate_tracking_app/api/models/responses/apply_response_dto.dart'; +import 'package:elevate_tracking_app/api/models/responses/vehicles_response.dart'; import 'package:elevate_tracking_app/core/api_result/api_result.dart'; import 'package:elevate_tracking_app/core/api_result/safe_api_call.dart'; import 'package:elevate_tracking_app/data/data_source/auth_remote_data_source.dart'; import 'package:elevate_tracking_app/domain/entites/apply_response_entity.dart'; import 'package:elevate_tracking_app/domain/entites/request/apply_request_entity.dart'; +import 'package:elevate_tracking_app/domain/entites/vehicles_entity.dart'; import 'package:injectable/injectable.dart'; @Injectable(as: AuthRemoteDataSource) @@ -25,4 +27,12 @@ class AuthRemoteDataSourceImpl implements AuthRemoteDataSource { return await _apiClient.apply(formData); }, (dto) => dto.toEntity()); } + + @override + Future>> getAllVehicles() async { + return safeApiCall>( + () => _apiClient.getAllVehicles(), + (dto) => dto.vehicles?.map((v) => v.toEntity()).toList() ?? [], + ); + } } diff --git a/lib/api/mapper/apply_mapper.dart b/lib/api/mapper/apply_mapper.dart index f5c4d41..318cdd6 100644 --- a/lib/api/mapper/apply_mapper.dart +++ b/lib/api/mapper/apply_mapper.dart @@ -1,7 +1,12 @@ import 'package:elevate_tracking_app/api/models/responses/apply_response_dto.dart'; +import 'package:elevate_tracking_app/api/models/responses/country_dto.dart'; +import 'package:elevate_tracking_app/api/models/responses/vehicle.dart'; import 'package:elevate_tracking_app/domain/entites/apply_response_entity.dart'; import 'package:elevate_tracking_app/api/models/requests/apply_request_dto.dart'; +import 'package:elevate_tracking_app/domain/entites/country_entity.dart'; import 'package:elevate_tracking_app/domain/entites/request/apply_request_entity.dart'; +import 'package:elevate_tracking_app/domain/entites/vehicles_entity.dart'; + extension ApplyResponseMapper on ApplyResponseDto { ApplyResponseEntity toEntity() { return ApplyResponseEntity( @@ -25,6 +30,7 @@ extension ApplyResponseMapper on ApplyResponseDto { ); } } + extension ApplyRequestMapper on ApplyRequestEntity { ApplyRequestDto toDto() { return ApplyRequestDto( @@ -44,3 +50,20 @@ extension ApplyRequestMapper on ApplyRequestEntity { ); } } + +extension VehicleMapper on Vehicle { + VehicleEntity toEntity() { + return VehicleEntity(id: id ?? '', type: type ?? '', image: image ?? ''); + } +} + +extension CountryMapper on CountryDto { + CountryEntity toEntity() { + return CountryEntity( + name: name ?? '', + flag: flag ?? '', + currency: currency ?? '', + flagImage: flagUrl ?? '', + ); + } +} diff --git a/lib/api/models/requests/apply_request_dto.dart b/lib/api/models/requests/apply_request_dto.dart index 06f4a9d..9235c06 100644 --- a/lib/api/models/requests/apply_request_dto.dart +++ b/lib/api/models/requests/apply_request_dto.dart @@ -12,6 +12,7 @@ class ApplyRequestDto extends Equatable { final String lastName; final String vehicleType; final String vehicleNumber; + @JsonKey(name: "NID") final String nid; final String email; final String password; @@ -22,7 +23,7 @@ class ApplyRequestDto extends Equatable { @JsonKey(includeToJson: false, includeFromJson: false) final File? vehicleLicense; - @JsonKey(includeToJson: false, includeFromJson: false) + @JsonKey(includeToJson: false, includeFromJson: false , name: "NIDImg") final File? nidImg; const ApplyRequestDto({ diff --git a/lib/api/models/responses/country_dto.dart b/lib/api/models/responses/country_dto.dart new file mode 100644 index 0000000..135deb4 --- /dev/null +++ b/lib/api/models/responses/country_dto.dart @@ -0,0 +1,78 @@ +import 'package:json_annotation/json_annotation.dart'; +import 'package:equatable/equatable.dart'; + +part 'country_dto.g.dart'; + +@JsonSerializable(explicitToJson: true) +class CountryDto extends Equatable { + final String? isoCode; + final String? name; + final String? phoneCode; + final String? flag; + final String? currency; + final String? latitude; + final String? longitude; + final List? timezones; + final String? flagUrl; // ✅ جديد + + const CountryDto({ + this.isoCode, + this.name, + this.phoneCode, + this.flag, + this.currency, + this.latitude, + this.longitude, + this.timezones, + this.flagUrl, // ✅ جديد + }); + + factory CountryDto.fromJson(Map json) => + _$CountryDtoFromJson(json); + + Map toJson() => _$CountryDtoToJson(this); + + @override + List get props => [ + isoCode, + name, + phoneCode, + flag, + currency, + latitude, + longitude, + timezones, + flagUrl, // ✅ جديد + ]; +} + +@JsonSerializable() +class Timezone extends Equatable { + final String? zoneName; + final int? gmtOffset; + final String? gmtOffsetName; + final String? abbreviation; + final String? tzName; + + const Timezone({ + this.zoneName, + this.gmtOffset, + this.gmtOffsetName, + this.abbreviation, + this.tzName, + }); + + factory Timezone.fromJson(Map json) => + _$TimezoneFromJson(json); + + Map toJson() => _$TimezoneToJson(this); + + @override + List get props => [ + zoneName, + gmtOffset, + gmtOffsetName, + abbreviation, + tzName, + ]; +} diff --git a/lib/api/models/responses/vehicle.dart b/lib/api/models/responses/vehicle.dart new file mode 100644 index 0000000..94c0d52 --- /dev/null +++ b/lib/api/models/responses/vehicle.dart @@ -0,0 +1,28 @@ +import 'package:json_annotation/json_annotation.dart'; +part 'vehicle.g.dart'; + +@JsonSerializable() +class Vehicle { + @JsonKey(name: '_id') + final String? id; + final String? type; + final String? image; + final String? createdAt; + final String? updatedAt; + @JsonKey(name: '__v') + final int? v; + + Vehicle({ + this.id, + this.type, + this.image, + this.createdAt, + this.updatedAt, + this.v, + }); + + factory Vehicle.fromJson(Map json) => + _$VehicleFromJson(json); + + Map toJson() => _$VehicleToJson(this); +} diff --git a/lib/api/models/responses/vehicles_response.dart b/lib/api/models/responses/vehicles_response.dart new file mode 100644 index 0000000..08d4437 --- /dev/null +++ b/lib/api/models/responses/vehicles_response.dart @@ -0,0 +1,43 @@ +import 'package:elevate_tracking_app/api/models/responses/vehicle.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'vehicles_response.g.dart'; + +@JsonSerializable(explicitToJson: true) +class VehiclesResponse { + final String? message; + final Metadata? metadata; + final List? vehicles; + + VehiclesResponse({ + this.message, + this.metadata, + this.vehicles, + }); + + factory VehiclesResponse.fromJson(Map json) => + _$VehiclesResponseFromJson(json); + + Map toJson() => _$VehiclesResponseToJson(this); +} + +@JsonSerializable() +class Metadata { + final int? currentPage; + final int? totalPages; + final int? limit; + final int? totalItems; + + Metadata({ + this.currentPage, + this.totalPages, + this.limit, + this.totalItems, + }); + + factory Metadata.fromJson(Map json) => + _$MetadataFromJson(json); + + Map toJson() => _$MetadataToJson(this); +} + diff --git a/lib/core/base_state/base_state.dart b/lib/core/base_state/base_state.dart new file mode 100644 index 0000000..44527e8 --- /dev/null +++ b/lib/core/base_state/base_state.dart @@ -0,0 +1,14 @@ +import 'package:equatable/equatable.dart'; + +class BaseState extends Equatable { + final String? errorMessage; + final bool isLoading; + final T? data; + const BaseState({this.data, this.errorMessage, this.isLoading = false}); + factory BaseState.loading() => const BaseState(isLoading: true); + factory BaseState.success(T data) => BaseState(data: data); + factory BaseState.error(String errorMessage) => + BaseState(errorMessage: errorMessage); + @override + List get props => [isLoading, errorMessage, data]; +} \ No newline at end of file diff --git a/lib/core/constants/app_theme.dart b/lib/core/constants/app_theme.dart index d4d6838..21ba852 100644 --- a/lib/core/constants/app_theme.dart +++ b/lib/core/constants/app_theme.dart @@ -3,7 +3,6 @@ import 'package:elevate_tracking_app/core/constants/const_keys.dart'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; - abstract class AppTheme { static ThemeData lightTheme = ThemeData( useMaterial3: true, diff --git a/lib/core/constants/const_keys.dart b/lib/core/constants/const_keys.dart index 605dc19..f0b5d84 100644 --- a/lib/core/constants/const_keys.dart +++ b/lib/core/constants/const_keys.dart @@ -9,4 +9,6 @@ abstract final class ConstKeys { static const String outfitFont = "Outfit"; static const String interFont = "Inter"; static const String iMFeellEnglishFont = "IMFellEnglish"; + static const String kMale = "male"; + static const String kFemale = "female"; } diff --git a/lib/core/constants/end_points.dart b/lib/core/constants/end_points.dart index 65c8f3d..f958d8f 100644 --- a/lib/core/constants/end_points.dart +++ b/lib/core/constants/end_points.dart @@ -1,3 +1,5 @@ abstract class Endpoints { - static const String apply = "v1/drivers/apply"; + static const String apply = "api/v1/drivers/apply"; + static const String vehicle = "api/v1/vehicles"; + static const String countryLocalData = "assets/data/country.json"; } diff --git a/lib/core/constants/widgets_keys.dart b/lib/core/constants/widgets_keys.dart new file mode 100644 index 0000000..b1b7042 --- /dev/null +++ b/lib/core/constants/widgets_keys.dart @@ -0,0 +1,42 @@ +abstract final class WidgetsKeys { + static const String kApplyScreenLabel = "k_apply_screen_label"; + static const String kApplyScreenIconArrowBack = + "k_apply_screen_icon_arrow_back"; + static const String kApplyScreenWelcome = "k_apply_screen_welcome"; + static const String kApplyScreenDescription = "k_apply_screen_description"; + static const String kApplyScreenCountryFormField = + "k_apply_screen_country_form_field"; + static const String kApplyScreenPadding = "k_apply_screen_padding"; + static const String kApplyScreenSingleChildScrollView = + "k_apply_screen_single_child_scroll_view"; + static const String kApplyScreenArrowDown = "k_apply_screen_arrow_down"; + static const String kApplyScreenIconUpload = "k_apply_screen_icon_upload"; + static const String kApplyScreenFirstNamedFormField = + "k_apply_screen_first_name_form_filed"; + static const String kApplyScreenSecondNamedFormField = + "k_apply_screen_second_name_form_filed"; + static const String kApplyScreenVehicleTypeFormField = + "k_apply_screen_vehicle_type_form_filed"; + static const String kApplyScreenVehicleNumberFormField = + "k_apply_screen_vehicle_number_form_filed"; + static const String kApplyScreenVehicleLicenseFormField = + "k_apply_screen_vehicle_license_form_filed"; + static const String kApplyScreenEmailFormField = + "k_apply_screen_email_form_filed"; + static const String kApplyScreenPhoneNumberFormField = + "k_apply_screen_phone_number_form_filed"; + static const String kApplyScreenIDNumberFormField = + "k_apply_screen_id_number_form_filed"; + static const String kApplyScreenIDImageFormField = + "k_apply_screen_id_image_form_filed"; + static const String kApplyScreenPasswordFormField = + "k_apply_screen_password_form_filed"; + static const String kApplyScreenConfirmPasswordFormField = + "k_apply_screen_confirm_password_form_filed"; + static const String kApplyScreenElevatedButtonContinue = + "k_apply_screen_elevated_button_continue"; + static const String kApplyScreenRadioGroup = "k_apply_screen_radio_group"; + static const String kApplyScreenTextGender = "k_apply_screen_text_gender"; + static const String kApplyScreenTextMale = "k_apply_screen_text_male"; + static const String kApplyScreenTextFemale = "k_apply_screen_text_female"; +} diff --git a/lib/core/module/asset_bundle_module.dart b/lib/core/module/asset_bundle_module.dart new file mode 100644 index 0000000..545f3d3 --- /dev/null +++ b/lib/core/module/asset_bundle_module.dart @@ -0,0 +1,8 @@ +import 'package:flutter/services.dart'; +import 'package:injectable/injectable.dart'; + +@module +abstract class AppModule { + @lazySingleton + AssetBundle get assetBundle => rootBundle; +} diff --git a/lib/core/router/app_router.dart b/lib/core/router/app_router.dart index fda2bbb..7687091 100644 --- a/lib/core/router/app_router.dart +++ b/lib/core/router/app_router.dart @@ -1,15 +1,20 @@ import 'package:elevate_tracking_app/core/router/route_names.dart'; +import 'package:elevate_tracking_app/presentation/apply/view/screen/apply_view.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; abstract class AppRouter { static final router = GoRouter( - initialLocation: RouteNames.onboarding, + initialLocation: RouteNames.apply, routes: [ GoRoute( path: RouteNames.onboarding, builder: (context, state) => const SizedBox(), ), + GoRoute( + path: RouteNames.apply, + builder: (context, state) => const ApplyView(), + ), ], ); } diff --git a/lib/core/utils/validations.dart b/lib/core/utils/validations.dart index cef4c76..7a6d8fb 100644 --- a/lib/core/utils/validations.dart +++ b/lib/core/utils/validations.dart @@ -1,5 +1,3 @@ - - import 'package:elevate_tracking_app/generated/l10n.dart'; class Validations { @@ -58,7 +56,7 @@ class Validations { } } - static String? validateFullName(String? val) { + static String? validateText(String? val) { if (val == null || val.isEmpty) { return AppLocalizations().thisFieldIsRequired; } else { @@ -79,6 +77,16 @@ class Validations { return null; } } + static String? validateFourteenDigitNumber(String? value) { + if (value == null || value.isEmpty) { + return AppLocalizations().numberRequired; + } + final regex = RegExp(r'^[0-9]{14}$'); + if (!regex.hasMatch(value)) { + return AppLocalizations().numberMustBeFourteenDigit; + } + return null; + } static String? validateAddress(String? val) { final RegExp addressRegex = RegExp(r"^[\p{L}\d\s,.\-\/#]+$", unicode: true); diff --git a/lib/data/data_source/auth_local_data_source.dart b/lib/data/data_source/auth_local_data_source.dart index 55826f6..81205e9 100644 --- a/lib/data/data_source/auth_local_data_source.dart +++ b/lib/data/data_source/auth_local_data_source.dart @@ -1,3 +1,6 @@ +import 'package:elevate_tracking_app/core/api_result/api_result.dart'; +import 'package:elevate_tracking_app/domain/entites/country_entity.dart'; + abstract interface class AuthLocalDataSource { - -} \ No newline at end of file + Future>> getAllCountry(); +} diff --git a/lib/data/data_source/auth_remote_data_source.dart b/lib/data/data_source/auth_remote_data_source.dart index 68b38c8..a828eed 100644 --- a/lib/data/data_source/auth_remote_data_source.dart +++ b/lib/data/data_source/auth_remote_data_source.dart @@ -1,9 +1,11 @@ import 'package:elevate_tracking_app/core/api_result/api_result.dart'; import 'package:elevate_tracking_app/domain/entites/apply_response_entity.dart'; import 'package:elevate_tracking_app/domain/entites/request/apply_request_entity.dart'; +import 'package:elevate_tracking_app/domain/entites/vehicles_entity.dart'; abstract interface class AuthRemoteDataSource { Future> apply({ required ApplyRequestEntity request, }); + Future>> getAllVehicles(); } diff --git a/lib/data/repo/auth_repo_impl.dart b/lib/data/repo/auth_repo_impl.dart index 076ea3f..03d261b 100644 --- a/lib/data/repo/auth_repo_impl.dart +++ b/lib/data/repo/auth_repo_impl.dart @@ -1,19 +1,32 @@ import 'package:elevate_tracking_app/core/api_result/api_result.dart'; +import 'package:elevate_tracking_app/data/data_source/auth_local_data_source.dart'; import 'package:elevate_tracking_app/data/data_source/auth_remote_data_source.dart'; import 'package:elevate_tracking_app/domain/entites/apply_response_entity.dart'; +import 'package:elevate_tracking_app/domain/entites/country_entity.dart'; import 'package:elevate_tracking_app/domain/entites/request/apply_request_entity.dart'; +import 'package:elevate_tracking_app/domain/entites/vehicles_entity.dart'; import 'package:elevate_tracking_app/domain/repo/auth_repo.dart'; import 'package:injectable/injectable.dart'; @Injectable(as: AuthRepo) class AuthRepoImpl implements AuthRepo { final AuthRemoteDataSource _authRemoteDataSource; - - AuthRepoImpl(this._authRemoteDataSource); + final AuthLocalDataSource _authLocalDataSource; + AuthRepoImpl(this._authRemoteDataSource, this._authLocalDataSource); @override Future> apply({ required ApplyRequestEntity request, }) async { return await _authRemoteDataSource.apply(request: request); } + + @override + Future>> getAllVehicles() async { + return await _authRemoteDataSource.getAllVehicles(); + } + + @override + Future>> getAllCountries() async { + return await _authLocalDataSource.getAllCountry(); + } } diff --git a/lib/domain/entites/country_entity.dart b/lib/domain/entites/country_entity.dart new file mode 100644 index 0000000..40ae26b --- /dev/null +++ b/lib/domain/entites/country_entity.dart @@ -0,0 +1,17 @@ +import 'package:equatable/equatable.dart'; + +class CountryEntity extends Equatable { + final String name; + final String flag; + final String currency; + final String flagImage; + const CountryEntity({ + required this.name, + required this.flagImage, + required this.flag, + required this.currency, + }); + + @override + List get props => [name, flag, currency]; +} diff --git a/lib/domain/entites/vehicles_entity.dart b/lib/domain/entites/vehicles_entity.dart new file mode 100644 index 0000000..9a5d689 --- /dev/null +++ b/lib/domain/entites/vehicles_entity.dart @@ -0,0 +1,11 @@ +class VehicleEntity { + final String id; + final String type; + final String image; + + const VehicleEntity({ + required this.id, + required this.type, + required this.image, + }); +} diff --git a/lib/domain/repo/auth_repo.dart b/lib/domain/repo/auth_repo.dart index b31c13b..a301f9c 100644 --- a/lib/domain/repo/auth_repo.dart +++ b/lib/domain/repo/auth_repo.dart @@ -1,9 +1,13 @@ import 'package:elevate_tracking_app/core/api_result/api_result.dart'; import 'package:elevate_tracking_app/domain/entites/apply_response_entity.dart'; +import 'package:elevate_tracking_app/domain/entites/country_entity.dart'; import 'package:elevate_tracking_app/domain/entites/request/apply_request_entity.dart'; +import 'package:elevate_tracking_app/domain/entites/vehicles_entity.dart'; abstract interface class AuthRepo { Future> apply({ required ApplyRequestEntity request, }); + Future>> getAllVehicles(); + Future>> getAllCountries(); } diff --git a/lib/domain/use_cases/apply_use_case.dart b/lib/domain/use_cases/apply_use_case.dart index 27dc96d..6cac829 100644 --- a/lib/domain/use_cases/apply_use_case.dart +++ b/lib/domain/use_cases/apply_use_case.dart @@ -7,7 +7,6 @@ import 'package:injectable/injectable.dart'; @injectable class ApplyUseCase { final AuthRepo _authRepo; - ApplyUseCase(this._authRepo); Future> call( ApplyRequestEntity request, diff --git a/lib/domain/use_cases/get_all_country_use_case.dart b/lib/domain/use_cases/get_all_country_use_case.dart new file mode 100644 index 0000000..f149640 --- /dev/null +++ b/lib/domain/use_cases/get_all_country_use_case.dart @@ -0,0 +1,14 @@ +import 'package:elevate_tracking_app/core/api_result/api_result.dart'; +import 'package:elevate_tracking_app/domain/entites/country_entity.dart'; +import 'package:elevate_tracking_app/domain/repo/auth_repo.dart'; +import 'package:injectable/injectable.dart'; + +@injectable +class GetAllCountryUseCase { + final AuthRepo _authRepo; + + GetAllCountryUseCase(this._authRepo); + Future>> call() async { + return await _authRepo.getAllCountries(); + } +} diff --git a/lib/domain/use_cases/get_all_vehicles_use_case.dart b/lib/domain/use_cases/get_all_vehicles_use_case.dart new file mode 100644 index 0000000..9e2f9a9 --- /dev/null +++ b/lib/domain/use_cases/get_all_vehicles_use_case.dart @@ -0,0 +1,14 @@ +import 'package:elevate_tracking_app/core/api_result/api_result.dart'; +import 'package:elevate_tracking_app/domain/entites/vehicles_entity.dart'; +import 'package:elevate_tracking_app/domain/repo/auth_repo.dart'; +import 'package:injectable/injectable.dart'; + +@injectable +class GetAllVehiclesUseCase { + final AuthRepo _authRepo; + + GetAllVehiclesUseCase(this._authRepo); + Future>> call() async { + return await _authRepo.getAllVehicles(); + } +} diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index cb44b82..5b44641 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -27,5 +27,35 @@ "ok": "Ok", "no": "No", "yes": "Yes", - "loading": "Loading..." + "loading": "Loading...", + "apply": "Apply", + "welcome": "Welcome!!", + "youWantToBeADeliveryManJoinOurTeam": "You want to be a delivery man?\nJoin our team", + "Country": "Country", + "firstLegalName": "First legal name", + "EnterFirstLegalName": "Enter first legal name", + "secondLegalName": "Second legal name", + "EnterSecondLegalName": "Enter Second legal name", + "vehicleType": "Vehicle type", + "vehicleNumber": "Vehicle number", + "enterVehicleNumber": "Enter Vehicle number", + "vehicleLicense": "Vehicle license", + "uploadLicensePhoto": "Upload license photo", + "email": "Email", + "enterYourEmail": "Enter you email", + "phoneNumber": "Phone number", + "enterYourPhoneNumber": "Enter you phone number", + "password": "Password", + "enterPassword": "Enter password", + "confirmPassword": "Confirm password", + "nidNumber": "ID number", + "enterIdNumber": "Enter national ID number", + "nidImage": "ID image", + "uploadIdImage": "Upload ID image", + "gender": "Gender", + "female": "Female", + "male": "Male", + "continueText": "Continue", + "numberRequired": "Number is required", + "numberMustBeFourteenDigit": "Number must be 14 digit" } \ No newline at end of file diff --git a/lib/presentation/apply/view/screen/apply_view.dart b/lib/presentation/apply/view/screen/apply_view.dart new file mode 100644 index 0000000..d0a32ac --- /dev/null +++ b/lib/presentation/apply/view/screen/apply_view.dart @@ -0,0 +1,42 @@ +import 'package:elevate_tracking_app/core/constants/widgets_keys.dart'; +import 'package:elevate_tracking_app/core/di/di.dart'; +import 'package:elevate_tracking_app/core/router/route_names.dart'; +import 'package:elevate_tracking_app/generated/l10n.dart'; +import 'package:elevate_tracking_app/presentation/apply/view/widgets/apply_body.dart'; +import 'package:elevate_tracking_app/presentation/apply/view_model/apply_event.dart'; +import 'package:elevate_tracking_app/presentation/apply/view_model/apply_view_model.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:go_router/go_router.dart'; + +class ApplyView extends StatelessWidget { + const ApplyView({super.key}); + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text( + key: const Key(WidgetsKeys.kApplyScreenLabel), + AppLocalizations.of(context).apply, + style: Theme.of( + context, + ).textTheme.bodyLarge?.copyWith(fontWeight: FontWeight.w500), + ), + leading: GestureDetector( + onTap: () { + context.go(RouteNames.login); + }, + child: const Icon( + key: Key(WidgetsKeys.kApplyScreenIconArrowBack), + Icons.arrow_back_ios_new_rounded, + ), + ), + ), + body: BlocProvider( + create: (context) => + getIt.get()..doIntent(ApplyEventGetAllData()), + child: const ApplyBody(), + ), + ); + } +} diff --git a/lib/presentation/apply/view/widgets/apply_body.dart b/lib/presentation/apply/view/widgets/apply_body.dart new file mode 100644 index 0000000..91f8014 --- /dev/null +++ b/lib/presentation/apply/view/widgets/apply_body.dart @@ -0,0 +1,244 @@ +import 'package:elevate_tracking_app/core/constants/app_colors.dart'; +import 'package:elevate_tracking_app/core/constants/app_icons.dart'; +import 'package:elevate_tracking_app/core/constants/widgets_keys.dart'; +import 'package:elevate_tracking_app/core/custom_widget/custom_dialog.dart'; +import 'package:elevate_tracking_app/core/utils/validations.dart'; +import 'package:elevate_tracking_app/generated/l10n.dart'; +import 'package:elevate_tracking_app/presentation/apply/view/widgets/custom_apply_radio.dart'; +import 'package:elevate_tracking_app/presentation/apply/view/widgets/custom_country_text_field.dart'; +import 'package:elevate_tracking_app/presentation/apply/view/widgets/custom_password_field.dart'; +import 'package:elevate_tracking_app/presentation/apply/view/widgets/custom_vehicle_type_text_filed.dart'; +import 'package:elevate_tracking_app/presentation/apply/view_model/apply_event.dart'; +import 'package:elevate_tracking_app/presentation/apply/view_model/apply_view_model.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:go_router/go_router.dart'; + +class ApplyBody extends StatelessWidget { + const ApplyBody({super.key}); + + @override + Widget build(BuildContext context) { + final local = AppLocalizations.of(context); + final theme = Theme.of(context); + final cubit = BlocProvider.of(context); + return BlocListener( + listener: (context, state) { + if (state.applyFun?.isLoading == true) { + CustomDialog.loading(context: context); + } else if (state.applyFun?.data != null) { + context.pop(); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text("success")), + ); // when success must be navigate to apply success + } else if (state.applyFun?.errorMessage != null) { + context.pop(); + CustomDialog.positiveButton( + context: context, + cancelable: true, + title: state.applyFun?.errorMessage, + ); + } + }, + child: Form( + autovalidateMode: AutovalidateMode.onUnfocus, + key: cubit.globalKey, + child: Padding( + key: const Key(WidgetsKeys.kApplyScreenPadding), + padding: EdgeInsets.symmetric(horizontal: 16.sp), + child: SingleChildScrollView( + key: const Key(WidgetsKeys.kApplyScreenSingleChildScrollView), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + key: const Key(WidgetsKeys.kApplyScreenPadding), + local.welcome, + style: theme.textTheme.bodyLarge?.copyWith( + fontWeight: FontWeight.w500, + ), + ), + SizedBox(height: 8.h), + Text( + key: const Key(WidgetsKeys.kApplyScreenDescription), + local.youWantToBeADeliveryManJoinOurTeam, + style: theme.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w500, + color: AppColors.gray, + ), + ), + SizedBox(height: 32.h), + const CustomCountryTextField(), + SizedBox(height: 25.h), + TextFormField( + controller: cubit.controllerFirstName, + validator: Validations.validateText, + style: theme.textTheme.bodyMedium, + + key: const Key(WidgetsKeys.kApplyScreenFirstNamedFormField), + decoration: InputDecoration( + label: Text(local.firstLegalName), + hintText: local.EnterFirstLegalName, + ), + ), + SizedBox(height: 25.h), + TextFormField( + validator: Validations.validateText, + style: theme.textTheme.bodyMedium, + + controller: cubit.controllerSecondName, + key: const Key(WidgetsKeys.kApplyScreenSecondNamedFormField), + decoration: InputDecoration( + label: Text(local.secondLegalName), + hintText: local.EnterSecondLegalName, + ), + ), + SizedBox(height: 25.h), + const CustomVehicleTypeTextFiled(), + SizedBox(height: 25.h), + TextFormField( + controller: cubit.controllerVehicleNumber, + validator: Validations.validateText, + style: theme.textTheme.bodyMedium, + key: const Key( + WidgetsKeys.kApplyScreenVehicleNumberFormField, + ), + decoration: InputDecoration( + label: Text(local.vehicleNumber), + hintText: local.enterVehicleNumber, + ), + ), + SizedBox(height: 25.h), + BlocBuilder( + builder: (context, state) { + return TextFormField( + style: theme.textTheme.bodyMedium, + validator: Validations.validateText, + controller: cubit.controllerVehicleLicense, + key: const Key( + WidgetsKeys.kApplyScreenVehicleLicenseFormField, + ), + readOnly: true, + decoration: InputDecoration( + prefixIcon: state.vehicleImagePath != null + ? const Icon(Icons.task_alt, color: AppColors.green) + : null, + label: Text(local.vehicleLicense), + hintText: local.uploadLicensePhoto, + suffixIcon: GestureDetector( + onTap: () { + cubit.doIntent(ApplyEventPickVehicleLicensePhoto()); + }, + child: ImageIcon( + key: const Key(WidgetsKeys.kApplyScreenIconUpload), + const AssetImage(AppIcons.iconUpload), + color: theme.colorScheme.secondary, + ), + ), + ), + ); + }, + ), + SizedBox(height: 25.h), + TextFormField( + controller: cubit.controllerEmail, + style: theme.textTheme.bodyMedium, + validator: Validations.validateEmail, + key: const Key(WidgetsKeys.kApplyScreenEmailFormField), + keyboardType: TextInputType.emailAddress, + decoration: InputDecoration( + label: Text(local.email), + hintText: local.enterYourEmail, + ), + ), + SizedBox(height: 25.h), + TextFormField( + style: theme.textTheme.bodyMedium, + validator: Validations.validatePhoneNumber, + controller: cubit.controllerPhoneNumber, + key: const Key(WidgetsKeys.kApplyScreenPhoneNumberFormField), + keyboardType: TextInputType.phone, + decoration: InputDecoration( + label: Text(local.phoneNumber), + hintText: local.enterYourPhoneNumber, + ), + ), + SizedBox(height: 25.h), + TextFormField( + style: theme.textTheme.bodyMedium, + validator: Validations.validateFourteenDigitNumber, + controller: cubit.controllerIdNumber, + key: const Key(WidgetsKeys.kApplyScreenIDNumberFormField), + keyboardType: TextInputType.phone, + decoration: InputDecoration( + label: Text(local.nidNumber), + hintText: local.enterIdNumber, + ), + ), + SizedBox(height: 25.h), + BlocBuilder( + builder: (context, state) { + return TextFormField( + readOnly: true, + style: theme.textTheme.bodyMedium, + validator: Validations.validateText, + controller: cubit.controllerIdImage, + key: const Key(WidgetsKeys.kApplyScreenIDImageFormField), + decoration: InputDecoration( + prefixIcon: state.idImagePath != null + ? const Icon(Icons.task_alt, color: AppColors.green) + : null, + label: Text(local.nidImage), + hintText: local.uploadIdImage, + suffixIcon: GestureDetector( + onTap: () { + cubit.doIntent(ApplyEventPickIDImage()); + }, + child: ImageIcon( + key: const Key(WidgetsKeys.kApplyScreenIconUpload), + const AssetImage(AppIcons.iconUpload), + color: theme.colorScheme.secondary, + ), + ), + ), + ); + }, + ), + SizedBox(height: 25.h), + const CustomPasswordField(), + SizedBox(height: 25.h), + const CustomApplyRadio(), + SizedBox(height: 25.h), + SizedBox( + width: double.infinity, + child: ValueListenableBuilder( + valueListenable: cubit.isUserAuthenticated, + builder: (context, value, child) { + return ElevatedButton( + key: const Key( + WidgetsKeys.kApplyScreenElevatedButtonContinue, + ), + onPressed: value + ? () => cubit.doIntent(ApplyValidationField()) + : null, + child: Text( + local.continueText, + style: theme.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w500, + color: theme.colorScheme.onSecondary, + ), + ), + ); + }, + ), + ), + SizedBox(height: 22.h), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/presentation/apply/view/widgets/custom_apply_radio.dart b/lib/presentation/apply/view/widgets/custom_apply_radio.dart new file mode 100644 index 0000000..1cf9305 --- /dev/null +++ b/lib/presentation/apply/view/widgets/custom_apply_radio.dart @@ -0,0 +1,59 @@ +import 'package:elevate_tracking_app/core/constants/app_colors.dart'; +import 'package:elevate_tracking_app/core/constants/const_keys.dart'; +import 'package:elevate_tracking_app/core/constants/widgets_keys.dart'; +import 'package:elevate_tracking_app/generated/l10n.dart'; +import 'package:elevate_tracking_app/presentation/apply/view_model/apply_view_model.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class CustomApplyRadio extends StatelessWidget { + const CustomApplyRadio({super.key}); + + @override + Widget build(BuildContext context) { + final local = AppLocalizations.of(context); + final theme = Theme.of(context); + final cubit = BlocProvider.of(context); + return Row( + children: [ + Text( + key: const Key(WidgetsKeys.kApplyScreenTextGender), + local.gender, + style: theme.textTheme.headlineMedium!.copyWith( + color: AppColors.gray, + ), + ), + ValueListenableBuilder( + valueListenable: cubit.gender, + builder: (context, value, child) => RadioGroup( + key: const Key(WidgetsKeys.kApplyScreenRadioGroup), + onChanged: (value) { + cubit.gender.value = value; + }, + groupValue: cubit.gender.value, + child: Row( + children: [ + const Radio(value: ConstKeys.kMale), + Text( + key: const Key(WidgetsKeys.kApplyScreenTextMale), + local.male, + style: Theme.of(context).textTheme.headlineSmall!.copyWith( + overflow: TextOverflow.ellipsis, + ), + ), + const Radio(value: ConstKeys.kFemale), + Text( + key: const Key(WidgetsKeys.kApplyScreenTextFemale), + local.female, + style: Theme.of(context).textTheme.headlineSmall!.copyWith( + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ), + ), + ], + ); + } +} diff --git a/lib/presentation/apply/view/widgets/custom_country_text_field.dart b/lib/presentation/apply/view/widgets/custom_country_text_field.dart new file mode 100644 index 0000000..5c6d322 --- /dev/null +++ b/lib/presentation/apply/view/widgets/custom_country_text_field.dart @@ -0,0 +1,103 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:elevate_tracking_app/core/constants/app_colors.dart'; +import 'package:elevate_tracking_app/core/constants/app_icons.dart'; +import 'package:elevate_tracking_app/core/constants/widgets_keys.dart'; +import 'package:elevate_tracking_app/core/utils/validations.dart'; +import 'package:elevate_tracking_app/domain/entites/country_entity.dart'; +import 'package:elevate_tracking_app/generated/l10n.dart'; +import 'package:elevate_tracking_app/presentation/apply/view_model/apply_view_model.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +class CustomCountryTextField extends StatelessWidget { + const CustomCountryTextField({super.key}); + @override + Widget build(BuildContext context) { + final local = AppLocalizations.of(context); + final theme = Theme.of(context); + final cubit = BlocProvider.of(context); + return BlocBuilder( + builder: (context, state) { + return TextFormField( + readOnly: true, + validator: Validations.validateText, + controller: cubit.controllerCountry, + style: theme.textTheme.bodyMedium, + key: const Key(WidgetsKeys.kApplyScreenCountryFormField), + decoration: InputDecoration( + label: Text(local.Country), + prefixIcon: Padding( + padding: EdgeInsets.symmetric(vertical: 15.h, horizontal: 7.w), + child: ValueListenableBuilder( + valueListenable: cubit.selectedCountry, + builder: (context, value, child) => SizedBox( + width: 10.w, + child: ClipRRect( + borderRadius: BorderRadiusGeometry.circular(3), + child: CachedNetworkImage( + imageUrl: cubit.selectedCountry.value?.flagImage ?? "", + errorWidget: (context, url, error) => const Icon( + Icons.public, + size: 20, + color: AppColors.gray, + ), + fit: BoxFit.fill, + ), + ), + ), + ), + ), + suffixIcon: PopupMenuButton( + shape: RoundedRectangleBorder( + borderRadius: BorderRadiusGeometry.circular(8), + ), + icon: ImageIcon( + key: const Key(WidgetsKeys.kApplyScreenArrowDown), + const AssetImage(AppIcons.iconArrowDown), + color: theme.colorScheme.secondary, + ), + onSelected: (value) { + cubit.selectedCountry.value = value; + cubit.controllerCountry.text = value.name; + }, + itemBuilder: (context) => List.generate( + state.allCountry?.data?.length ?? 0, + (index) => PopupMenuItem( + value: state.allCountry?.data?[index], + child: Row( + spacing: 8.sp, + children: [ + SizedBox( + width: 30.w, + height: 20.h, + child: ClipRRect( + borderRadius: BorderRadius.circular(2), + child: CachedNetworkImage( + imageUrl: + state.allCountry?.data?[index].flagImage ?? "", + errorWidget: (context, url, error) => const Icon( + Icons.public, + size: 20, + color: AppColors.gray, + ), + fit: BoxFit.cover, + ), + ), + ), + Text( + state.allCountry?.data?[index].name ?? "", + style: theme.textTheme.headlineSmall, + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + ), + ), + ), + ); + }, + ); + } +} diff --git a/lib/presentation/apply/view/widgets/custom_password_field.dart b/lib/presentation/apply/view/widgets/custom_password_field.dart new file mode 100644 index 0000000..76d6905 --- /dev/null +++ b/lib/presentation/apply/view/widgets/custom_password_field.dart @@ -0,0 +1,70 @@ +import 'package:elevate_tracking_app/core/constants/app_colors.dart'; +import 'package:elevate_tracking_app/core/constants/widgets_keys.dart'; +import 'package:elevate_tracking_app/core/utils/validations.dart'; +import 'package:elevate_tracking_app/generated/l10n.dart'; +import 'package:elevate_tracking_app/presentation/apply/view_model/apply_event.dart'; +import 'package:elevate_tracking_app/presentation/apply/view_model/apply_view_model.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +class CustomPasswordField extends StatelessWidget { + const CustomPasswordField({super.key}); + + @override + Widget build(BuildContext context) { + final local = AppLocalizations.of(context); + final theme = Theme.of(context); + final cubit = BlocProvider.of(context); + return Row( + spacing: 17.sp, + children: [ + Expanded( + child: ValueListenableBuilder( + valueListenable: cubit.isPasswordVisible, + builder: (context, value, child) => TextFormField( + obscureText: cubit.isPasswordVisible.value, + style: theme.textTheme.bodyMedium, + controller: cubit.controllerPassword, + validator: Validations.validatePassword, + key: const Key(WidgetsKeys.kApplyScreenPasswordFormField), + keyboardType: TextInputType.number, + decoration: InputDecoration( + suffixIcon: GestureDetector( + onTap: () => cubit.doIntent(ApplyPasswordVisibilityEvent()), + child: Icon( + value ? Icons.visibility_off : Icons.visibility, + color: AppColors.gray, + size: 25.sp, + ), + ), + label: Text(local.password), + hintText: local.enterPassword, + ), + ), + ), + ), + Expanded( + child: ValueListenableBuilder( + valueListenable: cubit.isPasswordVisible, + builder: (context, value, child) => TextFormField( + obscureText: cubit.isPasswordVisible.value, + style: theme.textTheme.bodyMedium, + controller: cubit.controllerConfirmPassword, + key: const Key(WidgetsKeys.kApplyScreenConfirmPasswordFormField), + keyboardType: TextInputType.number, + decoration: InputDecoration( + label: Text(local.confirmPassword), + hintText: local.confirmPassword, + ), + validator: (value) => Validations.validateConfirmPassword( + cubit.controllerPassword.text, + cubit.controllerConfirmPassword.text, + ), + ), + ), + ), + ], + ); + } +} diff --git a/lib/presentation/apply/view/widgets/custom_vehicle_type_text_filed.dart b/lib/presentation/apply/view/widgets/custom_vehicle_type_text_filed.dart new file mode 100644 index 0000000..f15cee3 --- /dev/null +++ b/lib/presentation/apply/view/widgets/custom_vehicle_type_text_filed.dart @@ -0,0 +1,117 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:elevate_tracking_app/core/constants/app_colors.dart'; +import 'package:elevate_tracking_app/core/constants/app_icons.dart'; +import 'package:elevate_tracking_app/core/constants/widgets_keys.dart'; +import 'package:elevate_tracking_app/core/utils/validations.dart'; +import 'package:elevate_tracking_app/domain/entites/vehicles_entity.dart'; +import 'package:elevate_tracking_app/generated/l10n.dart'; +import 'package:elevate_tracking_app/presentation/apply/view_model/apply_view_model.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +class CustomVehicleTypeTextFiled extends StatelessWidget { + const CustomVehicleTypeTextFiled({super.key}); + + @override + Widget build(BuildContext context) { + final local = AppLocalizations.of(context); + final theme = Theme.of(context); + final cubit = BlocProvider.of(context); + return BlocBuilder( + builder: (context, state) { + return TextFormField( + controller: cubit.controllerVehicleType, + validator: Validations.validateText, + style: theme.textTheme.bodyMedium, + readOnly: true, + key: const Key(WidgetsKeys.kApplyScreenVehicleTypeFormField), + decoration: InputDecoration( + label: Text(local.vehicleType), + prefixIcon: Padding( + padding: const EdgeInsets.all(4), + child: SizedBox( + height: 30.h, + width: 30.w, + child: ValueListenableBuilder( + valueListenable: cubit.selectedVehicle, + builder: (context, value, child) { + return state.allVehicleList?.isLoading == true + ? const Icon( + Icons.directions_car_outlined, + color: AppColors.gray, + ) + : state.allVehicleList?.data != null + ? CachedNetworkImage( + imageUrl: cubit.selectedVehicle.value?.image ?? "", + placeholder: (context, url) => const Icon( + Icons.directions_car_outlined, + color: AppColors.gray, + ), + errorWidget: (context, url, error) => const Icon( + Icons.directions_car_outlined, + color: AppColors.gray, + ), + ) + : const Icon( + Icons.directions_car_outlined, + color: AppColors.gray, + ); + }, + ), + ), + ), + suffixIcon: GestureDetector( + onTap: () {}, + child: PopupMenuButton( + icon: ImageIcon( + key: const Key(WidgetsKeys.kApplyScreenArrowDown), + const AssetImage(AppIcons.iconArrowDown), + color: theme.colorScheme.secondary, + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadiusGeometry.circular(8), + ), + onSelected: (value) { + cubit.selectedVehicle.value = value; + cubit.controllerVehicleType.text = value.type; + }, + itemBuilder: (context) => List.generate( + state.allVehicleList?.data?.length ?? 0, + (index) => PopupMenuItem( + value: state.allVehicleList?.data?[index], + child: Row( + spacing: 10.sp, + children: [ + SizedBox( + width: 30.w, + height: 30.h, + child: CachedNetworkImage( + imageUrl: + state.allVehicleList?.data?[index].image ?? "", + placeholder: (context, url) => const Icon( + Icons.directions_car_outlined, + color: AppColors.gray, + ), + errorWidget: (context, url, error) => const Icon( + Icons.directions_car_outlined, + color: AppColors.gray, + ), + ), + ), + Text( + state.allVehicleList?.data?[index].type ?? "", + style: theme.textTheme.headlineSmall, + ), + ], + ), + ), + ), + ), + ), + ), + ); + }, + ); + } +} diff --git a/lib/presentation/apply/view_model/apply_event.dart b/lib/presentation/apply/view_model/apply_event.dart new file mode 100644 index 0000000..664abb1 --- /dev/null +++ b/lib/presentation/apply/view_model/apply_event.dart @@ -0,0 +1,11 @@ +sealed class ApplyEvent {} + +class ApplyEventPickVehicleLicensePhoto extends ApplyEvent {} + +class ApplyEventPickIDImage extends ApplyEvent {} + +class ApplyEventGetAllData extends ApplyEvent {} + +class ApplyValidationField extends ApplyEvent {} + +class ApplyPasswordVisibilityEvent extends ApplyEvent {} diff --git a/lib/presentation/apply/view_model/apply_view_model.dart b/lib/presentation/apply/view_model/apply_view_model.dart new file mode 100644 index 0000000..f64c903 --- /dev/null +++ b/lib/presentation/apply/view_model/apply_view_model.dart @@ -0,0 +1,210 @@ +import 'dart:io'; +import 'package:elevate_tracking_app/core/api_result/api_result.dart'; +import 'package:elevate_tracking_app/core/base_state/base_state.dart'; +import 'package:elevate_tracking_app/core/constants/const_keys.dart'; +import 'package:elevate_tracking_app/domain/entites/apply_response_entity.dart'; +import 'package:elevate_tracking_app/domain/entites/country_entity.dart'; +import 'package:elevate_tracking_app/domain/entites/request/apply_request_entity.dart'; +import 'package:elevate_tracking_app/domain/entites/vehicles_entity.dart'; +import 'package:elevate_tracking_app/domain/use_cases/apply_use_case.dart'; +import 'package:elevate_tracking_app/domain/use_cases/get_all_country_use_case.dart'; +import 'package:elevate_tracking_app/domain/use_cases/get_all_vehicles_use_case.dart'; +import 'package:elevate_tracking_app/presentation/apply/view_model/apply_event.dart'; +import 'package:equatable/equatable.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:image_picker/image_picker.dart'; +import 'package:injectable/injectable.dart'; +import 'package:path/path.dart' as p; +part 'apply_view_model_state.dart'; + +@injectable +class ApplyViewModel extends Cubit { + ApplyViewModel( + this._allVehiclesUseCase, + this._allCountryUseCase, + this._applyUseCase, + ) : super(const ApplyViewModelState()) { + controllerFirstName.addListener(checkEnableButton); + controllerSecondName.addListener(checkEnableButton); + controllerCountry.addListener(checkEnableButton); + controllerVehicleType.addListener(checkEnableButton); + controllerVehicleNumber.addListener(checkEnableButton); + controllerVehicleLicense.addListener(checkEnableButton); + controllerEmail.addListener(checkEnableButton); + controllerPhoneNumber.addListener(checkEnableButton); + controllerIdNumber.addListener(checkEnableButton); + controllerIdImage.addListener(checkEnableButton); + controllerPassword.addListener(checkEnableButton); + controllerConfirmPassword.addListener(checkEnableButton); + } + final GetAllVehiclesUseCase _allVehiclesUseCase; + final GetAllCountryUseCase _allCountryUseCase; + final ApplyUseCase _applyUseCase; + TextEditingController controllerCountry = TextEditingController(); + TextEditingController controllerFirstName = TextEditingController(); + TextEditingController controllerSecondName = TextEditingController(); + TextEditingController controllerVehicleType = TextEditingController(); + TextEditingController controllerVehicleNumber = TextEditingController(); + TextEditingController controllerVehicleLicense = TextEditingController(); + TextEditingController controllerEmail = TextEditingController(); + TextEditingController controllerPhoneNumber = TextEditingController(); + TextEditingController controllerIdNumber = TextEditingController(); + TextEditingController controllerIdImage = TextEditingController(); + TextEditingController controllerPassword = TextEditingController(); + TextEditingController controllerConfirmPassword = TextEditingController(); + ValueNotifier gender = ValueNotifier(null); + final ValueNotifier isUserAuthenticated = ValueNotifier(false); + String? vehicleImagePath; + String? idImagePath; + GlobalKey globalKey = GlobalKey(); + ValueNotifier selectedVehicle = ValueNotifier( + null, + ); + ValueNotifier selectedCountry = ValueNotifier( + null, + ); + ValueNotifier isPasswordVisible = ValueNotifier(false); + Future doIntent(ApplyEvent event) async { + switch (event) { + case ApplyEventPickVehicleLicensePhoto(): + _getVehicleLicense(); + case ApplyEventPickIDImage(): + _getIdImage(); + case ApplyEventGetAllData(): + _getAllData(); + case ApplyValidationField(): + _validateFiled(); + case ApplyPasswordVisibilityEvent(): + _passwordVisibility(); + } + } + + void _passwordVisibility() { + isPasswordVisible.value = !isPasswordVisible.value; + } + + Future _validateFiled() async { + if (globalKey.currentState!.validate()) { + emit(state.copyWith(applyFun: BaseState.loading())); + final result = await _applyUseCase.call( + ApplyRequestEntity( + country: controllerCountry.text, + firstName: controllerFirstName.text, + lastName: controllerSecondName.text, + vehicleType: selectedVehicle.value?.id ?? "", + vehicleNumber: controllerVehicleNumber.text, + nid: controllerIdNumber.text, + email: controllerEmail.text, + password: controllerPassword.text, + rePassword: controllerConfirmPassword.text, + gender: gender.value ?? ConstKeys.kMale, + phone: controllerPhoneNumber.text, + nidImg: File(idImagePath!), + vehicleLicense: File(vehicleImagePath!), + ), + ); + switch (result) { + case ApiSuccessResult(): + emit( + state.copyWith( + applyFun: BaseState.success(result.data), + ), + ); + case ApiErrorResult(): + emit( + state.copyWith( + applyFun: BaseState.error( + result.errorMessage, + ), + ), + ); + } + } + } + + void checkEnableButton() { + if (controllerCountry.text.isNotEmpty && + controllerFirstName.text.isNotEmpty && + controllerSecondName.text.isNotEmpty && + controllerVehicleType.text.isNotEmpty && + controllerVehicleNumber.text.isNotEmpty && + controllerVehicleLicense.text.isNotEmpty && + controllerEmail.text.isNotEmpty && + controllerPhoneNumber.text.isNotEmpty && + controllerIdNumber.text.isNotEmpty && + controllerIdImage.text.isNotEmpty && + controllerPassword.text.isNotEmpty && + controllerConfirmPassword.text.isNotEmpty) { + isUserAuthenticated.value = true; + } else { + isUserAuthenticated.value = false; + } + } + + Future _getAllData() async { + await Future.wait([_getAllCountry(), _getAllVehicles()]); + } + + Future _getVehicleLicense() async { + final ImagePicker picker = ImagePicker(); + final pickedFile = await picker.pickImage(source: ImageSource.gallery); + if (pickedFile != null) { + emit(state.copyWith(vehicleImagePath: pickedFile.path)); + vehicleImagePath = pickedFile.path; + controllerVehicleLicense.text = p.basename(pickedFile.path); + } + } + + Future _getIdImage() async { + final ImagePicker picker = ImagePicker(); + final pickedFile = await picker.pickImage(source: ImageSource.gallery); + if (pickedFile != null) { + emit(state.copyWith(idImagePath: pickedFile.path)); + idImagePath = pickedFile.path; + controllerIdImage.text = p.basename(pickedFile.path); + } + } + + Future _getAllVehicles() async { + emit(state.copyWith(allVehicleList: BaseState.loading())); + final result = await _allVehiclesUseCase.call(); + switch (result) { + case ApiSuccessResult>(): + emit(state.copyWith(allVehicleList: BaseState.success(result.data))); + case ApiErrorResult>(): + emit( + state.copyWith(allVehicleList: BaseState.error(result.errorMessage)), + ); + } + } + + Future _getAllCountry() async { + emit(state.copyWith(allCountry: BaseState.loading())); + final result = await _allCountryUseCase.call(); + switch (result) { + case ApiSuccessResult>(): + emit(state.copyWith(allCountry: BaseState.success(result.data))); + case ApiErrorResult>(): + emit(state.copyWith(allCountry: BaseState.error(result.errorMessage))); + } + } + + @override + Future close() { + controllerCountry.dispose(); + controllerFirstName.dispose(); + controllerSecondName.dispose(); + controllerVehicleType.dispose(); + controllerVehicleNumber.dispose(); + controllerVehicleLicense.dispose(); + controllerEmail.dispose(); + controllerPhoneNumber.dispose(); + controllerIdNumber.dispose(); + controllerIdImage.dispose(); + controllerPassword.dispose(); + controllerConfirmPassword.dispose(); + return super.close(); + } +} diff --git a/lib/presentation/apply/view_model/apply_view_model_state.dart b/lib/presentation/apply/view_model/apply_view_model_state.dart new file mode 100644 index 0000000..15e871c --- /dev/null +++ b/lib/presentation/apply/view_model/apply_view_model_state.dart @@ -0,0 +1,40 @@ +part of 'apply_view_model.dart'; + +class ApplyViewModelState extends Equatable { + const ApplyViewModelState({ + this.vehicleImagePath, + this.allVehicleList, + this.allCountry, + this.idImagePath, + this.applyFun, + }); + final BaseState>? allVehicleList; + final BaseState>? allCountry; + final BaseState? applyFun; + final String? vehicleImagePath; + final String? idImagePath; + ApplyViewModelState copyWith({ + String? vehicleImagePath, + String? idImagePath, + BaseState>? allVehicleList, + BaseState>? allCountry, + BaseState? applyFun, + }) { + return ApplyViewModelState( + idImagePath: idImagePath ?? this.idImagePath, + vehicleImagePath: vehicleImagePath ?? this.vehicleImagePath, + allVehicleList: allVehicleList ?? this.allVehicleList, + allCountry: allCountry ?? this.allCountry, + applyFun: applyFun ?? this.applyFun, + ); + } + + @override + List get props => [ + vehicleImagePath, + allVehicleList, + allCountry, + idImagePath, + applyFun, + ]; +} diff --git a/pubspec.yaml b/pubspec.yaml index ca51754..e8de147 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -88,6 +88,7 @@ flutter: assets: - assets/images/ - assets/icons/ + - assets/data/ # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/to/resolution-aware-images # For details regarding adding assets from package dependencies, see diff --git a/test/api/client/api_client_test.dart b/test/api/client/api_client_test.dart index 60f8b7d..a2ff08c 100644 --- a/test/api/client/api_client_test.dart +++ b/test/api/client/api_client_test.dart @@ -2,6 +2,7 @@ import 'package:dio/dio.dart'; import 'package:elevate_tracking_app/api/client/api_client.dart'; import 'package:elevate_tracking_app/api/models/requests/apply_request_dto.dart'; import 'package:elevate_tracking_app/api/models/responses/apply_response_dto.dart'; +import 'package:elevate_tracking_app/api/models/responses/vehicles_response.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:http_mock_adapter/http_mock_adapter.dart' show DioAdapter, Matchers; @@ -16,7 +17,7 @@ void main() { late ApiClient apiClient; setUp(() { - dio = Dio(BaseOptions(baseUrl: "https://flower.elevateegy.com/api")); + dio = Dio(BaseOptions(baseUrl: "https://flower.elevateegy.com/")); dioAdapter = DioAdapter(dio: dio); apiClient = ApiClient(dio); }); @@ -26,7 +27,7 @@ void main() { await ApplyFixture.fakeApiClintRequestDto(); final ApplyResponseDto responseDto = ApplyFixture.fakeResponseDto(); dioAdapter.onPost( - "v1/drivers/apply", + "api/v1/drivers/apply", (server) => server.reply(200, responseDto.toJson()), data: Matchers.any, ); @@ -37,5 +38,19 @@ void main() { expect(result.message, equals(responseDto.message)); expect(result.driver.firstName, equals(responseDto.driver.firstName)); }); + test("test should be return VehiclesResponse ", () async { + //ARRANGE + final VehiclesResponse responseDto = ApplyFixture.fakeVehiclesResponse(); + dioAdapter.onGet( + "api/v1/vehicles", + (server) => server.reply(200, responseDto.toJson()), + ); + //ACT + final result = await apiClient.getAllVehicles(); + //ASSERT + expect(result, isA()); + expect(result.message, equals(responseDto.message)); + expect(result.vehicles?.first.id, equals(responseDto.vehicles?.first.id)); + }); }); } diff --git a/test/api/data_source/auth_local_data_source_impl_test.dart b/test/api/data_source/auth_local_data_source_impl_test.dart new file mode 100644 index 0000000..b664585 --- /dev/null +++ b/test/api/data_source/auth_local_data_source_impl_test.dart @@ -0,0 +1,43 @@ +import 'package:elevate_tracking_app/api/data_source/auth_local_data_source_impl.dart'; +import 'package:elevate_tracking_app/core/api_result/api_result.dart'; +import 'package:elevate_tracking_app/domain/entites/country_entity.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; + +import '../../fixture/apply_fixture.dart'; +import '../../fixture/fake_file_json.dart'; + +@GenerateMocks([AssetBundle]) +void main() { + group("test get all country", () { + late AuthLocalDataSourceImpl dataSource; + late FakeAssetBundle fakeBundle; + setUp(() { + fakeBundle = ApplyFixture.fakeBundle; + dataSource = AuthLocalDataSourceImpl(testBundle: fakeBundle); + }); + + test('returns List when fixture is valid', () async { + final result = await dataSource.getAllCountry(); + expect(result, isA>>()); + if (result is ApiSuccessResult>) { + final countries = result.data; + expect(countries, isNotEmpty); + expect(countries.first.name, equals('Turkey')); + expect(countries.first.flag, equals('🇹🇷')); + } else { + fail('Expected success result'); + } + }); + + test('returns error when asset is missing', () async { + final emptyBundle = FakeAssetBundle({}); + final ds = AuthLocalDataSourceImpl(testBundle: emptyBundle); + + final result = await ds.getAllCountry(); + + expect(result, isA()); + }); + }); +} diff --git a/test/api/mapper/apply_mapper_test.dart b/test/api/mapper/apply_mapper_test.dart index f5d2c4b..0db4645 100644 --- a/test/api/mapper/apply_mapper_test.dart +++ b/test/api/mapper/apply_mapper_test.dart @@ -1,5 +1,8 @@ import 'package:elevate_tracking_app/api/mapper/apply_mapper.dart'; import 'package:elevate_tracking_app/api/models/responses/apply_response_dto.dart'; +import 'package:elevate_tracking_app/api/models/responses/country_dto.dart'; +import 'package:elevate_tracking_app/api/models/responses/vehicle.dart'; +import 'package:elevate_tracking_app/domain/entites/country_entity.dart'; import 'package:elevate_tracking_app/domain/entites/request/apply_request_entity.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -27,7 +30,6 @@ void main() { test("toEntity should map correctly", () { // ARRANGE: Fake DTO final ApplyResponseDto dto = ApplyFixture.fakeResponseDto(); - // ACT: final entity = dto.toEntity(); // ASSERT: @@ -42,5 +44,26 @@ void main() { expect(entity.vehicleLicense, equals(dto.driver.vehicleLicense)); expect(entity.vehicleType, equals(dto.driver.vehicleType)); }); + test("vehicle toEntity should map correctly", () { + // ARRANGE: Fake DTO + final Vehicle dto = ApplyFixture.fakeVehicleDto(); + + // ACT: + final entity = dto.toEntity(); + // ASSERT: + expect(entity.id, equals(dto.id)); + expect(entity.image, equals(dto.image)); + expect(entity.type, equals(dto.type)); + }); + test("country toEntity should map correctly", () { + //ARRANGE: + final CountryDto dto = ApplyFixture.fakeCountryDto(); + //ACT: + final CountryEntity entity = dto.toEntity(); + //ASSERT: + expect(entity.currency, equals(dto.currency)); + expect(entity.name, equals(dto.name)); + expect(entity.flag, equals(dto.flag)); + }); }); } diff --git a/test/data/data_source/auth_remote_data_source_test.dart b/test/data/data_source/auth_remote_data_source_test.dart index c320ee5..fc826a3 100644 --- a/test/data/data_source/auth_remote_data_source_test.dart +++ b/test/data/data_source/auth_remote_data_source_test.dart @@ -2,8 +2,10 @@ import 'package:dio/dio.dart'; import 'package:elevate_tracking_app/api/client/api_client.dart'; import 'package:elevate_tracking_app/api/data_source/auth_remote_data_source_impl.dart'; import 'package:elevate_tracking_app/api/models/responses/apply_response_dto.dart'; +import 'package:elevate_tracking_app/api/models/responses/vehicles_response.dart'; import 'package:elevate_tracking_app/core/api_result/api_result.dart'; import 'package:elevate_tracking_app/domain/entites/apply_response_entity.dart'; +import 'package:elevate_tracking_app/domain/entites/vehicles_entity.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; @@ -14,8 +16,12 @@ import 'auth_remote_data_source_test.mocks.dart'; @GenerateMocks([ApiClient]) void main() { group("Apply remote data Source test", () { - final fakeRequestEntity = ApplyFixture.fakeRequestEntity(); - late ApplyResponseDto fakeResponseDto; + final fakeApplyRequestEntity = ApplyFixture.fakeRequestEntity(); + late ApplyResponseDto fakeApplyResponseDto; + final VehiclesResponse fakeVehicleResponse = + ApplyFixture.fakeVehiclesResponse(); + final List fakeListVehicles = + ApplyFixture.fakeVehicleEntity(); final DioException fakeDioException = DioException( requestOptions: RequestOptions(), message: "fake_message", @@ -24,14 +30,16 @@ void main() { late MockApiClient mockApiClient; late AuthRemoteDataSourceImpl authRemoteDataSourceImpl; setUp(() { - fakeResponseDto = ApplyFixture.fakeResponseDto(); + fakeApplyResponseDto = ApplyFixture.fakeResponseDto(); mockApiClient = MockApiClient(); authRemoteDataSourceImpl = AuthRemoteDataSourceImpl(mockApiClient); }); test("apply success", () async { - final fakeRequestEn = await fakeRequestEntity; + final fakeRequestEn = await fakeApplyRequestEntity; //ARRANGE - when(mockApiClient.apply(any)).thenAnswer((_) async => fakeResponseDto); + when( + mockApiClient.apply(any), + ).thenAnswer((_) async => fakeApplyResponseDto); //ACT final result = await authRemoteDataSourceImpl.apply( request: fakeRequestEn, @@ -40,12 +48,12 @@ void main() { expect(result, isA>()); expect( (result as ApiSuccessResult).data.message, - equals(fakeResponseDto.message), + equals(fakeApplyResponseDto.message), ); verify(mockApiClient.apply(any)).called(1); }); test("apply dio exception", () async { - final fakeRequestEn = await fakeRequestEntity; + final fakeRequestEn = await fakeApplyRequestEntity; //ARRANGE when(mockApiClient.apply(any)).thenThrow(fakeDioException); //ACT @@ -61,7 +69,7 @@ void main() { verify(mockApiClient.apply(any)).called(1); }); test("apply exception", () async { - final fakeRequestEn = await fakeRequestEntity; + final fakeRequestEn = await fakeApplyRequestEntity; //ARRANGE when(mockApiClient.apply(any)).thenThrow(fakeException); //ACT @@ -76,5 +84,46 @@ void main() { ); verify(mockApiClient.apply(any)).called(1); }); + test("get all vehicle success", () async { + //ARRANGE + when( + mockApiClient.getAllVehicles(), + ).thenAnswer((_) async => fakeVehicleResponse); + //ACT + final result = await authRemoteDataSourceImpl.getAllVehicles(); + //ASSERT + expect(result, isA>>()); + expect( + (result as ApiSuccessResult>).data.first.id, + equals(fakeListVehicles.first.id), + ); + verify(mockApiClient.getAllVehicles()).called(1); + }); + test("get all vehicle dio exception", () async { + //ARRANGE + when(mockApiClient.getAllVehicles()).thenThrow(fakeDioException); + //ACT + final result = await authRemoteDataSourceImpl.getAllVehicles(); + //ASSERT + expect(result, isA>>()); + expect( + (result as ApiErrorResult>).errorMessage, + equals(contains(fakeDioException.message)), + ); + verify(mockApiClient.getAllVehicles()).called(1); + }); + test("get all vehicle exception", () async { + //ARRANGE + when(mockApiClient.getAllVehicles()).thenThrow(fakeException); + //ACT + final result = await authRemoteDataSourceImpl.getAllVehicles(); + //ASSERT + expect(result, isA>>()); + expect( + (result as ApiErrorResult>).error, + equals(fakeException), + ); + verify(mockApiClient.getAllVehicles()).called(1); + }); }); } diff --git a/test/data/repo/auth_repo_impl_test.dart b/test/data/repo/auth_repo_impl_test.dart index 7b8e84d..6d5b7d6 100644 --- a/test/data/repo/auth_repo_impl_test.dart +++ b/test/data/repo/auth_repo_impl_test.dart @@ -1,8 +1,11 @@ import 'package:dio/dio.dart'; +import 'package:elevate_tracking_app/api/data_source/auth_local_data_source_impl.dart'; import 'package:elevate_tracking_app/api/data_source/auth_remote_data_source_impl.dart'; import 'package:elevate_tracking_app/core/api_result/api_result.dart'; import 'package:elevate_tracking_app/data/repo/auth_repo_impl.dart'; import 'package:elevate_tracking_app/domain/entites/apply_response_entity.dart'; +import 'package:elevate_tracking_app/domain/entites/country_entity.dart'; +import 'package:elevate_tracking_app/domain/entites/vehicles_entity.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; @@ -10,12 +13,16 @@ import 'package:mockito/mockito.dart'; import '../../fixture/apply_fixture.dart'; import 'auth_repo_impl_test.mocks.dart'; -@GenerateMocks([AuthRemoteDataSourceImpl]) +@GenerateMocks([AuthRemoteDataSourceImpl, AuthLocalDataSourceImpl]) void main() { group("Test Apply", () { - final fakeRequestEntity = ApplyFixture.fakeRequestEntity(); - final fakeResponseEntity = ApplyFixture.fakeResponseEntity(); + final fakeApplyRequestEntity = ApplyFixture.fakeRequestEntity(); + final fakeApplyResponseEntity = ApplyFixture.fakeResponseEntity(); + final fakeAllCountry = ApplyFixture.fakeCountryEntityList(); + final List fakeListVehicles = + ApplyFixture.fakeVehicleEntity(); late MockAuthRemoteDataSourceImpl mockAuthRemoteDataSourceImpl; + late MockAuthLocalDataSourceImpl mockAuthLocalDataSourceImpl; late AuthRepoImpl authRepoImpl; final DioException dioException = DioException( requestOptions: RequestOptions(), @@ -24,18 +31,34 @@ void main() { final Exception fakeException = Exception(); setUp(() { mockAuthRemoteDataSourceImpl = MockAuthRemoteDataSourceImpl(); - authRepoImpl = AuthRepoImpl(mockAuthRemoteDataSourceImpl); + mockAuthLocalDataSourceImpl = MockAuthLocalDataSourceImpl(); + authRepoImpl = AuthRepoImpl( + mockAuthRemoteDataSourceImpl, + mockAuthLocalDataSourceImpl, + ); provideDummy>( - ApiSuccessResult(fakeResponseEntity), + ApiSuccessResult(fakeApplyResponseEntity), ); provideDummy>( ApiErrorResult(fakeException), ); + provideDummy>>( + ApiSuccessResult>(fakeListVehicles), + ); + provideDummy>>( + ApiErrorResult>(fakeException), + ); + provideDummy>>( + ApiSuccessResult>(fakeAllCountry), + ); + provideDummy>>( + ApiErrorResult>(fakeException), + ); }); test("apply success ApiResult ApplyResponseEntity", () async { - final fakeRequestEn = await fakeRequestEntity; + final fakeRequestEn = await fakeApplyRequestEntity; final expectResult = ApiSuccessResult( - fakeResponseEntity, + fakeApplyResponseEntity, ); when( mockAuthRemoteDataSourceImpl.apply(request: fakeRequestEn), @@ -45,7 +68,7 @@ void main() { expect(result, isA>()); expect( (result as ApiSuccessResult).data.id, - equals(fakeResponseEntity.id), + equals(fakeApplyResponseEntity.id), ); verify( @@ -53,7 +76,7 @@ void main() { ).called(1); }); test("apply failure ApiResult DioError", () async { - final fakeRequestEn = await fakeRequestEntity; + final fakeRequestEn = await fakeApplyRequestEntity; final expectResult = ApiErrorResult(dioException); when( mockAuthRemoteDataSourceImpl.apply(request: fakeRequestEn), @@ -71,7 +94,7 @@ void main() { ).called(1); }); test("apply failure ApiResult Exception", () async { - final fakeRequestEn = await fakeRequestEntity; + final fakeRequestEn = await fakeApplyRequestEntity; final expectResult = ApiErrorResult(fakeException); when( mockAuthRemoteDataSourceImpl.apply(request: fakeRequestEn), @@ -88,5 +111,84 @@ void main() { mockAuthRemoteDataSourceImpl.apply(request: fakeRequestEn), ).called(1); }); + test("get all vehicles success ApiResult List", () async { + final expectResult = ApiSuccessResult>( + fakeListVehicles, + ); + when( + mockAuthRemoteDataSourceImpl.getAllVehicles(), + ).thenAnswer((_) async => expectResult); + + final result = await authRepoImpl.getAllVehicles(); + expect(result, isA>>()); + expect( + (result as ApiSuccessResult>).data.last.type, + equals(fakeListVehicles.last.type), + ); + + verify(mockAuthRemoteDataSourceImpl.getAllVehicles()).called(1); + }); + test("get all vehicles failure ApiResult DioError", () async { + final expectResult = ApiErrorResult>(dioException); + when( + mockAuthRemoteDataSourceImpl.getAllVehicles(), + ).thenAnswer((_) async => expectResult); + + final result = await authRepoImpl.getAllVehicles(); + expect(result, isA>>()); + expect( + (result as ApiErrorResult>).errorMessage, + contains(dioException.message), + ); + + verify(mockAuthRemoteDataSourceImpl.getAllVehicles()).called(1); + }); + test("get all vehicles failure ApiResult Exception", () async { + final expectResult = ApiErrorResult>(fakeException); + when( + mockAuthRemoteDataSourceImpl.getAllVehicles(), + ).thenAnswer((_) async => expectResult); + + final result = await authRepoImpl.getAllVehicles(); + expect(result, isA>>()); + expect( + (result as ApiErrorResult>).error, + equals(fakeException), + ); + + verify(mockAuthRemoteDataSourceImpl.getAllVehicles()).called(1); + }); + test("get all country success ApiResult List", () async { + final expectResult = ApiSuccessResult>( + fakeAllCountry, + ); + when( + mockAuthLocalDataSourceImpl.getAllCountry(), + ).thenAnswer((_) async => expectResult); + + final result = await authRepoImpl.getAllCountries(); + expect(result, isA>>()); + expect( + (result as ApiSuccessResult>).data.last.currency, + equals(fakeAllCountry.last.currency), + ); + + verify(mockAuthLocalDataSourceImpl.getAllCountry()).called(1); + }); + test("get all country failure ApiResult Exception", () async { + final expectResult = ApiErrorResult>(fakeException); + when( + mockAuthLocalDataSourceImpl.getAllCountry(), + ).thenAnswer((_) async => expectResult); + + final result = await authRepoImpl.getAllCountries(); + expect(result, isA>>()); + expect( + (result as ApiErrorResult>).error, + equals(fakeException), + ); + + verify(mockAuthLocalDataSourceImpl.getAllCountry()).called(1); + }); }); } diff --git a/test/domain/use_cases/get_all_country_use_case_test.dart b/test/domain/use_cases/get_all_country_use_case_test.dart new file mode 100644 index 0000000..a823ddc --- /dev/null +++ b/test/domain/use_cases/get_all_country_use_case_test.dart @@ -0,0 +1,63 @@ +import 'package:elevate_tracking_app/core/api_result/api_result.dart'; +import 'package:elevate_tracking_app/data/repo/auth_repo_impl.dart'; +import 'package:elevate_tracking_app/domain/entites/country_entity.dart'; +import 'package:elevate_tracking_app/domain/use_cases/get_all_country_use_case.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; + +import '../../fixture/apply_fixture.dart'; +import 'get_all_country_use_case_test.mocks.dart'; + +@GenerateMocks([AuthRepoImpl]) +void main() { + group("Test get all country use case", () { + final List fakeListCountry = + ApplyFixture.fakeCountryEntityList(); + late MockAuthRepoImpl mockAuthRepoImpl; + late GetAllCountryUseCase useCase; + final Exception fakeException = Exception(); + setUp(() { + mockAuthRepoImpl = MockAuthRepoImpl(); + useCase = GetAllCountryUseCase(mockAuthRepoImpl); + provideDummy>>( + ApiSuccessResult>(fakeListCountry), + ); + provideDummy>>( + ApiErrorResult>(fakeException), + ); + }); + test("get all country use case success", () async { + final expectResult = ApiSuccessResult>( + fakeListCountry, + ); + when( + mockAuthRepoImpl.getAllCountries(), + ).thenAnswer((_) async => expectResult); + + final result = await useCase.call(); + expect(result, isA>>()); + expect( + (result as ApiSuccessResult>).data.length, + equals(fakeListCountry.length), + ); + + verify(mockAuthRepoImpl.getAllCountries()).called(1); + }); + test("get all country use case failure ApiResult Exception", () async { + final expectResult = ApiErrorResult>(fakeException); + when( + mockAuthRepoImpl.getAllCountries(), + ).thenAnswer((_) async => expectResult); + + final result = await useCase.call(); + expect(result, isA>>()); + expect( + (result as ApiErrorResult>).error, + equals(fakeException), + ); + + verify(mockAuthRepoImpl.getAllCountries()).called(1); + }); + }); +} diff --git a/test/domain/use_cases/get_all_vehicles_test.dart b/test/domain/use_cases/get_all_vehicles_test.dart new file mode 100644 index 0000000..1d38a58 --- /dev/null +++ b/test/domain/use_cases/get_all_vehicles_test.dart @@ -0,0 +1,85 @@ +import 'package:dio/dio.dart'; +import 'package:elevate_tracking_app/core/api_result/api_result.dart'; +import 'package:elevate_tracking_app/data/repo/auth_repo_impl.dart'; +import 'package:elevate_tracking_app/domain/entites/vehicles_entity.dart'; +import 'package:elevate_tracking_app/domain/use_cases/get_all_vehicles_use_case.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import '../../fixture/apply_fixture.dart'; +import 'apply_use_case_test.mocks.dart'; + +@GenerateMocks([AuthRepoImpl]) +void main() { + group("Test get all vehicles use case", () { + final List fakeListVehicles = + ApplyFixture.fakeVehicleEntity(); + late MockAuthRepoImpl mockAuthRepoImpl; + late GetAllVehiclesUseCase useCase; + final DioException dioException = DioException( + requestOptions: RequestOptions(), + message: "fake_dio_message", + ); + final Exception fakeException = Exception(); + setUp(() { + mockAuthRepoImpl = MockAuthRepoImpl(); + useCase = GetAllVehiclesUseCase(mockAuthRepoImpl); + provideDummy>>( + ApiSuccessResult>(fakeListVehicles), + ); + provideDummy>>( + ApiErrorResult>(fakeException), + ); + }); + test( + "get all vehicles use case success ApiResult ApplyResponseEntity", + () async { + final expectResult = ApiSuccessResult>( + fakeListVehicles, + ); + when( + mockAuthRepoImpl.getAllVehicles(), + ).thenAnswer((_) async => expectResult); + + final result = await useCase.call(); + expect(result, isA>>()); + expect( + (result as ApiSuccessResult>).data.length, + equals(fakeListVehicles.length), + ); + + verify(mockAuthRepoImpl.getAllVehicles()).called(1); + }, + ); + test("get all vehicles use case failure ApiResult DioError", () async { + final expectResult = ApiErrorResult>(dioException); + when( + mockAuthRepoImpl.getAllVehicles(), + ).thenAnswer((_) async => expectResult); + + final result = await useCase.call(); + expect(result, isA>>()); + expect( + (result as ApiErrorResult>).errorMessage, + contains(dioException.message), + ); + + verify(mockAuthRepoImpl.getAllVehicles()).called(1); + }); + test("get all vehicles use case failure ApiResult Exception", () async { + final expectResult = ApiErrorResult>(fakeException); + when( + mockAuthRepoImpl.getAllVehicles(), + ).thenAnswer((_) async => expectResult); + + final result = await useCase.call(); + expect(result, isA>>()); + expect( + (result as ApiErrorResult>).error, + equals(fakeException), + ); + + verify(mockAuthRepoImpl.getAllVehicles()).called(1); + }); + }); +} diff --git a/test/fixture/apply_fixture.dart b/test/fixture/apply_fixture.dart index c28f625..9b9b33f 100644 --- a/test/fixture/apply_fixture.dart +++ b/test/fixture/apply_fixture.dart @@ -1,11 +1,20 @@ +import 'dart:convert'; import 'dart:io'; +import 'package:elevate_tracking_app/api/mapper/apply_mapper.dart'; import 'package:elevate_tracking_app/api/models/requests/apply_request_dto.dart'; import 'package:elevate_tracking_app/api/models/responses/apply_response_dto.dart'; +import 'package:elevate_tracking_app/api/models/responses/country_dto.dart'; import 'package:elevate_tracking_app/api/models/responses/driver_dto.dart'; +import 'package:elevate_tracking_app/api/models/responses/vehicle.dart'; +import 'package:elevate_tracking_app/api/models/responses/vehicles_response.dart'; +import 'package:elevate_tracking_app/core/constants/end_points.dart'; import 'package:elevate_tracking_app/domain/entites/apply_response_entity.dart'; +import 'package:elevate_tracking_app/domain/entites/country_entity.dart'; import 'package:elevate_tracking_app/domain/entites/request/apply_request_entity.dart'; +import 'package:elevate_tracking_app/domain/entites/vehicles_entity.dart'; +import 'fake_file_json.dart'; import 'fake_image_file.dart'; class ApplyFixture { @@ -114,4 +123,128 @@ class ApplyFixture { createdAt: "2025-09-21T05:26:21.346Z", ); } + + static VehiclesResponse fakeVehiclesResponse() { + return VehiclesResponse( + message: "success", + metadata: Metadata( + currentPage: 1, + totalPages: 1, + limit: 40, + totalItems: 7, + ), + vehicles: [ + Vehicle( + id: "fake-id-122", + type: "Motor Cycle", + image: "https://fakeimg.pl/250x100/?text=MotorCycle", + createdAt: "2024-12-25T01:45:45.397Z", + updatedAt: "2024-12-25T01:45:45.397Z", + v: 0, + ), + Vehicle( + id: "fake-id-123", + type: "SUV", + image: "https://fakeimg.pl/250x100/?text=SUV", + createdAt: "2024-12-25T01:47:19.749Z", + updatedAt: "2024-12-25T01:47:19.749Z", + v: 0, + ), + Vehicle( + id: "fake-id-124", + type: "SUV", + image: "https://fakeimg.pl/250x100/?text=SUV", + createdAt: "2024-12-25T01:47:19.749Z", + updatedAt: "2024-12-25T01:47:19.749Z", + v: 0, + ), + ], + ); + } + + static List fakeVehicleEntity() { + return fakeVehiclesResponse().vehicles! + .map((entity) => entity.toEntity()) + .toList(); + } + + static Vehicle fakeVehicleDto() { + return Vehicle( + id: "fake-id-123", + type: "SUV", + image: "https://fakeimg.pl/250x100/?text=SUV", + createdAt: "2024-12-25T01:47:19.749Z", + updatedAt: "2024-12-25T01:47:19.749Z", + v: 0, + ); + } + + static CountryDto fakeCountryDto() { + return const CountryDto( + isoCode: "EG", + name: "Egypt", + phoneCode: "20", + flag: "🇪🇬", + currency: "EGP", + latitude: "26.8206", + longitude: "30.8025", + timezones: [ + Timezone( + zoneName: "Africa/Cairo", + gmtOffset: 7200, + gmtOffsetName: "UTC+02:00", + abbreviation: "EET", + tzName: "Eastern European Time", + ), + ], + ); + } + + static CountryEntity fakeCountryEntity() { + return fakeCountryDto().toEntity(); + } + + static List fakeCountryDtoList() { + return [ + fakeCountryDto(), + const CountryDto( + isoCode: "SA", + name: "Saudi Arabia", + phoneCode: "966", + flag: "🇸🇦", + currency: "SAR", + latitude: "23.8859", + longitude: "45.0792", + timezones: [ + Timezone( + zoneName: "Asia/Riyadh", + gmtOffset: 10800, + gmtOffsetName: "UTC+03:00", + abbreviation: "AST", + tzName: "Arabian Standard Time", + ), + ], + ), + ]; + } + + /// Fake List of Country Entities + static List fakeCountryEntityList() { + return fakeCountryDtoList().map((c) => c.toEntity()).toList(); + } + + static FakeAssetBundle fakeBundle = FakeAssetBundle({ + Endpoints.countryLocalData: jsonEncode([ + { + "isoCode": "TR", + "name": "Turkey", + "phoneCode": "90", + "flag": "🇹🇷", + "flagUrl": "https://flagcdn.com/w320/tr.png", + "currency": "TRY", + "latitude": "39.00000000", + "longitude": "35.00000000", + }, + ]), + }); } diff --git a/test/fixture/fake_file_json.dart b/test/fixture/fake_file_json.dart new file mode 100644 index 0000000..1ebcbd2 --- /dev/null +++ b/test/fixture/fake_file_json.dart @@ -0,0 +1,25 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; + +class FakeAssetBundle extends CachingAssetBundle { + final Map files; + FakeAssetBundle(this.files); + + @override + Future loadString(String key, {bool cache = true}) async { + if (!files.containsKey(key)) { + throw FlutterError('File not found: $key'); + } + return files[key]!; + } + + @override + Future load(String key) async { + if (!files.containsKey(key)) { + throw FlutterError('File not found: $key'); + } + final stringData = files[key]!; + final bytes = Uint8List.fromList(stringData.codeUnits); + return ByteData.view(bytes.buffer); + } +} From d5a98ac1c35f4532bd46312fd87cd560068c85fa Mon Sep 17 00:00:00 2001 From: Moaz Osama Date: Fri, 26 Sep 2025 22:58:26 +0300 Subject: [PATCH 3/6] feat: add unit test to function apply view model --- .../apply/view/widgets/apply_body.dart | 214 +--------------- .../view/widgets/apply_body_builder.dart | 231 ++++++++++++++++++ .../apply/view_model/apply_event.dart | 2 +- .../apply/view_model/apply_view_model.dart | 74 +++--- .../apply_view_model_state_test.dart | 84 +++++++ 5 files changed, 353 insertions(+), 252 deletions(-) create mode 100644 lib/presentation/apply/view/widgets/apply_body_builder.dart create mode 100644 test/presentation/apply/view_model/apply_view_model_state_test.dart diff --git a/lib/presentation/apply/view/widgets/apply_body.dart b/lib/presentation/apply/view/widgets/apply_body.dart index 91f8014..485a708 100644 --- a/lib/presentation/apply/view/widgets/apply_body.dart +++ b/lib/presentation/apply/view/widgets/apply_body.dart @@ -1,18 +1,8 @@ -import 'package:elevate_tracking_app/core/constants/app_colors.dart'; -import 'package:elevate_tracking_app/core/constants/app_icons.dart'; -import 'package:elevate_tracking_app/core/constants/widgets_keys.dart'; import 'package:elevate_tracking_app/core/custom_widget/custom_dialog.dart'; -import 'package:elevate_tracking_app/core/utils/validations.dart'; -import 'package:elevate_tracking_app/generated/l10n.dart'; -import 'package:elevate_tracking_app/presentation/apply/view/widgets/custom_apply_radio.dart'; -import 'package:elevate_tracking_app/presentation/apply/view/widgets/custom_country_text_field.dart'; -import 'package:elevate_tracking_app/presentation/apply/view/widgets/custom_password_field.dart'; -import 'package:elevate_tracking_app/presentation/apply/view/widgets/custom_vehicle_type_text_filed.dart'; -import 'package:elevate_tracking_app/presentation/apply/view_model/apply_event.dart'; +import 'package:elevate_tracking_app/presentation/apply/view/widgets/apply_body_builder.dart'; import 'package:elevate_tracking_app/presentation/apply/view_model/apply_view_model.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:go_router/go_router.dart'; class ApplyBody extends StatelessWidget { @@ -20,9 +10,6 @@ class ApplyBody extends StatelessWidget { @override Widget build(BuildContext context) { - final local = AppLocalizations.of(context); - final theme = Theme.of(context); - final cubit = BlocProvider.of(context); return BlocListener( listener: (context, state) { if (state.applyFun?.isLoading == true) { @@ -41,204 +28,7 @@ class ApplyBody extends StatelessWidget { ); } }, - child: Form( - autovalidateMode: AutovalidateMode.onUnfocus, - key: cubit.globalKey, - child: Padding( - key: const Key(WidgetsKeys.kApplyScreenPadding), - padding: EdgeInsets.symmetric(horizontal: 16.sp), - child: SingleChildScrollView( - key: const Key(WidgetsKeys.kApplyScreenSingleChildScrollView), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - key: const Key(WidgetsKeys.kApplyScreenPadding), - local.welcome, - style: theme.textTheme.bodyLarge?.copyWith( - fontWeight: FontWeight.w500, - ), - ), - SizedBox(height: 8.h), - Text( - key: const Key(WidgetsKeys.kApplyScreenDescription), - local.youWantToBeADeliveryManJoinOurTeam, - style: theme.textTheme.bodyMedium?.copyWith( - fontWeight: FontWeight.w500, - color: AppColors.gray, - ), - ), - SizedBox(height: 32.h), - const CustomCountryTextField(), - SizedBox(height: 25.h), - TextFormField( - controller: cubit.controllerFirstName, - validator: Validations.validateText, - style: theme.textTheme.bodyMedium, - - key: const Key(WidgetsKeys.kApplyScreenFirstNamedFormField), - decoration: InputDecoration( - label: Text(local.firstLegalName), - hintText: local.EnterFirstLegalName, - ), - ), - SizedBox(height: 25.h), - TextFormField( - validator: Validations.validateText, - style: theme.textTheme.bodyMedium, - - controller: cubit.controllerSecondName, - key: const Key(WidgetsKeys.kApplyScreenSecondNamedFormField), - decoration: InputDecoration( - label: Text(local.secondLegalName), - hintText: local.EnterSecondLegalName, - ), - ), - SizedBox(height: 25.h), - const CustomVehicleTypeTextFiled(), - SizedBox(height: 25.h), - TextFormField( - controller: cubit.controllerVehicleNumber, - validator: Validations.validateText, - style: theme.textTheme.bodyMedium, - key: const Key( - WidgetsKeys.kApplyScreenVehicleNumberFormField, - ), - decoration: InputDecoration( - label: Text(local.vehicleNumber), - hintText: local.enterVehicleNumber, - ), - ), - SizedBox(height: 25.h), - BlocBuilder( - builder: (context, state) { - return TextFormField( - style: theme.textTheme.bodyMedium, - validator: Validations.validateText, - controller: cubit.controllerVehicleLicense, - key: const Key( - WidgetsKeys.kApplyScreenVehicleLicenseFormField, - ), - readOnly: true, - decoration: InputDecoration( - prefixIcon: state.vehicleImagePath != null - ? const Icon(Icons.task_alt, color: AppColors.green) - : null, - label: Text(local.vehicleLicense), - hintText: local.uploadLicensePhoto, - suffixIcon: GestureDetector( - onTap: () { - cubit.doIntent(ApplyEventPickVehicleLicensePhoto()); - }, - child: ImageIcon( - key: const Key(WidgetsKeys.kApplyScreenIconUpload), - const AssetImage(AppIcons.iconUpload), - color: theme.colorScheme.secondary, - ), - ), - ), - ); - }, - ), - SizedBox(height: 25.h), - TextFormField( - controller: cubit.controllerEmail, - style: theme.textTheme.bodyMedium, - validator: Validations.validateEmail, - key: const Key(WidgetsKeys.kApplyScreenEmailFormField), - keyboardType: TextInputType.emailAddress, - decoration: InputDecoration( - label: Text(local.email), - hintText: local.enterYourEmail, - ), - ), - SizedBox(height: 25.h), - TextFormField( - style: theme.textTheme.bodyMedium, - validator: Validations.validatePhoneNumber, - controller: cubit.controllerPhoneNumber, - key: const Key(WidgetsKeys.kApplyScreenPhoneNumberFormField), - keyboardType: TextInputType.phone, - decoration: InputDecoration( - label: Text(local.phoneNumber), - hintText: local.enterYourPhoneNumber, - ), - ), - SizedBox(height: 25.h), - TextFormField( - style: theme.textTheme.bodyMedium, - validator: Validations.validateFourteenDigitNumber, - controller: cubit.controllerIdNumber, - key: const Key(WidgetsKeys.kApplyScreenIDNumberFormField), - keyboardType: TextInputType.phone, - decoration: InputDecoration( - label: Text(local.nidNumber), - hintText: local.enterIdNumber, - ), - ), - SizedBox(height: 25.h), - BlocBuilder( - builder: (context, state) { - return TextFormField( - readOnly: true, - style: theme.textTheme.bodyMedium, - validator: Validations.validateText, - controller: cubit.controllerIdImage, - key: const Key(WidgetsKeys.kApplyScreenIDImageFormField), - decoration: InputDecoration( - prefixIcon: state.idImagePath != null - ? const Icon(Icons.task_alt, color: AppColors.green) - : null, - label: Text(local.nidImage), - hintText: local.uploadIdImage, - suffixIcon: GestureDetector( - onTap: () { - cubit.doIntent(ApplyEventPickIDImage()); - }, - child: ImageIcon( - key: const Key(WidgetsKeys.kApplyScreenIconUpload), - const AssetImage(AppIcons.iconUpload), - color: theme.colorScheme.secondary, - ), - ), - ), - ); - }, - ), - SizedBox(height: 25.h), - const CustomPasswordField(), - SizedBox(height: 25.h), - const CustomApplyRadio(), - SizedBox(height: 25.h), - SizedBox( - width: double.infinity, - child: ValueListenableBuilder( - valueListenable: cubit.isUserAuthenticated, - builder: (context, value, child) { - return ElevatedButton( - key: const Key( - WidgetsKeys.kApplyScreenElevatedButtonContinue, - ), - onPressed: value - ? () => cubit.doIntent(ApplyValidationField()) - : null, - child: Text( - local.continueText, - style: theme.textTheme.bodyMedium?.copyWith( - fontWeight: FontWeight.w500, - color: theme.colorScheme.onSecondary, - ), - ), - ); - }, - ), - ), - SizedBox(height: 22.h), - ], - ), - ), - ), - ), + child: const ApplyBodyBuilder(), ); } } diff --git a/lib/presentation/apply/view/widgets/apply_body_builder.dart b/lib/presentation/apply/view/widgets/apply_body_builder.dart new file mode 100644 index 0000000..6db8fc0 --- /dev/null +++ b/lib/presentation/apply/view/widgets/apply_body_builder.dart @@ -0,0 +1,231 @@ +import 'package:elevate_tracking_app/core/constants/app_colors.dart'; +import 'package:elevate_tracking_app/core/constants/app_icons.dart'; +import 'package:elevate_tracking_app/core/constants/widgets_keys.dart'; +import 'package:elevate_tracking_app/core/utils/validations.dart'; +import 'package:elevate_tracking_app/generated/l10n.dart'; +import 'package:elevate_tracking_app/presentation/apply/view/widgets/custom_apply_radio.dart'; +import 'package:elevate_tracking_app/presentation/apply/view/widgets/custom_country_text_field.dart'; +import 'package:elevate_tracking_app/presentation/apply/view/widgets/custom_password_field.dart'; +import 'package:elevate_tracking_app/presentation/apply/view/widgets/custom_vehicle_type_text_filed.dart'; +import 'package:elevate_tracking_app/presentation/apply/view_model/apply_event.dart'; +import 'package:elevate_tracking_app/presentation/apply/view_model/apply_view_model.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +class ApplyBodyBuilder extends StatefulWidget { + const ApplyBodyBuilder({super.key}); + + @override + State createState() => _ApplyBodyBuilderState(); +} + +class _ApplyBodyBuilderState extends State { + final GlobalKey _globalKey = GlobalKey(); + @override + Widget build(BuildContext context) { + final local = AppLocalizations.of(context); + final theme = Theme.of(context); + final cubit = BlocProvider.of(context); + return Form( + autovalidateMode: AutovalidateMode.onUnfocus, + key: _globalKey, + child: Padding( + key: const Key(WidgetsKeys.kApplyScreenPadding), + padding: EdgeInsets.symmetric(horizontal: 16.sp), + child: SingleChildScrollView( + key: const Key(WidgetsKeys.kApplyScreenSingleChildScrollView), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + key: const Key(WidgetsKeys.kApplyScreenPadding), + local.welcome, + style: theme.textTheme.bodyLarge?.copyWith( + fontWeight: FontWeight.w500, + ), + ), + SizedBox(height: 8.h), + Text( + key: const Key(WidgetsKeys.kApplyScreenDescription), + local.youWantToBeADeliveryManJoinOurTeam, + style: theme.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w500, + color: AppColors.gray, + ), + ), + SizedBox(height: 32.h), + const CustomCountryTextField(), + SizedBox(height: 25.h), + TextFormField( + controller: cubit.controllerFirstName, + validator: Validations.validateText, + style: theme.textTheme.bodyMedium, + + key: const Key(WidgetsKeys.kApplyScreenFirstNamedFormField), + decoration: InputDecoration( + label: Text(local.firstLegalName), + hintText: local.EnterFirstLegalName, + ), + ), + SizedBox(height: 25.h), + TextFormField( + validator: Validations.validateText, + style: theme.textTheme.bodyMedium, + + controller: cubit.controllerSecondName, + key: const Key(WidgetsKeys.kApplyScreenSecondNamedFormField), + decoration: InputDecoration( + label: Text(local.secondLegalName), + hintText: local.EnterSecondLegalName, + ), + ), + SizedBox(height: 25.h), + const CustomVehicleTypeTextFiled(), + SizedBox(height: 25.h), + TextFormField( + controller: cubit.controllerVehicleNumber, + validator: Validations.validateText, + style: theme.textTheme.bodyMedium, + key: const Key(WidgetsKeys.kApplyScreenVehicleNumberFormField), + decoration: InputDecoration( + label: Text(local.vehicleNumber), + hintText: local.enterVehicleNumber, + ), + ), + SizedBox(height: 25.h), + BlocBuilder( + builder: (context, state) { + return TextFormField( + style: theme.textTheme.bodyMedium, + validator: Validations.validateText, + controller: cubit.controllerVehicleLicense, + key: const Key( + WidgetsKeys.kApplyScreenVehicleLicenseFormField, + ), + readOnly: true, + decoration: InputDecoration( + prefixIcon: state.vehicleImagePath != null + ? const Icon(Icons.task_alt, color: AppColors.green) + : null, + label: Text(local.vehicleLicense), + hintText: local.uploadLicensePhoto, + suffixIcon: GestureDetector( + onTap: () { + cubit.doIntent(ApplyEventPickVehicleLicensePhoto()); + }, + child: ImageIcon( + key: const Key(WidgetsKeys.kApplyScreenIconUpload), + const AssetImage(AppIcons.iconUpload), + color: theme.colorScheme.secondary, + ), + ), + ), + ); + }, + ), + SizedBox(height: 25.h), + TextFormField( + controller: cubit.controllerEmail, + style: theme.textTheme.bodyMedium, + validator: Validations.validateEmail, + key: const Key(WidgetsKeys.kApplyScreenEmailFormField), + keyboardType: TextInputType.emailAddress, + decoration: InputDecoration( + label: Text(local.email), + hintText: local.enterYourEmail, + ), + ), + SizedBox(height: 25.h), + TextFormField( + style: theme.textTheme.bodyMedium, + validator: Validations.validatePhoneNumber, + controller: cubit.controllerPhoneNumber, + key: const Key(WidgetsKeys.kApplyScreenPhoneNumberFormField), + keyboardType: TextInputType.phone, + decoration: InputDecoration( + label: Text(local.phoneNumber), + hintText: local.enterYourPhoneNumber, + ), + ), + SizedBox(height: 25.h), + TextFormField( + style: theme.textTheme.bodyMedium, + validator: Validations.validateFourteenDigitNumber, + controller: cubit.controllerIdNumber, + key: const Key(WidgetsKeys.kApplyScreenIDNumberFormField), + keyboardType: TextInputType.phone, + decoration: InputDecoration( + label: Text(local.nidNumber), + hintText: local.enterIdNumber, + ), + ), + SizedBox(height: 25.h), + BlocBuilder( + builder: (context, state) { + return TextFormField( + readOnly: true, + style: theme.textTheme.bodyMedium, + validator: Validations.validateText, + controller: cubit.controllerIdImage, + key: const Key(WidgetsKeys.kApplyScreenIDImageFormField), + decoration: InputDecoration( + prefixIcon: state.idImagePath != null + ? const Icon(Icons.task_alt, color: AppColors.green) + : null, + label: Text(local.nidImage), + hintText: local.uploadIdImage, + suffixIcon: GestureDetector( + onTap: () { + cubit.doIntent(ApplyEventPickIDImage()); + }, + child: ImageIcon( + key: const Key(WidgetsKeys.kApplyScreenIconUpload), + const AssetImage(AppIcons.iconUpload), + color: theme.colorScheme.secondary, + ), + ), + ), + ); + }, + ), + SizedBox(height: 25.h), + const CustomPasswordField(), + SizedBox(height: 25.h), + const CustomApplyRadio(), + SizedBox(height: 25.h), + SizedBox( + width: double.infinity, + child: ValueListenableBuilder( + valueListenable: cubit.isUserAuthenticated, + builder: (context, value, child) { + return ElevatedButton( + key: const Key( + WidgetsKeys.kApplyScreenElevatedButtonContinue, + ), + onPressed: value + ? () { + if (_globalKey.currentState!.validate()) { + cubit.doIntent(ApplySendDataEvent()); + } + } + : null, + child: Text( + local.continueText, + style: theme.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w500, + color: theme.colorScheme.onSecondary, + ), + ), + ); + }, + ), + ), + SizedBox(height: 22.h), + ], + ), + ), + ), + ); + } +} diff --git a/lib/presentation/apply/view_model/apply_event.dart b/lib/presentation/apply/view_model/apply_event.dart index 664abb1..f572edd 100644 --- a/lib/presentation/apply/view_model/apply_event.dart +++ b/lib/presentation/apply/view_model/apply_event.dart @@ -6,6 +6,6 @@ class ApplyEventPickIDImage extends ApplyEvent {} class ApplyEventGetAllData extends ApplyEvent {} -class ApplyValidationField extends ApplyEvent {} +class ApplySendDataEvent extends ApplyEvent {} class ApplyPasswordVisibilityEvent extends ApplyEvent {} diff --git a/lib/presentation/apply/view_model/apply_view_model.dart b/lib/presentation/apply/view_model/apply_view_model.dart index f64c903..ac60cf9 100644 --- a/lib/presentation/apply/view_model/apply_view_model.dart +++ b/lib/presentation/apply/view_model/apply_view_model.dart @@ -54,11 +54,11 @@ class ApplyViewModel extends Cubit { TextEditingController controllerIdImage = TextEditingController(); TextEditingController controllerPassword = TextEditingController(); TextEditingController controllerConfirmPassword = TextEditingController(); + ValueNotifier gender = ValueNotifier(null); final ValueNotifier isUserAuthenticated = ValueNotifier(false); String? vehicleImagePath; String? idImagePath; - GlobalKey globalKey = GlobalKey(); ValueNotifier selectedVehicle = ValueNotifier( null, ); @@ -74,8 +74,8 @@ class ApplyViewModel extends Cubit { _getIdImage(); case ApplyEventGetAllData(): _getAllData(); - case ApplyValidationField(): - _validateFiled(); + case ApplySendDataEvent(): + _apply(); case ApplyPasswordVisibilityEvent(): _passwordVisibility(); } @@ -85,42 +85,38 @@ class ApplyViewModel extends Cubit { isPasswordVisible.value = !isPasswordVisible.value; } - Future _validateFiled() async { - if (globalKey.currentState!.validate()) { - emit(state.copyWith(applyFun: BaseState.loading())); - final result = await _applyUseCase.call( - ApplyRequestEntity( - country: controllerCountry.text, - firstName: controllerFirstName.text, - lastName: controllerSecondName.text, - vehicleType: selectedVehicle.value?.id ?? "", - vehicleNumber: controllerVehicleNumber.text, - nid: controllerIdNumber.text, - email: controllerEmail.text, - password: controllerPassword.text, - rePassword: controllerConfirmPassword.text, - gender: gender.value ?? ConstKeys.kMale, - phone: controllerPhoneNumber.text, - nidImg: File(idImagePath!), - vehicleLicense: File(vehicleImagePath!), - ), - ); - switch (result) { - case ApiSuccessResult(): - emit( - state.copyWith( - applyFun: BaseState.success(result.data), - ), - ); - case ApiErrorResult(): - emit( - state.copyWith( - applyFun: BaseState.error( - result.errorMessage, - ), - ), - ); - } + Future _apply() async { + emit(state.copyWith(applyFun: BaseState.loading())); + final result = await _applyUseCase.call( + ApplyRequestEntity( + country: controllerCountry.text, + firstName: controllerFirstName.text, + lastName: controllerSecondName.text, + vehicleType: selectedVehicle.value?.id ?? "", + vehicleNumber: controllerVehicleNumber.text, + nid: controllerIdNumber.text, + email: controllerEmail.text, + password: controllerPassword.text, + rePassword: controllerConfirmPassword.text, + gender: gender.value ?? ConstKeys.kMale, + phone: controllerPhoneNumber.text, + nidImg: File(idImagePath ?? ''), + vehicleLicense: File(vehicleImagePath ?? ''), + ), + ); + switch (result) { + case ApiSuccessResult(): + emit( + state.copyWith( + applyFun: BaseState.success(result.data), + ), + ); + case ApiErrorResult(): + emit( + state.copyWith( + applyFun: BaseState.error(result.errorMessage), + ), + ); } } diff --git a/test/presentation/apply/view_model/apply_view_model_state_test.dart b/test/presentation/apply/view_model/apply_view_model_state_test.dart new file mode 100644 index 0000000..35b4ea8 --- /dev/null +++ b/test/presentation/apply/view_model/apply_view_model_state_test.dart @@ -0,0 +1,84 @@ +import 'package:bloc_test/bloc_test.dart'; +import 'package:dio/dio.dart'; +import 'package:elevate_tracking_app/core/api_result/api_result.dart'; +import 'package:elevate_tracking_app/core/base_state/base_state.dart'; +import 'package:elevate_tracking_app/domain/entites/apply_response_entity.dart'; +import 'package:elevate_tracking_app/domain/entites/request/apply_request_entity.dart'; +import 'package:elevate_tracking_app/domain/use_cases/apply_use_case.dart'; +import 'package:elevate_tracking_app/domain/use_cases/get_all_country_use_case.dart'; +import 'package:elevate_tracking_app/domain/use_cases/get_all_vehicles_use_case.dart'; +import 'package:elevate_tracking_app/presentation/apply/view_model/apply_event.dart'; +import 'package:elevate_tracking_app/presentation/apply/view_model/apply_view_model.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; + +import '../../../fixture/apply_fixture.dart'; +import 'apply_view_model_state_test.mocks.dart'; + +@GenerateMocks([GetAllCountryUseCase, ApplyUseCase, GetAllVehiclesUseCase]) +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + late MockGetAllCountryUseCase mockGetAllCountryUseCase; + late MockGetAllVehiclesUseCase mockGetAllVehiclesUseCase; + late MockApplyUseCase mockApplyUseCase; + late ApplyViewModelState state; + late ApplyViewModel viewModel; + final ApplyResponseEntity responseEntity = ApplyFixture.fakeResponseEntity(); + + final DioException dioException = DioException( + requestOptions: RequestOptions(), + message: "An unexpected error occurred: DioError", + ); + + setUp(() { + mockApplyUseCase = MockApplyUseCase(); + mockGetAllVehiclesUseCase = MockGetAllVehiclesUseCase(); + mockGetAllCountryUseCase = MockGetAllCountryUseCase(); + viewModel = ApplyViewModel( + mockGetAllVehiclesUseCase, + mockGetAllCountryUseCase, + mockApplyUseCase, + ); + provideDummy>( + ApiSuccessResult(responseEntity), + ); + provideDummy>(ApiErrorResult(dioException)); + state = const ApplyViewModelState(); + }); + + group("Test Apply Function", () { + blocTest( + "test apply function emit success", + build: () { + when( + mockApplyUseCase.call(any), + ).thenAnswer((_) async => ApiSuccessResult(responseEntity)); + return viewModel; + }, + act: (cubit) => cubit.doIntent(ApplySendDataEvent()), + expect: () => [ + state.copyWith(applyFun: BaseState.loading()), + state.copyWith(applyFun: BaseState.success(responseEntity)), + ], + ); + blocTest( + "test apply function emit Failure", + build: () { + when( + mockApplyUseCase.call(any), + ).thenAnswer((_) async => ApiErrorResult(dioException)); + return viewModel; + }, + act: (cubit) => cubit.doIntent(ApplySendDataEvent()), + expect: () => [ + state.copyWith(applyFun: BaseState.loading()), + isA().having( + (s) => s.applyFun?.errorMessage, + "error", + contains(dioException.message), + ), + ], + ); + }); +} From a91738be0aff70951d43418f19437b076a64a12b Mon Sep 17 00:00:00 2001 From: Moaz Osama Date: Sun, 28 Sep 2025 09:02:49 +0300 Subject: [PATCH 4/6] feat: finish unit test view model --- .../apply/view_model/apply_view_model.dart | 3 +- test/api/client/api_client_test.dart | 2 +- .../auth_local_data_source_impl_test.dart | 4 +- test/api/mapper/apply_mapper_test.dart | 2 +- .../requests/apply_request_dto_test.dart | 2 +- .../auth_remote_data_source_test.dart | 2 +- test/data/repo/auth_repo_impl_test.dart | 18 +-- .../domain/use_cases/apply_use_case_test.dart | 2 +- .../get_all_country_use_case_test.dart | 2 +- .../use_cases/get_all_vehicles_test.dart | 2 +- test/{fixture => dummy}/apply_fixture.dart | 0 test/{fixture => dummy}/fake_file_json.dart | 0 test/{fixture => dummy}/fake_image_file.dart | 0 .../apply_view_model_state_test.dart | 106 ++++++++++++++++-- 14 files changed, 119 insertions(+), 26 deletions(-) rename test/{fixture => dummy}/apply_fixture.dart (100%) rename test/{fixture => dummy}/fake_file_json.dart (100%) rename test/{fixture => dummy}/fake_image_file.dart (100%) diff --git a/lib/presentation/apply/view_model/apply_view_model.dart b/lib/presentation/apply/view_model/apply_view_model.dart index ac60cf9..e6e91cc 100644 --- a/lib/presentation/apply/view_model/apply_view_model.dart +++ b/lib/presentation/apply/view_model/apply_view_model.dart @@ -140,7 +140,8 @@ class ApplyViewModel extends Cubit { } Future _getAllData() async { - await Future.wait([_getAllCountry(), _getAllVehicles()]); + await _getAllCountry(); + await _getAllVehicles(); } Future _getVehicleLicense() async { diff --git a/test/api/client/api_client_test.dart b/test/api/client/api_client_test.dart index a2ff08c..ab6b354 100644 --- a/test/api/client/api_client_test.dart +++ b/test/api/client/api_client_test.dart @@ -7,7 +7,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:http_mock_adapter/http_mock_adapter.dart' show DioAdapter, Matchers; -import '../../fixture/apply_fixture.dart'; +import '../../dummy/apply_fixture.dart'; void main() { group("Group ApplyResponseDto", () { diff --git a/test/api/data_source/auth_local_data_source_impl_test.dart b/test/api/data_source/auth_local_data_source_impl_test.dart index b664585..0cabf22 100644 --- a/test/api/data_source/auth_local_data_source_impl_test.dart +++ b/test/api/data_source/auth_local_data_source_impl_test.dart @@ -5,8 +5,8 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; -import '../../fixture/apply_fixture.dart'; -import '../../fixture/fake_file_json.dart'; +import '../../dummy/apply_fixture.dart'; +import '../../dummy/fake_file_json.dart'; @GenerateMocks([AssetBundle]) void main() { diff --git a/test/api/mapper/apply_mapper_test.dart b/test/api/mapper/apply_mapper_test.dart index 0db4645..ece372b 100644 --- a/test/api/mapper/apply_mapper_test.dart +++ b/test/api/mapper/apply_mapper_test.dart @@ -6,7 +6,7 @@ import 'package:elevate_tracking_app/domain/entites/country_entity.dart'; import 'package:elevate_tracking_app/domain/entites/request/apply_request_entity.dart'; import 'package:flutter_test/flutter_test.dart'; -import '../../fixture/apply_fixture.dart'; +import '../../dummy/apply_fixture.dart'; void main() { group("Apply Mapper", () { diff --git a/test/api/models/requests/apply_request_dto_test.dart b/test/api/models/requests/apply_request_dto_test.dart index c904e34..768fbc7 100644 --- a/test/api/models/requests/apply_request_dto_test.dart +++ b/test/api/models/requests/apply_request_dto_test.dart @@ -1,5 +1,5 @@ import 'package:flutter_test/flutter_test.dart'; -import '../../../fixture/apply_fixture.dart'; +import '../../../dummy/apply_fixture.dart'; void main() { group('ApplyRequestDto.toFormData', () { diff --git a/test/data/data_source/auth_remote_data_source_test.dart b/test/data/data_source/auth_remote_data_source_test.dart index fc826a3..365dfb9 100644 --- a/test/data/data_source/auth_remote_data_source_test.dart +++ b/test/data/data_source/auth_remote_data_source_test.dart @@ -10,7 +10,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; -import '../../fixture/apply_fixture.dart'; +import '../../dummy/apply_fixture.dart'; import 'auth_remote_data_source_test.mocks.dart'; @GenerateMocks([ApiClient]) diff --git a/test/data/repo/auth_repo_impl_test.dart b/test/data/repo/auth_repo_impl_test.dart index 6d5b7d6..562c8cc 100644 --- a/test/data/repo/auth_repo_impl_test.dart +++ b/test/data/repo/auth_repo_impl_test.dart @@ -1,7 +1,7 @@ import 'package:dio/dio.dart'; -import 'package:elevate_tracking_app/api/data_source/auth_local_data_source_impl.dart'; -import 'package:elevate_tracking_app/api/data_source/auth_remote_data_source_impl.dart'; import 'package:elevate_tracking_app/core/api_result/api_result.dart'; +import 'package:elevate_tracking_app/data/data_source/auth_local_data_source.dart'; +import 'package:elevate_tracking_app/data/data_source/auth_remote_data_source.dart'; import 'package:elevate_tracking_app/data/repo/auth_repo_impl.dart'; import 'package:elevate_tracking_app/domain/entites/apply_response_entity.dart'; import 'package:elevate_tracking_app/domain/entites/country_entity.dart'; @@ -10,10 +10,10 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; -import '../../fixture/apply_fixture.dart'; +import '../../dummy/apply_fixture.dart'; import 'auth_repo_impl_test.mocks.dart'; -@GenerateMocks([AuthRemoteDataSourceImpl, AuthLocalDataSourceImpl]) +@GenerateMocks([AuthRemoteDataSource, AuthLocalDataSource]) void main() { group("Test Apply", () { final fakeApplyRequestEntity = ApplyFixture.fakeRequestEntity(); @@ -21,17 +21,17 @@ void main() { final fakeAllCountry = ApplyFixture.fakeCountryEntityList(); final List fakeListVehicles = ApplyFixture.fakeVehicleEntity(); - late MockAuthRemoteDataSourceImpl mockAuthRemoteDataSourceImpl; - late MockAuthLocalDataSourceImpl mockAuthLocalDataSourceImpl; + late MockAuthRemoteDataSource mockAuthRemoteDataSourceImpl; + late MockAuthLocalDataSource mockAuthLocalDataSourceImpl; late AuthRepoImpl authRepoImpl; final DioException dioException = DioException( requestOptions: RequestOptions(), message: "fake_dio_message", ); final Exception fakeException = Exception(); - setUp(() { - mockAuthRemoteDataSourceImpl = MockAuthRemoteDataSourceImpl(); - mockAuthLocalDataSourceImpl = MockAuthLocalDataSourceImpl(); + setUpAll(() { + mockAuthRemoteDataSourceImpl = MockAuthRemoteDataSource(); + mockAuthLocalDataSourceImpl = MockAuthLocalDataSource(); authRepoImpl = AuthRepoImpl( mockAuthRemoteDataSourceImpl, mockAuthLocalDataSourceImpl, diff --git a/test/domain/use_cases/apply_use_case_test.dart b/test/domain/use_cases/apply_use_case_test.dart index 4cd62f3..913524a 100644 --- a/test/domain/use_cases/apply_use_case_test.dart +++ b/test/domain/use_cases/apply_use_case_test.dart @@ -7,7 +7,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; -import '../../fixture/apply_fixture.dart'; +import '../../dummy/apply_fixture.dart'; import 'apply_use_case_test.mocks.dart'; @GenerateMocks([AuthRepoImpl]) diff --git a/test/domain/use_cases/get_all_country_use_case_test.dart b/test/domain/use_cases/get_all_country_use_case_test.dart index a823ddc..1523c95 100644 --- a/test/domain/use_cases/get_all_country_use_case_test.dart +++ b/test/domain/use_cases/get_all_country_use_case_test.dart @@ -6,7 +6,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; -import '../../fixture/apply_fixture.dart'; +import '../../dummy/apply_fixture.dart'; import 'get_all_country_use_case_test.mocks.dart'; @GenerateMocks([AuthRepoImpl]) diff --git a/test/domain/use_cases/get_all_vehicles_test.dart b/test/domain/use_cases/get_all_vehicles_test.dart index 1d38a58..d7b7c32 100644 --- a/test/domain/use_cases/get_all_vehicles_test.dart +++ b/test/domain/use_cases/get_all_vehicles_test.dart @@ -6,7 +6,7 @@ import 'package:elevate_tracking_app/domain/use_cases/get_all_vehicles_use_case. import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; -import '../../fixture/apply_fixture.dart'; +import '../../dummy/apply_fixture.dart'; import 'apply_use_case_test.mocks.dart'; @GenerateMocks([AuthRepoImpl]) diff --git a/test/fixture/apply_fixture.dart b/test/dummy/apply_fixture.dart similarity index 100% rename from test/fixture/apply_fixture.dart rename to test/dummy/apply_fixture.dart diff --git a/test/fixture/fake_file_json.dart b/test/dummy/fake_file_json.dart similarity index 100% rename from test/fixture/fake_file_json.dart rename to test/dummy/fake_file_json.dart diff --git a/test/fixture/fake_image_file.dart b/test/dummy/fake_image_file.dart similarity index 100% rename from test/fixture/fake_image_file.dart rename to test/dummy/fake_image_file.dart diff --git a/test/presentation/apply/view_model/apply_view_model_state_test.dart b/test/presentation/apply/view_model/apply_view_model_state_test.dart index 35b4ea8..d218431 100644 --- a/test/presentation/apply/view_model/apply_view_model_state_test.dart +++ b/test/presentation/apply/view_model/apply_view_model_state_test.dart @@ -3,20 +3,29 @@ import 'package:dio/dio.dart'; import 'package:elevate_tracking_app/core/api_result/api_result.dart'; import 'package:elevate_tracking_app/core/base_state/base_state.dart'; import 'package:elevate_tracking_app/domain/entites/apply_response_entity.dart'; -import 'package:elevate_tracking_app/domain/entites/request/apply_request_entity.dart'; +import 'package:elevate_tracking_app/domain/entites/country_entity.dart'; +import 'package:elevate_tracking_app/domain/entites/vehicles_entity.dart'; import 'package:elevate_tracking_app/domain/use_cases/apply_use_case.dart'; import 'package:elevate_tracking_app/domain/use_cases/get_all_country_use_case.dart'; import 'package:elevate_tracking_app/domain/use_cases/get_all_vehicles_use_case.dart'; import 'package:elevate_tracking_app/presentation/apply/view_model/apply_event.dart'; import 'package:elevate_tracking_app/presentation/apply/view_model/apply_view_model.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:image_picker/image_picker.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; -import '../../../fixture/apply_fixture.dart'; +import '../../../dummy/apply_fixture.dart'; +import '../../../dummy/fake_image_file.dart'; import 'apply_view_model_state_test.mocks.dart'; -@GenerateMocks([GetAllCountryUseCase, ApplyUseCase, GetAllVehiclesUseCase]) +@GenerateMocks([ + GetAllCountryUseCase, + ApplyUseCase, + GetAllVehiclesUseCase, + ImagePicker, + XFile, +]) void main() { TestWidgetsFlutterBinding.ensureInitialized(); late MockGetAllCountryUseCase mockGetAllCountryUseCase; @@ -24,7 +33,13 @@ void main() { late MockApplyUseCase mockApplyUseCase; late ApplyViewModelState state; late ApplyViewModel viewModel; - final ApplyResponseEntity responseEntity = ApplyFixture.fakeResponseEntity(); + late MockImagePicker mockPicker; + final ApplyResponseEntity applyResponseEntity = + ApplyFixture.fakeResponseEntity(); + final List vehicleResponseEntity = + ApplyFixture.fakeVehicleEntity(); + final List countryResponseEntity = + ApplyFixture.fakeCountryEntityList(); final DioException dioException = DioException( requestOptions: RequestOptions(), @@ -32,6 +47,7 @@ void main() { ); setUp(() { + mockPicker = MockImagePicker(); mockApplyUseCase = MockApplyUseCase(); mockGetAllVehiclesUseCase = MockGetAllVehiclesUseCase(); mockGetAllCountryUseCase = MockGetAllCountryUseCase(); @@ -41,10 +57,18 @@ void main() { mockApplyUseCase, ); provideDummy>( - ApiSuccessResult(responseEntity), + ApiSuccessResult(applyResponseEntity), ); provideDummy>(ApiErrorResult(dioException)); state = const ApplyViewModelState(); + provideDummy>>( + ApiSuccessResult(vehicleResponseEntity), + ); + provideDummy>>(ApiErrorResult(dioException)); + provideDummy>>( + ApiSuccessResult(countryResponseEntity), + ); + provideDummy>>(ApiErrorResult(dioException)); }); group("Test Apply Function", () { @@ -53,13 +77,13 @@ void main() { build: () { when( mockApplyUseCase.call(any), - ).thenAnswer((_) async => ApiSuccessResult(responseEntity)); + ).thenAnswer((_) async => ApiSuccessResult(applyResponseEntity)); return viewModel; }, act: (cubit) => cubit.doIntent(ApplySendDataEvent()), expect: () => [ state.copyWith(applyFun: BaseState.loading()), - state.copyWith(applyFun: BaseState.success(responseEntity)), + state.copyWith(applyFun: BaseState.success(applyResponseEntity)), ], ); blocTest( @@ -81,4 +105,72 @@ void main() { ], ); }); + group("Test get all vehicle Function", () { + blocTest( + "test get all vehicle function emit success", + build: () { + when( + mockGetAllVehiclesUseCase.call(), + ).thenAnswer((_) async => ApiSuccessResult(vehicleResponseEntity)); + when( + mockGetAllCountryUseCase.call(), + ).thenAnswer((_) async => ApiSuccessResult(countryResponseEntity)); + return viewModel; + }, + act: (cubit) => cubit.doIntent(ApplyEventGetAllData()), + expect: () => [ + state.copyWith(allCountry: BaseState.loading()), + state.copyWith(allCountry: BaseState.success(countryResponseEntity)), + state.copyWith( + allVehicleList: BaseState.loading(), + allCountry: BaseState.success(countryResponseEntity), + ), + state.copyWith( + allVehicleList: BaseState.success(vehicleResponseEntity), + allCountry: BaseState.success(countryResponseEntity), + ), + ], + ); + + blocTest( + "test get all vehicle function emit Failure", + build: () { + when( + mockGetAllCountryUseCase.call(), + ).thenAnswer((_) async => ApiErrorResult(dioException)); + when( + mockGetAllVehiclesUseCase.call(), + ).thenAnswer((_) async => ApiErrorResult(dioException)); + return viewModel; + }, + act: (cubit) => cubit.doIntent(ApplyEventGetAllData()), + expect: () => [ + state.copyWith(allCountry: BaseState.loading()), + + anyOf([ + isA().having( + (s) => s.allCountry?.errorMessage, + "error", + contains(dioException.message), + ), + state.copyWith(allVehicleList: BaseState.loading()), + ]), + + anyOf([ + isA().having( + (s) => s.allCountry?.errorMessage, + "error", + contains(dioException.message), + ), + state.copyWith(allVehicleList: BaseState.loading()), + ]), + + isA().having( + (s) => s.allVehicleList?.errorMessage, + "error", + contains(dioException.message), + ), + ], + ); + }); } From 58dafd7e5c293f080e90e2b8517cee98abf7e860 Mon Sep 17 00:00:00 2001 From: Moaz Osama Date: Sun, 28 Sep 2025 09:04:40 +0300 Subject: [PATCH 5/6] fix: un used variable --- .../view_model/apply_view_model_state_test.dart | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/test/presentation/apply/view_model/apply_view_model_state_test.dart b/test/presentation/apply/view_model/apply_view_model_state_test.dart index d218431..59687ca 100644 --- a/test/presentation/apply/view_model/apply_view_model_state_test.dart +++ b/test/presentation/apply/view_model/apply_view_model_state_test.dart @@ -11,21 +11,13 @@ import 'package:elevate_tracking_app/domain/use_cases/get_all_vehicles_use_case. import 'package:elevate_tracking_app/presentation/apply/view_model/apply_event.dart'; import 'package:elevate_tracking_app/presentation/apply/view_model/apply_view_model.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:image_picker/image_picker.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import '../../../dummy/apply_fixture.dart'; -import '../../../dummy/fake_image_file.dart'; import 'apply_view_model_state_test.mocks.dart'; -@GenerateMocks([ - GetAllCountryUseCase, - ApplyUseCase, - GetAllVehiclesUseCase, - ImagePicker, - XFile, -]) +@GenerateMocks([GetAllCountryUseCase, ApplyUseCase, GetAllVehiclesUseCase]) void main() { TestWidgetsFlutterBinding.ensureInitialized(); late MockGetAllCountryUseCase mockGetAllCountryUseCase; @@ -33,7 +25,6 @@ void main() { late MockApplyUseCase mockApplyUseCase; late ApplyViewModelState state; late ApplyViewModel viewModel; - late MockImagePicker mockPicker; final ApplyResponseEntity applyResponseEntity = ApplyFixture.fakeResponseEntity(); final List vehicleResponseEntity = @@ -47,7 +38,6 @@ void main() { ); setUp(() { - mockPicker = MockImagePicker(); mockApplyUseCase = MockApplyUseCase(); mockGetAllVehiclesUseCase = MockGetAllVehiclesUseCase(); mockGetAllCountryUseCase = MockGetAllCountryUseCase(); From 8b614ed0d319aef7553d07a1bf935d4262a1eea3 Mon Sep 17 00:00:00 2001 From: Moaz Osama Date: Mon, 29 Sep 2025 19:59:20 +0300 Subject: [PATCH 6/6] feat: init widget test --- lib/core/constants/widgets_keys.dart | 4 +- .../view/widgets/apply_body_builder.dart | 3 +- .../apply/view/screen/apply_view_test.dart | 69 +++++++++++++++++++ 3 files changed, 72 insertions(+), 4 deletions(-) create mode 100644 test/presentation/apply/view/screen/apply_view_test.dart diff --git a/lib/core/constants/widgets_keys.dart b/lib/core/constants/widgets_keys.dart index 67a4725..af94489 100644 --- a/lib/core/constants/widgets_keys.dart +++ b/lib/core/constants/widgets_keys.dart @@ -8,13 +8,13 @@ abstract final class WidgetsKeys { "k_apply_screen_icon_arrow_back"; static const String kApplyScreenWelcome = "k_apply_screen_welcome"; static const String kApplyScreenDescription = "k_apply_screen_description"; - static const String kApplyScreenCountryFormField = - "k_apply_screen_country_form_field"; static const String kApplyScreenPadding = "k_apply_screen_padding"; static const String kApplyScreenSingleChildScrollView = "k_apply_screen_single_child_scroll_view"; static const String kApplyScreenArrowDown = "k_apply_screen_arrow_down"; static const String kApplyScreenIconUpload = "k_apply_screen_icon_upload"; + static const String kApplyScreenCountryFormField = + "k_apply_screen_country_form_field"; static const String kApplyScreenFirstNamedFormField = "k_apply_screen_first_name_form_filed"; static const String kApplyScreenSecondNamedFormField = diff --git a/lib/presentation/apply/view/widgets/apply_body_builder.dart b/lib/presentation/apply/view/widgets/apply_body_builder.dart index 6db8fc0..f1dd12d 100644 --- a/lib/presentation/apply/view/widgets/apply_body_builder.dart +++ b/lib/presentation/apply/view/widgets/apply_body_builder.dart @@ -39,7 +39,7 @@ class _ApplyBodyBuilderState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - key: const Key(WidgetsKeys.kApplyScreenPadding), + key: const Key(WidgetsKeys.kApplyScreenWelcome), local.welcome, style: theme.textTheme.bodyLarge?.copyWith( fontWeight: FontWeight.w500, @@ -61,7 +61,6 @@ class _ApplyBodyBuilderState extends State { controller: cubit.controllerFirstName, validator: Validations.validateText, style: theme.textTheme.bodyMedium, - key: const Key(WidgetsKeys.kApplyScreenFirstNamedFormField), decoration: InputDecoration( label: Text(local.firstLegalName), diff --git a/test/presentation/apply/view/screen/apply_view_test.dart b/test/presentation/apply/view/screen/apply_view_test.dart new file mode 100644 index 0000000..4ed4b04 --- /dev/null +++ b/test/presentation/apply/view/screen/apply_view_test.dart @@ -0,0 +1,69 @@ +import 'package:elevate_tracking_app/core/api_result/api_result.dart'; +import 'package:elevate_tracking_app/core/constants/widgets_keys.dart'; +import 'package:elevate_tracking_app/core/custom_widget/test_app_wrapper.dart'; +import 'package:elevate_tracking_app/core/di/di.dart'; +import 'package:elevate_tracking_app/domain/entites/country_entity.dart'; +import 'package:elevate_tracking_app/domain/entites/vehicles_entity.dart'; +import 'package:elevate_tracking_app/domain/use_cases/get_all_vehicles_use_case.dart'; +import 'package:elevate_tracking_app/presentation/apply/view/screen/apply_view.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; + +import '../../../../dummy/apply_fixture.dart'; +import 'apply_view_test.mocks.dart'; + +@GenerateMocks([GetAllVehiclesUseCase]) +void main() { + late MockGetAllVehiclesUseCase mockGetAllVehiclesUseCase; + ApplyFixture.fakeResponseEntity(); + final List vehicleResponseEntity = + ApplyFixture.fakeVehicleEntity(); + final List countryResponseEntity = + ApplyFixture.fakeCountryEntityList(); + ApplyFixture.fakeRequestEntity(); + setUp(() { + configureDependencies(); + mockGetAllVehiclesUseCase = MockGetAllVehiclesUseCase(); + provideDummy>>( + ApiSuccessResult(vehicleResponseEntity), + ); + provideDummy>>( + ApiSuccessResult(countryResponseEntity), + ); + }); + testWidgets('apply test structure', (WidgetTester tester) async { + await tester.pumpWidget(const TestAppWrapper(child: ApplyView())); + when( + mockGetAllVehiclesUseCase.call(), + ).thenAnswer((_) async => ApiSuccessResult(vehicleResponseEntity)); + await tester.pumpAndSettle(); + expect(find.byType(AppBar), findsOneWidget); + expect( + find.byKey(const Key(WidgetsKeys.kApplyScreenLabel)), + findsOneWidget, + ); + expect( + find.byKey(const Key(WidgetsKeys.kApplyScreenIconArrowBack)), + findsOneWidget, + ); + expect( + find.byKey(const Key(WidgetsKeys.kApplyScreenPadding)), + findsOneWidget, + ); + expect(find.byType(TextFormField), findsNWidgets(12)); + expect( + find.byKey(const Key(WidgetsKeys.kApplyScreenElevatedButtonContinue)), + findsOneWidget, + ); + expect( + find.byKey(const Key(WidgetsKeys.kApplyScreenTextGender)), + findsOneWidget, + ); + expect( + find.byKey(const Key(WidgetsKeys.kApplyScreenRadioGroup)), + findsOneWidget, + ); + }); +}