feat: update profile setup and bank account management; enhance API integration and data handling

This commit is contained in:
Achintha Isuru
2026-03-17 14:32:26 -04:00
parent b6a655a261
commit de388c9a77
21 changed files with 142 additions and 85 deletions

View File

@@ -61,7 +61,7 @@ class StaffSession extends Equatable {
vendorId: staff['vendorId'] as String?, vendorId: staff['vendorId'] as String?,
workforceNumber: staff['workforceNumber'] as String?, workforceNumber: staff['workforceNumber'] as String?,
metadata: (staff['metadata'] as Map<String, dynamic>?) ?? const <String, dynamic>{}, metadata: (staff['metadata'] as Map<String, dynamic>?) ?? const <String, dynamic>{},
userId: user['userId'] as String?, userId: (user['userId'] ?? user['id']) as String?,
tenantName: tenant['tenantName'] as String?, tenantName: tenant['tenantName'] as String?,
tenantSlug: tenant['tenantSlug'] as String?, tenantSlug: tenant['tenantSlug'] as String?,
); );

View File

@@ -1,3 +1,4 @@
import 'package:firebase_auth/firebase_auth.dart';
import 'package:krow_core/core.dart'; import 'package:krow_core/core.dart';
import 'package:krow_domain/krow_domain.dart'; import 'package:krow_domain/krow_domain.dart';
@@ -20,18 +21,32 @@ class ProfileSetupRepositoryImpl implements ProfileSetupRepository {
@override @override
Future<void> submitProfile({ Future<void> submitProfile({
required String fullName, required String fullName,
required String phoneNumber,
String? bio, String? bio,
required List<String> preferredLocations, required List<String> preferredLocations,
required double maxDistanceMiles, required double maxDistanceMiles,
required List<String> industries, required List<String> industries,
required List<String> skills, required List<String> skills,
}) async { }) async {
// Convert location label strings to the object shape the V2 API expects.
// The backend zod schema requires: { label, city?, state?, ... }.
final List<Map<String, String>> locationObjects = preferredLocations
.map((String label) => <String, String>{'label': label})
.toList();
// Resolve the phone number: prefer the explicit parameter, but fall back
// to the Firebase Auth current user's phone if the caller passed empty.
final String resolvedPhone = phoneNumber.isNotEmpty
? phoneNumber
: (FirebaseAuth.instance.currentUser?.phoneNumber ?? '');
final ApiResponse response = await _apiService.post( final ApiResponse response = await _apiService.post(
StaffEndpoints.profileSetup, StaffEndpoints.profileSetup,
data: <String, dynamic>{ data: <String, dynamic>{
'fullName': fullName, 'fullName': fullName,
'phoneNumber': resolvedPhone,
if (bio != null && bio.isNotEmpty) 'bio': bio, if (bio != null && bio.isNotEmpty) 'bio': bio,
'preferredLocations': preferredLocations, 'preferredLocations': locationObjects,
'maxDistanceMiles': maxDistanceMiles.toInt(), 'maxDistanceMiles': maxDistanceMiles.toInt(),
'industries': industries, 'industries': industries,
'skills': skills, 'skills': skills,

View File

@@ -1,7 +1,9 @@
/// Interface for the staff profile setup repository.
abstract class ProfileSetupRepository { abstract class ProfileSetupRepository {
/// Submits the staff profile setup data to the backend.
Future<void> submitProfile({ Future<void> submitProfile({
required String fullName, required String fullName,
required String phoneNumber,
String? bio, String? bio,
required List<String> preferredLocations, required List<String> preferredLocations,
required double maxDistanceMiles, required double maxDistanceMiles,

View File

@@ -13,6 +13,7 @@ class SubmitProfileSetup {
/// Submits the profile setup with the given data. /// Submits the profile setup with the given data.
Future<void> call({ Future<void> call({
required String fullName, required String fullName,
required String phoneNumber,
String? bio, String? bio,
required List<String> preferredLocations, required List<String> preferredLocations,
required double maxDistanceMiles, required double maxDistanceMiles,
@@ -21,6 +22,7 @@ class SubmitProfileSetup {
}) { }) {
return repository.submitProfile( return repository.submitProfile(
fullName: fullName, fullName: fullName,
phoneNumber: phoneNumber,
bio: bio, bio: bio,
preferredLocations: preferredLocations, preferredLocations: preferredLocations,
maxDistanceMiles: maxDistanceMiles, maxDistanceMiles: maxDistanceMiles,

View File

@@ -12,11 +12,16 @@ export 'package:staff_authentication/src/presentation/blocs/profile_setup/profil
class ProfileSetupBloc extends Bloc<ProfileSetupEvent, ProfileSetupState> class ProfileSetupBloc extends Bloc<ProfileSetupEvent, ProfileSetupState>
with BlocErrorHandler<ProfileSetupState> { with BlocErrorHandler<ProfileSetupState> {
/// Creates a [ProfileSetupBloc]. /// Creates a [ProfileSetupBloc].
///
/// [phoneNumber] is the authenticated user's phone from the sign-up flow,
/// required by the V2 profile-setup endpoint.
ProfileSetupBloc({ ProfileSetupBloc({
required SubmitProfileSetup submitProfileSetup, required SubmitProfileSetup submitProfileSetup,
required SearchCitiesUseCase searchCities, required SearchCitiesUseCase searchCities,
required String phoneNumber,
}) : _submitProfileSetup = submitProfileSetup, }) : _submitProfileSetup = submitProfileSetup,
_searchCities = searchCities, _searchCities = searchCities,
_phoneNumber = phoneNumber,
super(const ProfileSetupState()) { super(const ProfileSetupState()) {
on<ProfileSetupFullNameChanged>(_onFullNameChanged); on<ProfileSetupFullNameChanged>(_onFullNameChanged);
on<ProfileSetupBioChanged>(_onBioChanged); on<ProfileSetupBioChanged>(_onBioChanged);
@@ -35,6 +40,9 @@ class ProfileSetupBloc extends Bloc<ProfileSetupEvent, ProfileSetupState>
/// The use case for searching cities. /// The use case for searching cities.
final SearchCitiesUseCase _searchCities; final SearchCitiesUseCase _searchCities;
/// The user's phone number from the sign-up flow.
final String _phoneNumber;
/// Handles the [ProfileSetupFullNameChanged] event. /// Handles the [ProfileSetupFullNameChanged] event.
void _onFullNameChanged( void _onFullNameChanged(
ProfileSetupFullNameChanged event, ProfileSetupFullNameChanged event,
@@ -95,6 +103,7 @@ class ProfileSetupBloc extends Bloc<ProfileSetupEvent, ProfileSetupState>
action: () async { action: () async {
await _submitProfileSetup( await _submitProfileSetup(
fullName: state.fullName, fullName: state.fullName,
phoneNumber: _phoneNumber,
bio: state.bio.isEmpty ? null : state.bio, bio: state.bio.isEmpty ? null : state.bio,
preferredLocations: state.preferredLocations, preferredLocations: state.preferredLocations,
maxDistanceMiles: state.maxDistanceMiles, maxDistanceMiles: state.maxDistanceMiles,

View File

@@ -56,6 +56,7 @@ class StaffAuthenticationModule extends Module {
() => ProfileSetupBloc( () => ProfileSetupBloc(
submitProfileSetup: i.get<SubmitProfileSetup>(), submitProfileSetup: i.get<SubmitProfileSetup>(),
searchCities: i.get<SearchCitiesUseCase>(), searchCities: i.get<SearchCitiesUseCase>(),
phoneNumber: i.get<AuthBloc>().state.phoneNumber,
), ),
); );
} }

View File

@@ -78,7 +78,7 @@ class ClockInRepositoryImpl implements ClockInRepositoryInterface {
id: json['shiftId'] as String, id: json['shiftId'] as String,
orderId: json['orderId'] as String? ?? '', orderId: json['orderId'] as String? ?? '',
title: json['clientName'] as String? ?? json['roleName'] as String? ?? '', title: json['clientName'] as String? ?? json['roleName'] as String? ?? '',
status: ShiftStatus.fromJson(json['attendanceStatus'] as String?), status: ShiftStatus.assigned,
startsAt: DateTime.parse(json['startTime'] as String), startsAt: DateTime.parse(json['startTime'] as String),
endsAt: DateTime.parse(json['endTime'] as String), endsAt: DateTime.parse(json['endTime'] as String),
locationName: json['location'] as String?, locationName: json['location'] as String?,

View File

@@ -29,7 +29,7 @@ class CertificatesRepositoryImpl implements CertificatesRepository {
final ApiResponse response = final ApiResponse response =
await _api.get(StaffEndpoints.certificates); await _api.get(StaffEndpoints.certificates);
final List<dynamic> items = final List<dynamic> items =
response.data['certificates'] as List<dynamic>? ?? <dynamic>[]; response.data['items'] as List<dynamic>? ?? <dynamic>[];
return items return items
.map((dynamic json) => .map((dynamic json) =>
StaffCertificate.fromJson(json as Map<String, dynamic>)) StaffCertificate.fromJson(json as Map<String, dynamic>))

View File

@@ -28,7 +28,7 @@ class DocumentsRepositoryImpl implements DocumentsRepository {
Future<List<ProfileDocument>> getDocuments() async { Future<List<ProfileDocument>> getDocuments() async {
final ApiResponse response = final ApiResponse response =
await _api.get(StaffEndpoints.documents); await _api.get(StaffEndpoints.documents);
final List<dynamic> items = response.data['documents'] as List<dynamic>? ?? <dynamic>[]; final List<dynamic> items = response.data['items'] as List<dynamic>? ?? <dynamic>[];
return items return items
.map((dynamic json) => .map((dynamic json) =>
ProfileDocument.fromJson(json as Map<String, dynamic>)) ProfileDocument.fromJson(json as Map<String, dynamic>))

View File

@@ -19,7 +19,7 @@ class TaxFormsRepositoryImpl implements TaxFormsRepository {
Future<List<TaxForm>> getTaxForms() async { Future<List<TaxForm>> getTaxForms() async {
final ApiResponse response = final ApiResponse response =
await _api.get(StaffEndpoints.taxForms); await _api.get(StaffEndpoints.taxForms);
final List<dynamic> items = response.data['taxForms'] as List<dynamic>? ?? <dynamic>[]; final List<dynamic> items = response.data['items'] as List<dynamic>? ?? <dynamic>[];
return items return items
.map((dynamic json) => .map((dynamic json) =>
TaxForm.fromJson(json as Map<String, dynamic>)) TaxForm.fromJson(json as Map<String, dynamic>))

View File

@@ -1,6 +1,7 @@
import 'package:krow_core/core.dart'; import 'package:krow_core/core.dart';
import 'package:krow_domain/krow_domain.dart'; import 'package:krow_domain/krow_domain.dart';
import 'package:staff_bank_account/src/domain/arguments/add_bank_account_params.dart';
import 'package:staff_bank_account/src/domain/repositories/bank_account_repository.dart'; import 'package:staff_bank_account/src/domain/repositories/bank_account_repository.dart';
/// Implementation of [BankAccountRepository] using the V2 API. /// Implementation of [BankAccountRepository] using the V2 API.
@@ -17,7 +18,7 @@ class BankAccountRepositoryImpl implements BankAccountRepository {
Future<List<BankAccount>> getAccounts() async { Future<List<BankAccount>> getAccounts() async {
final ApiResponse response = final ApiResponse response =
await _api.get(StaffEndpoints.bankAccounts); await _api.get(StaffEndpoints.bankAccounts);
final List<dynamic> items = response.data['accounts'] as List<dynamic>? ?? <dynamic>[]; final List<dynamic> items = response.data['items'] as List<dynamic>? ?? <dynamic>[];
return items return items
.map((dynamic json) => .map((dynamic json) =>
BankAccount.fromJson(json as Map<String, dynamic>)) BankAccount.fromJson(json as Map<String, dynamic>))
@@ -25,10 +26,10 @@ class BankAccountRepositoryImpl implements BankAccountRepository {
} }
@override @override
Future<void> addAccount(BankAccount account) async { Future<void> addAccount(AddBankAccountParams params) async {
await _api.post( await _api.post(
StaffEndpoints.bankAccounts, StaffEndpoints.bankAccounts,
data: account.toJson(), data: params.toJson(),
); );
} }
} }

View File

@@ -1,16 +1,44 @@
import 'package:equatable/equatable.dart';
import 'package:krow_core/core.dart'; import 'package:krow_core/core.dart';
import 'package:krow_domain/krow_domain.dart'; import 'package:krow_domain/krow_domain.dart' show AccountType;
/// Arguments for adding a bank account. /// Parameters for creating a new bank account via the V2 API.
class AddBankAccountParams extends UseCaseArgument with EquatableMixin { ///
/// Maps directly to the `bankAccountCreateSchema` zod schema:
/// `{ bankName, accountNumber, routingNumber, accountType }`.
class AddBankAccountParams extends UseCaseArgument {
/// Creates an [AddBankAccountParams].
const AddBankAccountParams({
required this.bankName,
required this.accountNumber,
required this.routingNumber,
required this.accountType,
});
const AddBankAccountParams({required this.account}); /// Name of the bank / financial institution.
final BankAccount account; final String bankName;
/// Full account number.
final String accountNumber;
/// Routing / transit number.
final String routingNumber;
/// Account type (checking or savings).
final AccountType accountType;
/// Serialises to the V2 API request body.
Map<String, dynamic> toJson() => <String, dynamic>{
'bankName': bankName,
'accountNumber': accountNumber,
'routingNumber': routingNumber,
'accountType': accountType.toJson(),
};
@override @override
List<Object?> get props => <Object?>[account]; List<Object?> get props => <Object?>[
bankName,
@override accountNumber,
bool? get stringify => true; routingNumber,
accountType,
];
} }

View File

@@ -1,12 +1,12 @@
import 'package:krow_domain/krow_domain.dart'; import 'package:krow_domain/krow_domain.dart' show BankAccount;
import '../arguments/add_bank_account_params.dart';
/// Repository interface for managing bank accounts. /// Repository interface for managing bank accounts.
///
/// Uses [BankAccount] from the V2 domain layer.
abstract class BankAccountRepository { abstract class BankAccountRepository {
/// Fetches the list of bank accounts for the current staff member. /// Fetches the list of bank accounts for the current staff member.
Future<List<BankAccount>> getAccounts(); Future<List<BankAccount>> getAccounts();
/// Adds a new bank account. /// Creates a new bank account with the given [params].
Future<void> addAccount(BankAccount account); Future<void> addAccount(AddBankAccountParams params);
} }

View File

@@ -1,15 +1,17 @@
import 'package:krow_core/core.dart'; import 'package:krow_core/core.dart';
import '../repositories/bank_account_repository.dart';
import '../arguments/add_bank_account_params.dart'; import '../arguments/add_bank_account_params.dart';
import '../repositories/bank_account_repository.dart';
/// Use case to add a bank account. /// Use case to add a bank account.
class AddBankAccountUseCase implements UseCase<AddBankAccountParams, void> { class AddBankAccountUseCase implements UseCase<AddBankAccountParams, void> {
/// Creates an [AddBankAccountUseCase].
AddBankAccountUseCase(this._repository); AddBankAccountUseCase(this._repository);
final BankAccountRepository _repository; final BankAccountRepository _repository;
@override @override
Future<void> call(AddBankAccountParams params) { Future<void> call(AddBankAccountParams params) {
return _repository.addAccount(params.account); return _repository.addAccount(params);
} }
} }

View File

@@ -1,6 +1,7 @@
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:krow_core/core.dart'; import 'package:krow_core/core.dart';
import 'package:krow_domain/krow_domain.dart'; import 'package:krow_domain/krow_domain.dart' show AccountType, BankAccount;
import '../../domain/arguments/add_bank_account_params.dart'; import '../../domain/arguments/add_bank_account_params.dart';
import '../../domain/usecases/add_bank_account_usecase.dart'; import '../../domain/usecases/add_bank_account_usecase.dart';
import '../../domain/usecases/get_bank_accounts_usecase.dart'; import '../../domain/usecases/get_bank_accounts_usecase.dart';
@@ -47,15 +48,10 @@ class BankAccountCubit extends Cubit<BankAccountState>
}) async { }) async {
emit(state.copyWith(status: BankAccountStatus.loading)); emit(state.copyWith(status: BankAccountStatus.loading));
// Create domain entity final AddBankAccountParams params = AddBankAccountParams(
final BankAccount newAccount = BankAccount(
accountId: '', // Generated by server
bankName: bankName, bankName: bankName,
providerReference: routingNumber, accountNumber: accountNumber,
last4: accountNumber.length > 4 routingNumber: routingNumber,
? accountNumber.substring(accountNumber.length - 4)
: accountNumber,
isPrimary: false,
accountType: type == 'CHECKING' accountType: type == 'CHECKING'
? AccountType.checking ? AccountType.checking
: AccountType.savings, : AccountType.savings,
@@ -64,7 +60,7 @@ class BankAccountCubit extends Cubit<BankAccountState>
await handleError( await handleError(
emit: emit, emit: emit,
action: () async { action: () async {
await _addBankAccountUseCase(AddBankAccountParams(account: newAccount)); await _addBankAccountUseCase(params);
// Re-fetch to get latest state including server-generated IDs // Re-fetch to get latest state including server-generated IDs
await loadAccounts(); await loadAccounts();

View File

@@ -22,7 +22,7 @@ class TimeCardRepositoryImpl implements TimeCardRepository {
'month': month.month, 'month': month.month,
}, },
); );
final List<dynamic> items = response.data['entries'] as List<dynamic>? ?? <dynamic>[]; final List<dynamic> items = response.data['items'] as List<dynamic>? ?? <dynamic>[];
return items return items
.map((dynamic json) => .map((dynamic json) =>
TimeCardEntry.fromJson(json as Map<String, dynamic>)) TimeCardEntry.fromJson(json as Map<String, dynamic>))

View File

@@ -18,7 +18,8 @@ class EmergencyContactRepositoryImpl
Future<List<EmergencyContact>> getContacts() async { Future<List<EmergencyContact>> getContacts() async {
final ApiResponse response = final ApiResponse response =
await _api.get(StaffEndpoints.emergencyContacts); await _api.get(StaffEndpoints.emergencyContacts);
final List<dynamic> items = response.data['contacts'] as List<dynamic>? ?? <dynamic>[]; final List<dynamic> items =
response.data['items'] as List<dynamic>? ?? <dynamic>[];
return items return items
.map((dynamic json) => .map((dynamic json) =>
EmergencyContact.fromJson(json as Map<String, dynamic>)) EmergencyContact.fromJson(json as Map<String, dynamic>))
@@ -27,12 +28,27 @@ class EmergencyContactRepositoryImpl
@override @override
Future<void> saveContacts(List<EmergencyContact> contacts) async { Future<void> saveContacts(List<EmergencyContact> contacts) async {
await _api.post( for (final EmergencyContact contact in contacts) {
StaffEndpoints.emergencyContacts, final Map<String, dynamic> body = <String, dynamic>{
data: <String, dynamic>{ 'fullName': contact.fullName,
'contacts': 'phone': contact.phone,
contacts.map((EmergencyContact c) => c.toJson()).toList(), 'relationshipType': contact.relationshipType,
}, 'isPrimary': contact.isPrimary,
); };
if (contact.contactId.isNotEmpty) {
// Existing contact — update via PUT.
await _api.put(
StaffEndpoints.emergencyContactUpdate(contact.contactId),
data: body,
);
} else {
// New contact — create via POST.
await _api.post(
StaffEndpoints.emergencyContacts,
data: body,
);
}
}
} }
} }

View File

@@ -1,3 +1,4 @@
import 'package:dio/dio.dart';
import 'package:krow_core/core.dart'; import 'package:krow_core/core.dart';
import 'package:krow_domain/krow_domain.dart'; import 'package:krow_domain/krow_domain.dart';
@@ -10,20 +11,12 @@ import 'package:staff_profile_info/src/domain/repositories/personal_info_reposit
class PersonalInfoRepositoryImpl implements PersonalInfoRepositoryInterface { class PersonalInfoRepositoryImpl implements PersonalInfoRepositoryInterface {
/// Creates a [PersonalInfoRepositoryImpl]. /// Creates a [PersonalInfoRepositoryImpl].
/// ///
/// Requires the V2 [BaseApiService] for HTTP communication, /// Requires the V2 [BaseApiService] for HTTP communication.
/// [FileUploadService] for uploading files to cloud storage, and
/// [SignedUrlService] for generating signed download URLs.
PersonalInfoRepositoryImpl({ PersonalInfoRepositoryImpl({
required BaseApiService apiService, required BaseApiService apiService,
required FileUploadService uploadService, }) : _api = apiService;
required SignedUrlService signedUrlService,
}) : _api = apiService,
_uploadService = uploadService,
_signedUrlService = signedUrlService;
final BaseApiService _api; final BaseApiService _api;
final FileUploadService _uploadService;
final SignedUrlService _signedUrlService;
@override @override
Future<StaffPersonalInfo> getStaffProfile() async { Future<StaffPersonalInfo> getStaffProfile() async {
@@ -39,39 +32,34 @@ class PersonalInfoRepositoryImpl implements PersonalInfoRepositoryInterface {
required String staffId, required String staffId,
required Map<String, dynamic> data, required Map<String, dynamic> data,
}) async { }) async {
final ApiResponse response = await _api.put( // The PUT response returns { staffId, fullName, email, phone, metadata }
// which does not match the StaffPersonalInfo shape. Perform the update
// and then re-fetch the full profile to return the correct entity.
await _api.put(
StaffEndpoints.personalInfo, StaffEndpoints.personalInfo,
data: data, data: data,
); );
final Map<String, dynamic> json = return getStaffProfile();
response.data as Map<String, dynamic>;
return StaffPersonalInfo.fromJson(json);
} }
@override @override
Future<String> uploadProfilePhoto(String filePath) async { Future<String> uploadProfilePhoto(String filePath) async {
// 1. Upload the file to cloud storage. // The backend expects a multipart file upload at /staff/profile/photo.
final FileUploadResponse uploadRes = await _uploadService.uploadFile( // It uploads to GCS, updates staff metadata, and returns a signed URL.
filePath: filePath, final String fileName =
fileName: 'staff_profile_photo_${DateTime.now().millisecondsSinceEpoch}.jpg';
'staff_profile_photo_${DateTime.now().millisecondsSinceEpoch}.jpg', final FormData formData = FormData.fromMap(<String, dynamic>{
visibility: FileVisibility.public, 'file': await MultipartFile.fromFile(filePath, filename: fileName),
); });
// 2. Generate a signed URL for the uploaded file. final ApiResponse response = await _api.post(
final SignedUrlResponse signedUrlRes =
await _signedUrlService.createSignedUrl(fileUri: uploadRes.fileUri);
final String photoUrl = signedUrlRes.signedUrl;
// 3. Submit the photo URL to the V2 API.
await _api.post(
StaffEndpoints.profilePhoto, StaffEndpoints.profilePhoto,
data: <String, dynamic>{ data: formData,
'fileUri': uploadRes.fileUri,
'photoUrl': photoUrl,
},
); );
final Map<String, dynamic> json =
response.data as Map<String, dynamic>;
return photoUrl; // Backend returns { staffId, fileUri, signedUrl, expiresAt }.
return json['signedUrl'] as String? ?? '';
} }
} }

View File

@@ -27,8 +27,6 @@ class StaffProfileInfoModule extends Module {
i.addLazySingleton<PersonalInfoRepositoryInterface>( i.addLazySingleton<PersonalInfoRepositoryInterface>(
() => PersonalInfoRepositoryImpl( () => PersonalInfoRepositoryImpl(
apiService: i.get<BaseApiService>(), apiService: i.get<BaseApiService>(),
uploadService: i.get<FileUploadService>(),
signedUrlService: i.get<SignedUrlService>(),
), ),
); );

View File

@@ -14,6 +14,7 @@ dependencies:
flutter_bloc: ^8.1.0 flutter_bloc: ^8.1.0
bloc: ^8.1.0 bloc: ^8.1.0
flutter_modular: ^6.3.0 flutter_modular: ^6.3.0
dio: ^5.9.1
equatable: ^2.0.5 equatable: ^2.0.5
# Architecture Packages # Architecture Packages

View File

@@ -96,12 +96,10 @@ class ShiftsRepositoryImpl implements ShiftsRepositoryInterface {
final ApiResponse response = final ApiResponse response =
await _apiService.get(StaffEndpoints.shiftsCompleted); await _apiService.get(StaffEndpoints.shiftsCompleted);
final List<dynamic> items = _extractItems(response.data); final List<dynamic> items = _extractItems(response.data);
var x = items return items
.map((dynamic json) => .map((dynamic json) =>
CompletedShift.fromJson(json as Map<String, dynamic>)) CompletedShift.fromJson(json as Map<String, dynamic>))
.toList(); .toList();
return x;
} }
@override @override