Skip to content
Draft
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
4 changes: 2 additions & 2 deletions src/components/ConfirmedRoute.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import useOnyx from '@hooks/useOnyx';
import useStyleUtils from '@hooks/useStyleUtils';
import useThemeStyles from '@hooks/useThemeStyles';
import getArrayDepth from '@libs/getArrayDepth';
import {getWaypointIndex} from '@libs/TransactionUtils';
import {getTransactionRoutes, getWaypointIndex} from '@libs/TransactionUtils';
import {init as initMapboxToken, stop as stopMapboxToken} from '@userActions/MapboxToken';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
Expand Down Expand Up @@ -37,7 +37,7 @@ type ConfirmedRouteProps = {

function ConfirmedRoute({transaction, isSmallerIcon, shouldHaveBorderRadius = true, requireRouteToDisplayMap = false, interactive}: ConfirmedRouteProps) {
const {isOffline} = useNetwork();
const {route0: route} = transaction?.routes ?? {};
const {route0: route} = getTransactionRoutes(transaction) ?? {};
const waypoints = transaction?.comment?.waypoints ?? {};
const coordinates = route?.geometry?.coordinates ?? [];
const styles = useThemeStyles();
Expand Down
4 changes: 2 additions & 2 deletions src/components/DistanceRequest/DistanceRequestFooter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import useOnyx from '@hooks/useOnyx';
import usePolicy from '@hooks/usePolicy';
import useThemeStyles from '@hooks/useThemeStyles';
import DistanceRequestUtils from '@libs/DistanceRequestUtils';
import {getDistanceInMeters, getWaypointIndex, isCustomUnitRateIDForP2P} from '@libs/TransactionUtils';
import {getDistanceInMeters, getTransactionRoutes, getWaypointIndex, isCustomUnitRateIDForP2P} from '@libs/TransactionUtils';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {Policy} from '@src/types/onyx';
Expand Down Expand Up @@ -103,7 +103,7 @@ function DistanceRequestFooter({waypoints, transaction, navigateToWaypointEditPa
zoom: CONST.MAPBOX.DEFAULT_ZOOM,
location: waypointMarkers?.at(0)?.coordinate ?? CONST.MAPBOX.DEFAULT_COORDINATE,
}}
directionCoordinates={(transaction?.routes?.route0?.geometry?.coordinates as Array<[number, number]>) ?? []}
directionCoordinates={(getTransactionRoutes(transaction)?.route0?.geometry?.coordinates as Array<[number, number]>) ?? []}
style={[styles.mapView, styles.mapEditView]}
waypoints={waypointMarkers}
styleURL={CONST.MAPBOX.STYLE_URL}
Expand Down
39 changes: 39 additions & 0 deletions src/libs/GPSDraftDetailsUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,43 @@ function isTripStopped(gpsDraftDetails: GpsDraftDetails | undefined): boolean {
return !gpsDraftDetails?.isTracking && getTotalGpsTripPoints(gpsDraftDetails) > 0;
}

/**
* Decodes GPS coordinates stored by the backend as a base64-encoded binary blob.
* Each point occupies 16 bytes: float64 latitude (little-endian) followed by float64 longitude (little-endian).
* This matches the PHP encoding in TransactionUtils::encodeGpsCoordinates() which uses pack('dd', lat, lng).
*/
function decodeGpsCoordinates(base64Blob: string): Array<{lat: number; lng: number}> {
const binaryString = atob(base64Blob);
const arrayBuffer = new ArrayBuffer(binaryString.length);
const uint8Array = new Uint8Array(arrayBuffer);
for (let i = 0; i < binaryString.length; i++) {
uint8Array[i] = binaryString.charCodeAt(i);
}
const view = new DataView(arrayBuffer);
const points: Array<{lat: number; lng: number}> = [];
for (let i = 0; i < uint8Array.byteLength; i += 16) {
points.push({
lat: view.getFloat64(i, true),
lng: view.getFloat64(i + 8, true),
});
}
return points;
}

function getRoutesFromEncodedGpsCoordinates(encodedGpsCoordinates: string, distanceInMeters?: number | null): Routes {
const coordinates = decodeGpsCoordinates(encodedGpsCoordinates).map(({lat, lng}) => [lng, lat]);

return {
route0: {
distance: distanceInMeters ?? null,
geometry: {
type: 'LineString',
coordinates,
},
},
};
}

function getGpsPoints(gpsDraftDetails: GpsDraftDetails | undefined): GPSPoint[][] {
return gpsDraftDetails?.gpsPoints ?? [[]];
}
Expand All @@ -177,8 +214,10 @@ function getLastGpsPoint(gpsDraftDetails: GpsDraftDetails | undefined): GPSPoint
}

export {
decodeGpsCoordinates,
getGPSRoutes,
getGPSWaypoints,
getRoutesFromEncodedGpsCoordinates,
stopGpsTrip,
getGPSConvertedDistance,
getStringifiedGPSCoordinates,
Expand Down
21 changes: 20 additions & 1 deletion src/libs/TransactionUtils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {getCategoryDefaultTaxRate, isCategoryMissing} from '@libs/CategoryUtils'
import {convertToBackendAmount, getCurrencyDecimals, getCurrencySymbol} from '@libs/CurrencyUtils';
import DateUtils from '@libs/DateUtils';
import DistanceRequestUtils from '@libs/DistanceRequestUtils';
import {getRoutesFromEncodedGpsCoordinates} from '@libs/GPSDraftDetailsUtils';
import {toLocaleDigit} from '@libs/LocaleDigitUtils';
import {translateLocal} from '@libs/Localize';
import Log from '@libs/Log';
Expand Down Expand Up @@ -1834,11 +1835,28 @@ function hasPendingUI(transaction: OnyxEntry<Transaction>, transactionViolations
return isScanning(transaction) || isPending(transaction) || (!!transaction && hasPendingRTERViolation(transactionViolations));
}

/**
* Returns transaction routes, falling back to decoding encodedGpsCoordinates when routes are absent.
*/
function getTransactionRoutes(transaction: OnyxEntry<Transaction>): Routes | undefined {
if (transaction?.routes) {
return transaction.routes;
}

if (transaction?.comment?.encodedGpsCoordinates) {
const unit = transaction.comment?.customUnit?.distanceUnit;
const distanceInMeters = unit ? getDistanceInMeters(transaction, unit) : null;
return getRoutesFromEncodedGpsCoordinates(transaction.comment.encodedGpsCoordinates, distanceInMeters);
}

return undefined;
}

/**
* Check if the transaction has a defined route
*/
function hasRoute(transaction: OnyxEntry<Transaction>, isDistanceRequestType?: boolean): boolean {
return !!transaction?.routes?.route0?.geometry?.coordinates || (!!isDistanceRequestType && transaction?.comment?.customUnit?.quantity !== undefined);
return !!getTransactionRoutes(transaction)?.route0?.geometry?.coordinates || (!!isDistanceRequestType && transaction?.comment?.customUnit?.quantity !== undefined);
}

function waypointHasValidAddress(waypoint: RecentWaypoint | Waypoint): boolean {
Expand Down Expand Up @@ -2988,6 +3006,7 @@ export {
hasReceipt,
hasEReceipt,
hasRoute,
getTransactionRoutes,
isReceiptBeingScanned,
didReceiptScanSucceed,
getValidWaypoints,
Expand Down
3 changes: 3 additions & 0 deletions src/types/onyx/Transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,9 @@ type Comment = {

/** Odometer end image (File object with uri on web, URI string on native) */
odometerEndImage?: FileObject | string;

/** Base64-encoded binary GPS coordinates (16 bytes per point: float64 lat LE + float64 lng LE) */
encodedGpsCoordinates?: string;
};

/** Model of transaction custom unit */
Expand Down
Loading