Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion assets/translations/ar.json
Original file line number Diff line number Diff line change
Expand Up @@ -248,5 +248,6 @@
"userAddress": "عنوان المستخدم",
"accept": "قبول",
"reject": "رفض",
"noPendingOrders": "لا توجد طلبات معلقة"
"noPendingOrders": "لا توجد طلبات معلقة",
"floweryRider": "سائق فلاوري"
}
3 changes: 2 additions & 1 deletion assets/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -250,5 +250,6 @@
"userAddress": "User address",
"accept": "Accept",
"reject": "Reject",
"noPendingOrders": "No pending orders"
"noPendingOrders": "No pending orders",
"floweryRider": "Flowery Rider"
}
17 changes: 17 additions & 0 deletions lib/app/config/auth_storage/auth_storage.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,26 @@ class AuthStorage {
static const _tokenKey = 'auth_token';
static const _userKey = 'user_data';
static const _rememberMeKey = 'remember_me';
static const _orderIdKey = 'order_id';

Future<SharedPreferences> get _prefs async =>
await SharedPreferences.getInstance();

Future<void> saveOrderId(String orderId) async {
final prefs = await _prefs;
await prefs.setString(_orderIdKey, orderId);
}

Future<String?> getOrderId() async {
final prefs = await _prefs;
return prefs.getString(_orderIdKey);
}

Future<void> clearOrderId() async {
final prefs = await _prefs;
await prefs.remove(_orderIdKey);
}

Future<void> saveToken(String token) async {
final prefs = await _prefs;
await prefs.setString(_tokenKey, token);
Expand Down Expand Up @@ -54,5 +70,6 @@ class AuthStorage {
await clearToken();
await clearUser();
await setRememberMe(false);
await clearOrderId();
}
}
22 changes: 22 additions & 0 deletions lib/app/config/di/di.config.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions lib/app/config/network/network_module.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'package:dio/dio.dart';
import 'package:tracking_app/app/config/auth_storage/auth_storage.dart';
import 'package:tracking_app/app/config/network/interceptor.dart';
import 'package:injectable/injectable.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:pretty_dio_logger/pretty_dio_logger.dart';

import '../../core/api_manger/api_client.dart';
Expand Down Expand Up @@ -37,4 +38,8 @@ abstract class NetworkModule {

@lazySingleton
ApiClient authApiClient(Dio dio) => ApiClient(dio);

@lazySingleton
@Named('firestore')
FirebaseFirestore get firestore => FirebaseFirestore.instance;
}
1 change: 0 additions & 1 deletion lib/app/core/api_manger/api_client.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import 'dart:io';

import 'package:dio/dio.dart';
import 'package:retrofit/dio.dart';
import 'package:tracking_app/app/core/values/app_endpoint_strings.dart';
import 'package:tracking_app/features/auth/data/model/response/change_password_dto.dart';
import 'package:tracking_app/features/auth/data/model/request/LoginRequest.dart';
Expand Down
1 change: 1 addition & 0 deletions lib/app/core/app_constants.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,5 @@ class AppConstants {
static const String english = 'English';
static const String arabic = 'Arabic';
static const String logoutFailed = 'Logout failed';
static const String floweryRider = 'Flowery Rider';
}
6 changes: 6 additions & 0 deletions lib/features/home/api/driverOrderDataS_imp.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'package:tracking_app/app/core/network/api_result.dart';
import 'package:tracking_app/app/core/network/safe_api_call.dart';
import 'package:tracking_app/features/home/data/datascourse/driverOrderDatascource.dart';
import 'package:tracking_app/features/home/data/model/response/orderRespons.dart';
import 'package:tracking_app/features/profile/data/models/responses/edit_profile_response.dart';

@Injectable(as: DriverOrderDataSource)
class DriverOrderDataSourceImpl implements DriverOrderDataSource {
Expand All @@ -15,4 +16,9 @@ class DriverOrderDataSourceImpl implements DriverOrderDataSource {
Future<ApiResult<OrderResponse>> getPendingOrders(String token) {
return safeApiCall(call: () => _apiClient.getPendingOrders(token));
}

@override
Future<ApiResult<EditProfileResponse>> getProfile(String token) {
return safeApiCall(call: () => _apiClient.getProfile(token: token));
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import 'package:tracking_app/app/core/network/api_result.dart';
import 'package:tracking_app/features/home/data/model/response/orderRespons.dart';
import 'package:tracking_app/features/profile/data/models/responses/edit_profile_response.dart';

abstract class DriverOrderDataSource {
Future<ApiResult<OrderResponse>> getPendingOrders(String token);
Future<ApiResult<EditProfileResponse>> getProfile(String token);
}
6 changes: 6 additions & 0 deletions lib/features/home/data/repo/driverOrderRepo_impl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'package:injectable/injectable.dart';
import 'package:tracking_app/app/core/network/api_result.dart';
import 'package:tracking_app/features/home/data/datascourse/driverOrderDatascource.dart';
import 'package:tracking_app/features/home/data/model/response/orderRespons.dart';
import 'package:tracking_app/features/profile/data/models/responses/edit_profile_response.dart';
import 'package:tracking_app/features/home/domain/repo/driverOrderRepo.dart';

@Injectable(as: DriverOrderRepo)
Expand All @@ -14,4 +15,9 @@ class DriverOrderRepositoryImpl implements DriverOrderRepo {
Future<ApiResult<OrderResponse>> getPendingOrders(String token) {
return _dataSource.getPendingOrders(token);
}

@override
Future<ApiResult<EditProfileResponse>> getProfile(String token) {
return _dataSource.getProfile(token);
}
}
2 changes: 2 additions & 0 deletions lib/features/home/domain/repo/driverOrderRepo.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import 'package:tracking_app/app/core/network/api_result.dart';
import 'package:tracking_app/features/home/data/model/response/orderRespons.dart';
import 'package:tracking_app/features/profile/data/models/responses/edit_profile_response.dart';

abstract class DriverOrderRepo {
Future<ApiResult<OrderResponse>> getPendingOrders(String token);
Future<ApiResult<EditProfileResponse>> getProfile(String token);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:injectable/injectable.dart';
import 'package:tracking_app/features/profile/data/models/driver_model.dart';

@injectable
class UploadDriverFireDataUseCase {
final FirebaseFirestore _firestore;

UploadDriverFireDataUseCase(@Named('firestore') this._firestore);

Future<void> call(
DriverModel driver, {
required double lat,
required double lng,
String? deviceToken,
}) async {
final driverCollection = _firestore.collection('drivers');
await driverCollection.doc(driver.Id).set({
'id': driver.Id,
'name': '${driver.firstName} ${driver.lastName}',
'phone': driver.phone,
'currentLocation': {'lat': lat, 'lng': lng},
'deviceToken': deviceToken,
}, SetOptions(merge: true));
}
}
Comment on lines +1 to +26
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing test coverage: The newly added use cases UploadDriverFireDataUseCase and UploadOrderFireDataUseCase lack test coverage. Test files should be created at test/features/home/domain/usecases/upload_driver_fire_data_use_case_test.dart and test/features/home/domain/usecases/upload_order_fire_data_use_case_test.dart following the pattern used for getdriverOrderUsecase_test.dart.

Copilot uses AI. Check for mistakes.
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import 'package:cloud_firestore/cloud_firestore.dart' hide Order;
import 'package:injectable/injectable.dart' hide Order;
import 'package:tracking_app/features/home/data/model/response/orderRespons.dart';

@injectable
class UploadOrderFireDataUseCase {
final FirebaseFirestore _firestore;

UploadOrderFireDataUseCase(@Named('firestore') this._firestore);

Future<void> call({required Order order, required String driverId}) async {
final orderCollection = _firestore.collection('orders');

final data = {
'driver_id': driverId,
'oder_dt': {
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in field name 'oder_dt' should be 'order_dt'. This typo will affect the Firestore document structure and any code that reads from this collection.

Suggested change
'oder_dt': {
'order_dt': {

Copilot uses AI. Check for mistakes.
'items':
order.orderItems
?.map(
(e) => {
'productId': e.product?.id,
'title': e.product?.title,
'quantity': e.quantity,
'price': e.product?.price,
'image': e.product?.imgCover,
},
)
.toList() ??
[],
'orderId': order.id,
'pickupAddress': {
'address': order.store?.address ?? '',
'name': order.store?.name ?? '',
},
'status': order.state ?? 'pending',
'totalPrice': order.totalPrice ?? 0,
'userAddress':
'${order.shippingAddress?.street ?? ''}, ${order.shippingAddress?.city ?? ''}',
},
'userAddress': {
'adress':
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in field name 'adress' should be 'address'. This inconsistency exists in the Firestore document structure while line 42 correctly uses 'address' in the concatenation string.

Suggested change
'adress':
'address':

Copilot uses AI. Check for mistakes.
'${order.shippingAddress?.street ?? ''}, ${order.shippingAddress?.city ?? ''}',
'name': '${order.user?.firstName ?? ''} ${order.user?.lastName ?? ''}',
'user_id': order.user?.id ?? '',
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Data duplication: The 'user_id' field appears twice - once at the root level (line 46) and once inside the 'userAddress' object (line 44). This redundancy could lead to data inconsistencies and should be consolidated to a single location.

Suggested change
'user_id': order.user?.id ?? '',

Copilot uses AI. Check for mistakes.
},
Comment on lines +37 to +45
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Data duplication: The 'userAddress' field appears twice in the document structure - once inside 'oder_dt' (line 37-38) and once at the root level (line 40-45). This creates redundant data that could lead to inconsistencies if one is updated without the other. Consider consolidating this data or clearly documenting why both are needed.

Copilot uses AI. Check for mistakes.
'user_id': order.user?.id ?? '',
};

if (order.id != null) {
await orderCollection.doc(order.id).set(data, SetOptions(merge: true));
} else {
await orderCollection.add(data);
}
}
}
86 changes: 84 additions & 2 deletions lib/features/home/presentation/manger/driverorderCubit.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/foundation.dart';
import 'package:geolocator/geolocator.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:injectable/injectable.dart' hide Order;
import 'package:tracking_app/features/home/data/model/response/orderRespons.dart';
Expand All @@ -7,21 +10,34 @@ import 'package:tracking_app/app/core/network/api_result.dart';
import 'package:tracking_app/features/home/domain/usecase/getdriverOrderUsecase.dart';
import 'package:tracking_app/features/home/presentation/manger/driverorderIntent.dart';
import 'package:tracking_app/features/home/presentation/manger/driverorderStates.dart';
import 'package:tracking_app/features/home/domain/repo/driverOrderRepo.dart';
import 'package:tracking_app/features/home/domain/usecase/upload_driver_fire_data_use_case.dart';
import 'package:tracking_app/features/home/domain/usecase/upload_order_fire_data_use_case.dart';

@injectable
class DriverOrderCubit extends Cubit<DriverOrderState> {
final GetDriverOrdersUseCase _getDriverOrdersUseCase;
final AuthStorage _authStorage;
final UploadDriverFireDataUseCase _uploadDriverFireDataUseCase;
final UploadOrderFireDataUseCase _uploadOrderFireDataUseCase;
final DriverOrderRepo _driverOrderRepository;

DriverOrderCubit(this._getDriverOrdersUseCase, this._authStorage)
: super(DriverOrderState());
DriverOrderCubit(
this._getDriverOrdersUseCase,
this._authStorage,
this._uploadDriverFireDataUseCase,
this._uploadOrderFireDataUseCase,
this._driverOrderRepository,
) : super(DriverOrderState());

void onIntent(DriverOrderIntent intent) {
switch (intent) {
case GetPendingOrders():
_getPendingOrders();
case RemoveOrder(order: final order):
_removeOrder(order);
case AcceptOrder(order: final order):
_acceptOrder(order);
Comment on lines +39 to +40
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing test coverage: The AcceptOrder intent handling in _acceptOrder method is not covered by tests. The existing test file driverorderCubit_test.dart should be updated to include test cases for the AcceptOrder intent.

Copilot uses AI. Check for mistakes.
}
}

Expand All @@ -43,6 +59,72 @@ class DriverOrderCubit extends Cubit<DriverOrderState> {
}
}

Future<void> _acceptOrder(Order order) async {
final token = await _authStorage.getToken();
if (token == null) return;

final result = await _driverOrderRepository.getProfile(token);

if (result is SuccessApiResult) {
final profile = (result as SuccessApiResult).data;
if (profile.driver != null) {
try {
final position = await _determinePosition();
if (position == null) {
if (kDebugMode)
print("Location permission denied or service disabled.");
return;
}

final deviceToken = await FirebaseMessaging.instance.getToken();
await _uploadDriverFireDataUseCase(
profile.driver!,
lat: position.latitude,
lng: position.longitude,
deviceToken: deviceToken,
);

await _uploadOrderFireDataUseCase(
order: order,
driverId: profile.driver?.Id ?? '',
);

if (order.id != null) {
await _authStorage.saveOrderId(order.id!);
}
} catch (e) {
if (kDebugMode) {
print("Firestore/Location Error: $e");
}
}
}
Comment on lines +63 to +100
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing error handling: The _acceptOrder method does not update the UI state or provide any user feedback when location permissions are denied, location services are disabled, or when Firestore operations fail. Users will experience silent failures with no indication of what went wrong.

Suggested change
final token = await _authStorage.getToken();
if (token == null) return;
final result = await _driverOrderRepository.getProfile(token);
if (result is SuccessApiResult) {
final profile = (result as SuccessApiResult).data;
if (profile.driver != null) {
try {
final position = await _determinePosition();
if (position == null) {
if (kDebugMode)
print("Location permission denied or service disabled.");
return;
}
final deviceToken = await FirebaseMessaging.instance.getToken();
await _uploadDriverFireDataUseCase(
profile.driver!,
lat: position.latitude,
lng: position.longitude,
deviceToken: deviceToken,
);
await _uploadOrderFireDataUseCase(
order: order,
driverId: profile.driver?.Id ?? '',
);
if (order.id != null) {
await _authStorage.saveOrderId(order.id!);
}
} catch (e) {
if (kDebugMode) {
print("Firestore/Location Error: $e");
}
}
}
// Indicate that an accept operation is in progress.
emit(state.copyWith(orderResource: Resource.loading()));
final token = await _authStorage.getToken();
if (token == null) {
emit(
state.copyWith(
orderResource: Resource.error("User not authenticated"),
),
);
return;
}
final result = await _driverOrderRepository.getProfile(token);
if (result is SuccessApiResult) {
final profile = (result as SuccessApiResult).data;
if (profile.driver == null) {
emit(
state.copyWith(
orderResource: Resource.error("Driver profile not found"),
),
);
return;
}
try {
final position = await _determinePosition();
if (position == null) {
if (kDebugMode) {
print("Location permission denied or service disabled.");
}
emit(
state.copyWith(
orderResource: Resource.error(
"Location permission denied or service disabled",
),
),
);
return;
}
final deviceToken = await FirebaseMessaging.instance.getToken();
await _uploadDriverFireDataUseCase(
profile.driver!,
lat: position.latitude,
lng: position.longitude,
deviceToken: deviceToken,
);
await _uploadOrderFireDataUseCase(
order: order,
driverId: profile.driver?.Id ?? '',
);
if (order.id != null) {
await _authStorage.saveOrderId(order.id!);
}
} catch (e) {
if (kDebugMode) {
print("Firestore/Location Error: $e");
}
emit(
state.copyWith(
orderResource: Resource.error("Failed to accept order"),
),
);
}
} else if (result is ErrorApiResult) {
final error = (result as ErrorApiResult).error;
emit(
state.copyWith(
orderResource: Resource.error(error),
),
);

Copilot uses AI. Check for mistakes.
}
}
Comment on lines +62 to +102
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing state update after accepting order: After successfully accepting an order, the method should either remove the order from the list or refresh the pending orders list. Currently, the accepted order remains visible in the UI until manual refresh.

Copilot uses AI. Check for mistakes.

Future<Position?> _determinePosition() async {
bool serviceEnabled;
LocationPermission permission;

serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (!serviceEnabled) {
return null;
}

permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission();
if (permission == LocationPermission.denied) {
return null;
}
}

if (permission == LocationPermission.deniedForever) {
return null;
}

return await Geolocator.getCurrentPosition();
}

Future<void> _getPendingOrders() async {
emit(state.copyWith(orderResource: Resource.loading()));
final token = await _authStorage.getToken();
Expand Down
5 changes: 5 additions & 0 deletions lib/features/home/presentation/manger/driverorderIntent.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,8 @@ class RemoveOrder extends DriverOrderIntent {
final Order order;
RemoveOrder(this.order);
}

class AcceptOrder extends DriverOrderIntent {
final Order order;
AcceptOrder(this.order);
}
Loading
Loading