Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Barcode scanner #1834

Merged
merged 37 commits into from
Dec 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
882aa3d
feat(barcode): create basic ui & scanner component
r0xsh Jul 12, 2024
e386778
wip
r0xsh Jul 17, 2024
a6c305b
feat(ui): build all the UI and small logic
r0xsh Aug 27, 2024
1af156c
fix: make unused argument deprecated
r0xsh Sep 4, 2024
668dbe5
feat: app fully functionnal
r0xsh Sep 4, 2024
01940c0
feat(barcode): polish :clean:
r0xsh Sep 5, 2024
3dceb6e
Merge branch 'master' into feature/barcode
r0xsh Sep 5, 2024
6ef3e4b
Merge branch 'master' into feature/barcode
r0xsh Oct 30, 2024
874ada4
Merge branch 'master' into feature/barcode
r0xsh Nov 7, 2024
a14f4a7
feat: enable `client action` behaviors
r0xsh Nov 8, 2024
7c449d0
feat: add `ask_to_complete` behavior.
r0xsh Nov 8, 2024
bf939ea
fix: fix navigation flow after completing a task via the barcode
r0xsh Nov 8, 2024
3315760
feat: Simplify Barcode component state. Introduce "actions queue"
r0xsh Nov 21, 2024
f0d4672
fix: just forward the promise.
r0xsh Nov 21, 2024
5ebf3f6
feat(i18n): add translations
r0xsh Nov 26, 2024
30aa645
feat: add support for action token and forward it to `X-Action-Token`
r0xsh Nov 27, 2024
dec59da
fix: navigation callback was not called due to a change of params of
r0xsh Nov 27, 2024
bcd16f7
fix: fix navigation behavior when navigate to `Task`
r0xsh Nov 27, 2024
2b154f6
add translations.
r0xsh Nov 27, 2024
77450b7
Merge branch 'master' into feature/barcode
r0xsh Nov 27, 2024
d2ee8e1
add little-date dep
r0xsh Nov 27, 2024
3e1cd64
fix: handle dark mode
r0xsh Nov 28, 2024
df749ac
fix: show status when scanned
r0xsh Nov 28, 2024
a421250
feat: do not display push notifications while in barcode view.
r0xsh Dec 2, 2024
f9a6ba2
improve status label on barcode screen. Fix navigator warning
r0xsh Dec 4, 2024
818eeba
fix linter
r0xsh Dec 4, 2024
990d170
clean code.
r0xsh Dec 4, 2024
aaaf9b7
fix lint error
r0xsh Dec 4, 2024
e963610
add missing dep in useCallback
r0xsh Dec 4, 2024
31bb1a9
make the linter happy again
r0xsh Dec 4, 2024
80aa194
fix(ui): change `Add note` color back to blue
r0xsh Dec 6, 2024
9232e77
feat(barcode): add Start task button
r0xsh Dec 6, 2024
3210222
feat: Add ask to start pickup action
r0xsh Dec 16, 2024
b58147e
Merge branch 'master' into feature/barcode
r0xsh Dec 26, 2024
ade5eb2
fix linter.
r0xsh Dec 26, 2024
a3913da
fix json
r0xsh Dec 26, 2024
bcc9096
fix json.
r0xsh Dec 26, 2024
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
144 changes: 144 additions & 0 deletions src/components/BarcodeCameraView.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import React, { useState, forwardRef } from 'react';
import { Dimensions, Text } from 'react-native';
import { View, Vibration } from 'react-native';
import { CameraView, useCameraPermissions } from 'expo-camera/next';
import _ from 'lodash';
import { Icon } from 'native-base';
import Ionicons from 'react-native-vector-icons/Ionicons';
import { TouchableOpacity } from 'react-native-gesture-handler';
import FocusHandler from './FocusHandler';

function getBoxBoundaries(points) {
const xs = points.map(point => point.x);
const ys = points.map(point => point.y);
const minX = Math.min(...xs);
const maxX = Math.max(...xs);
const minY = Math.min(...ys);
const maxY = Math.max(...ys);
return { minX, maxX, minY, maxY };
}

function horizontalLineIntersectsBox(lineY, lineStartX, lineEndX, points) {
const { minX, maxX, minY, maxY } = getBoxBoundaries(points);

if (lineY < minY || lineY > maxY) {
return false;
}
if (lineStartX > lineEndX) {
[lineStartX, lineEndX] = [lineEndX, lineStartX];
}
return lineEndX >= minX && lineStartX <= maxX;
}

function BarcodeCameraView({ disabled, ...props }, ref) {
const [hasPermission, requestPermission] = useCameraPermissions();

const { width: CameraWidth } = Dimensions.get('window');
const CameraHeight = CameraWidth * 0.55;
const PaddingHorizontal = CameraWidth * 0.1618;

const [barcode, setBarcode] = useState(null);
const [flash, setFlash] = useState(false);

//The hide behavior is a hack to force the camera to re-render
//if another camera is opened on modal. If we don't do this, the
//camera will not render.
const [hide, setHide] = useState(disabled ?? false);

if (!hasPermission) {
return (
<View>
<Text>No camera permission</Text>
Copy link
Member

Choose a reason for hiding this comment

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

maybe write a more helpful message? like "Allow camera in app setting to use the barcode scanner"?

Copy link
Member Author

Choose a reason for hiding this comment

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

if i click unassign then it unassigns but I am not redirected back to the "My tasks" screen

Yup this is intentional behavior, but this feature is hard to design since it can be used is many scenarios. But i think is can be solve by having a "batch scanning mode".
The feature was originally thought for doing batch scan at morning when the rider prepares his route. But you're right this create some friction in a rider workflow.

if i click OK then nothing happens, the popup close and no information shows up in task field, i am stuck on the empty barcode screen + barcode scanning does not seem to work a second time

Just go back, no ?

scanning an unassigned task -> it assigns directly to me without a popup opening, is that normal?

Yup expected behavior, the alert is shown only when the task is already assigned to self or another rider.

what does the "ok" button means to the user in the various popups? it is more a yes/no question no? so it should be a "NO" button

It just close the modal, maybe No label will be clearer 👍

when clicking on "ok" nothing shows, nothing happens (and i think you can not scan anymore)

You can't scan twice the same barcode ? But i agree this is a bit annoying. I already thought about a potential fix for that: https://github.com/coopcycle/coopcycle-app/pull/1834/files#diff-044fc4e1bab6b1c678131dde9f7719a282973acbf04d245c16c2fd26cb335d8aR248-R254

how one is supposed to complete a task? when i scan a task that is assigned to me then i can only unassign, not complete. also in the UI/UX when the task is assigned to me, complete the task should be the main choice

The task should be in DOING status to trigger the complete behavior

</View>
);
}

if (!hasPermission.granted) {
requestPermission();
return <View />;
}

const onScanned = _.throttle(result => {
const { data, cornerPoints } = result;
if (
!horizontalLineIntersectsBox(
CameraHeight / 2,
PaddingHorizontal,
CameraWidth - PaddingHorizontal,
cornerPoints,
)
) {
return;
}

if (data !== barcode) {
Vibration.vibrate();
setBarcode(data);
props.onScanned(data);
}
}, 200);

return (
<>
<FocusHandler
onFocus={() => {
if (!disabled) {
setHide(false);
}
}}
onBlur={() => {
setHide(true);
}}
/>
{!hide && (
<CameraView
ref={ref}
enableTorch={flash}
style={{
height: CameraHeight,
width: CameraWidth,
position: 'relative',
}}
onBarcodeScanned={onScanned}
barcodeScannerSettings={{
barcodeTypes: ['code128'],
}}>
<View
style={{
position: 'absolute',
borderColor: 'rgba(255, 0, 0, 0.6)',
borderBottomWidth: 1,
top: CameraHeight / 2,
left: PaddingHorizontal,
right: PaddingHorizontal,
}}
/>
<View
style={{
position: 'absolute',
top: 10,
right: 10,
}}>
<TouchableOpacity onPress={() => setFlash(!flash)}>
<Icon
as={Ionicons}
name={flash ? 'flash-sharp' : 'flash-off-sharp'}
style={{ color: 'rgba(255, 255, 255, 0.6)' }}
/>
</TouchableOpacity>
</View>
</CameraView>
)}
{hide && (
<View
style={{
height: CameraHeight,
width: CameraWidth,
backgroundColor: 'black',
}}></View>
)}
</>
);
}

export default forwardRef(BarcodeCameraView);
14 changes: 12 additions & 2 deletions src/components/NotificationHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ import { useDispatch, useSelector } from 'react-redux';

import {
clearNotifications,
shouldNotificationBeDisplayed,
startSound,
stopSound,
} from '../redux/App/actions';
import NotificationModal from './NotificationModal';
import {
selectNotificationsToDisplay,
selectNotificationsWithSound,
selectShouldNotificationBeDisplayed
} from '../redux/App/selectors';
import { AppState } from 'react-native';

Expand All @@ -25,6 +27,7 @@ const NOTIFICATION_DURATION_MS = 10000;
export default function NotificationHandler() {
const notificationsToDisplay = useSelector(selectNotificationsToDisplay);
const notificationsWithSound = useSelector(selectNotificationsWithSound);
const shouldNotificationBeDisplayed = useSelector(selectShouldNotificationBeDisplayed);

const hasNotifications = useMemo(
() =>
Expand Down Expand Up @@ -53,12 +56,19 @@ export default function NotificationHandler() {
// but it's very limited, e.g. handlers set via setTimeout are not executed
// so we do not play sound in that case, because we will not be able to stop it
// use memoized value to avoid re-starting the sound when more notifications arrive
if (hasNotificationsWithSound && AppState.currentState === 'active') {
if (
shouldNotificationBeDisplayed &&
hasNotificationsWithSound &&
AppState.currentState === 'active') {
dispatch(startSound());
} else {
dispatch(stopSound());
}
}, [hasNotificationsWithSound, dispatch]);
}, [shouldNotificationBeDisplayed, hasNotificationsWithSound, dispatch]);

if (!shouldNotificationBeDisplayed) {
return null;
}

return (
<NotificationModal
Expand Down
21 changes: 20 additions & 1 deletion src/i18n/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,25 @@
"CART_TIME_RANGE_CHANGED_MODAL_MESSAGE": "Die Öffnungszeiten erlauben es nicht, die Bestellung heute aufzugeben",
"CART_TIME_RANGE_CHANGED_MODAL_CHOOSE_TIME_RANGE_TEXT": "Bitte wählen Sie einen anderen Zeitraum",
"CART_TIME_RANGE_CHANGED_MODAL_SELECT_TIME_RANGE_ACTION": "Lieferzeit ändern",
"CART_TIME_RANGE_CHANGED_MODAL_CHOOSE_RESTAURANT_ACTION": "Wählen Sie ein anderes Restaurant"
"CART_TIME_RANGE_CHANGED_MODAL_CHOOSE_RESTAURANT_ACTION": "Wählen Sie ein anderes Restaurant",
"BARCODE_TASK_ALREADY_ASSIGNED_TITLE": "Aufgabe bereits zugewiesen",
"BARCODE_TASK_ALREADY_ASSIGNED_ANOTHER_MESSAGE": "Diese Aufgabe wurde bereits einem anderen Benutzer zugewiesen",
"BARCODE_TASK_ALREADY_ASSIGNED_SELF_MESSAGE": "Diese Aufgabe wurde dir bereits zugewiesen",
"BARCODE_TASK_ALREADY_ASSIGNED_UNASSIGN": "Zuweisung aufheben",
"BARCODE_TASK_ALREADY_ASSIGNED_ASSIGN_TO_ME": "Mir zuweisen",
"TASK_MULTIPLE_PACKAGES": "Mehrere Pakete",
"X_PACKAGES": "{{count}} Pakete",
"NO_NEED_TO_SCAN_OTHERS": "Es ist nicht nötig, die anderen Pakete zu scannen.",
"ADD_NOTE": "Notiz hinzufügen",
"REPORT_AN_INCIDENT": "Einen Vorfall melden",
"UPDATE_PARCEL_DETAILS": "Paketdetails aktualisieren",
"PICTURES": "Fotos",
"TASK_TODO": "Zu erledigen",
"TASK_DOING": "In Bearbeitung",
"TASK_FAILED": "Fehlgeschlagen",
"TASK_DONE": "Erledigt",
"TASK_CANCELLED": "Abgebrochen",
"ASK_TO_START_PICKUP_TITLE": "Abholaufgabe",
"ASK_TO_START_PICKUP_MESSAGE": "Die Abholaufgabe wurde noch nicht gestartet."
}
}
23 changes: 21 additions & 2 deletions src/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,25 @@
"ORDER__SHIPPING_TIME_RANGE__NOT_AVAILABLE": "Not available at the moment",
"LOOPEAT_HAS_RETURNS": "This task has zero waste returns",
"EDENRED_ELIGIBLE_AMOUNT": "Ticket Restaurant® eligible amount",
"EDENRED_COMPLEMENT": "Complement"
}
"EDENRED_COMPLEMENT": "Complement",
"BARCODE_TASK_ALREADY_ASSIGNED_TITLE": "Task already assigned",
"BARCODE_TASK_ALREADY_ASSIGNED_ANOTHER_MESSAGE": "This task has already been assigned to another user",
"BARCODE_TASK_ALREADY_ASSIGNED_SELF_MESSAGE": "This task has already been assigned to you",
"BARCODE_TASK_ALREADY_ASSIGNED_UNASSIGN": "Unassign",
"BARCODE_TASK_ALREADY_ASSIGNED_ASSIGN_TO_ME": "Assign to me",
"TASK_MULTIPLE_PACKAGES": "Multiple packages",
"X_PACKAGES": "{{count}} packages",
"NO_NEED_TO_SCAN_OTHERS": "No need to scan the others packages.",
"ADD_NOTE": "Add Note",
"REPORT_AN_INCIDENT": "Report an incident",
"UPDATE_PARCEL_DETAILS": "Update parcel details",
"PICTURES": "Photos",
"TASK_TODO": "To do",
"TASK_DOING": "In progress",
"TASK_FAILED": "Failed",
"TASK_DONE": "Done",
"TASK_CANCELLED": "Cancelled",
"ASK_TO_START_PICKUP_TITLE": "Pickup task",
"ASK_TO_START_PICKUP_MESSAGE": "The pickup task is not yet started."
}
}
21 changes: 20 additions & 1 deletion src/i18n/locales/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,25 @@
"CART_TIME_RANGE_CHANGED_MODAL_CHOOSE_RESTAURANT_ACTION": "Elige otro restaurante",
"ORDER__SHIPPED_AT__NOT_AVAILABLE": "El horario de entrega ya no está disponible",
"ORDER__SHIPPED_AT__EXPIRED": "El horario de entrega ya ha pasado",
"ORDER__SHIPPING_TIME_RANGE__NOT_AVAILABLE": "No esta disponible en estos momentos"
"ORDER__SHIPPING_TIME_RANGE__NOT_AVAILABLE": "No esta disponible en estos momentos",
"BARCODE_TASK_ALREADY_ASSIGNED_TITLE": "Tarea ya asignada",
"BARCODE_TASK_ALREADY_ASSIGNED_ANOTHER_MESSAGE": "Esta tarea ya ha sido asignada a otro usuario",
"BARCODE_TASK_ALREADY_ASSIGNED_SELF_MESSAGE": "Esta tarea ya te ha sido asignada",
"BARCODE_TASK_ALREADY_ASSIGNED_UNASSIGN": "Desasignar",
"BARCODE_TASK_ALREADY_ASSIGNED_ASSIGN_TO_ME": "Asignármela",
"TASK_MULTIPLE_PACKAGES": "Varios paquetes",
"X_PACKAGES": "{{count}} paquetes",
"NO_NEED_TO_SCAN_OTHERS": "No es necesario escanear los demás paquetes.",
"ADD_NOTE": "Agregar nota",
"REPORT_AN_INCIDENT": "Reportar un incidente",
"UPDATE_PARCEL_DETAILS": "Actualizar detalles del paquete",
"PICTURES": "Fotos",
"TASK_TODO": "Por hacer",
"TASK_DOING": "En progreso",
"TASK_FAILED": "Fallido",
"TASK_DONE": "Hecho",
"TASK_CANCELLED": "Cancelado",
"ASK_TO_START_PICKUP_TITLE": "Tarea de recogida",
"ASK_TO_START_PICKUP_MESSAGE": "La tarea de recogida aún no ha comenzado."
}
}
21 changes: 20 additions & 1 deletion src/i18n/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,25 @@
"ORDER__SHIPPED_AT__EXPIRED": "L'horaire de livraison est dépassé",
"ORDER__SHIPPING_TIME_RANGE__NOT_AVAILABLE": "Indisponible pour l'instant",
"EDENRED_ELIGIBLE_AMOUNT": "Montant éligible Ticket Restaurant®",
"EDENRED_COMPLEMENT": "Complément"
"EDENRED_COMPLEMENT": "Complément",
"BARCODE_TASK_ALREADY_ASSIGNED_TITLE": "Tâche déjà assignée",
"BARCODE_TASK_ALREADY_ASSIGNED_ANOTHER_MESSAGE": "Cette tâche a déjà été assignée à un autre utilisateur",
"BARCODE_TASK_ALREADY_ASSIGNED_SELF_MESSAGE": "Cette tâche vous a déjà été assignée",
"BARCODE_TASK_ALREADY_ASSIGNED_UNASSIGN": "Désassigner",
"BARCODE_TASK_ALREADY_ASSIGNED_ASSIGN_TO_ME": "M'assigner",
"TASK_MULTIPLE_PACKAGES": "Plusieurs colis",
"X_PACKAGES": "{{count}} colis",
"NO_NEED_TO_SCAN_OTHERS": "Pas besoin de scanner les autres colis.",
"ADD_NOTE": "Ajouter une note",
"REPORT_AN_INCIDENT": "Signaler un incident",
"UPDATE_PARCEL_DETAILS": "Mettre à jour les détails du colis",
"PICTURES": "Photos",
"TASK_TODO": "A faire",
"TASK_DOING": "En cours",
"TASK_FAILED": "Echouée",
"TASK_DONE": "Fait",
"TASK_CANCELLED": "Annulée",
"ASK_TO_START_PICKUP_TITLE": "Tâche de ramassage",
"ASK_TO_START_PICKUP_MESSAGE": "La tâche de ramassage n'est pas encore commencée."
}
}
21 changes: 20 additions & 1 deletion src/i18n/locales/it.json
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,25 @@
"MULTIPLE_SERVERS_IN_SAME_CITY_MODAL_TEXT": "C'è più di una cooperativa nella tua città, puoi navigare tra esse usando i tab o facendo swipe a sinistra o destra.",
"DO_NOT_SHOW_IT_AGAIN": "Non mostrare più",
"total_packages": "Totale: {{count}} pacchetto",
"total_packages_plural": "Totale: {{count}} pacchetti"
"total_packages_plural": "Totale: {{count}} pacchetti",
"BARCODE_TASK_ALREADY_ASSIGNED_TITLE": "Attività già assegnata",
"BARCODE_TASK_ALREADY_ASSIGNED_ANOTHER_MESSAGE": "Questa attività è già stata assegnata a un altro utente",
"BARCODE_TASK_ALREADY_ASSIGNED_SELF_MESSAGE": "Questa attività è già stata assegnata a te",
"BARCODE_TASK_ALREADY_ASSIGNED_UNASSIGN": "Rimuovi assegnazione",
"BARCODE_TASK_ALREADY_ASSIGNED_ASSIGN_TO_ME": "Assegna a me",
"TASK_MULTIPLE_PACKAGES": "Più pacchi",
"X_PACKAGES": "{{count}} pacchi",
"NO_NEED_TO_SCAN_OTHERS": "Non è necessario scansionare gli altri pacchi.",
"ADD_NOTE": "Aggiungi nota",
"REPORT_AN_INCIDENT": "Segnala un incidente",
"UPDATE_PARCEL_DETAILS": "Aggiorna i dettagli del pacco",
"PICTURES": "Foto",
"TASK_TODO": "Da fare",
"TASK_DOING": "In corso",
"TASK_FAILED": "Fallito",
"TASK_DONE": "Fatto",
"TASK_CANCELLED": "Annullato",
"ASK_TO_START_PICKUP_TITLE": "Attività di ritiro",
"ASK_TO_START_PICKUP_MESSAGE": "L'attività di ritiro non è ancora iniziata."
}
}
Loading
Loading