Source: input/QrOqt.js

import { createContext, useCallback, useContext, useMemo, useRef, useState } from "react"
import { useDispatch, useSelector } from "react-redux";
import { appActions } from "../../redux";
import { booleanString, emptyObject, FormContext } from "../../_core";
import beepFX from './QrBeep.aac';
import objectPath from "object-path";

const CryptoJS = require("crypto-js");
/**
 * Componente que renderiza un mati-button
 * @name Reactor.Components.Input.QrOqt
 * @param {string} open nombre de variable booleana que indica si esta abierto o cerrado el lector QR
 * @param {string} variable nombre de variable que va a contener el resultado
 * @param {string} context 
 * @param {string} verify 
 * @param {string} [list = "false"] indica si tiene que leer varios codigos
 * @param {string} list_count
 * @param {string} [debug = "false"]
 * @param {string} [decode = "true"]
 * @class 
 * @example 
<qr open="qr" variable="usuario" context="login" decode="false" auto_submit="true" >
  <qr_offcanvas options='{"style": {}}'>
    <div class="d-flex flex-column h-100" style="background-color:black;color:white">
      <div class="mt-2 pt-2 pb-3 mx-2 text-center">
        <div class="titulo-camara">Enfoque el código del DNI</div>
      </div>
      <div style="max-height: calc(100vh - 210px) ; overflow: hidden; width: 100vw">  
        <qr_cam options='{
          "width":"100%", 
          "height": "-",
          "formats":["PDF_417", "QR_CODE"]
        }'></qr_cam>
      </div>
      <qr_torch_on>
        <div class="d-flex justify-content-center mt-2 pt-2 mx-2">
          <div class="align-self-center font-14">
            Encender
          </div>
          <div class="align-self-center ps-1 mt-n1">
            <img src="images/light-on.png" width="24"/>
          </div>
        </div>
      </qr_torch_on>
      <qr_torch_off>
        <div class="d-flex justify-content-center mt-2 pt-2 mx-2">
          <div class="align-self-center font-14">
            Apagar
          </div>
          <div class="align-self-center ps-1 mt-n1">
            <img src="images/light-off.png" width="26"/>
          </div>
        </div>
      </qr_torch_off>
      <qr_close>
        <div id="footer-bar" class="footer-bar-4 footer-bar-attached iosTabBar mx-2">
          <div class="d-flex w-100">
            <div class="align-self-center w-100">
              <a class="btn btn-full bg-danger shadow-bg shadow-bg-s font-14 text-button" href="x">
                <img src="images/previous.png" width="30">
                VOLVER
              </a>
            </div>
          </div>
        </div>                              
      </qr_close>
      <div>
        <qr_error>Error: código inválido.</qr_error>
      </div>
    </div>
  </qr_offcanvas>
  <qr_open>
    <div class="mt-4 mx-4 text-center">
      <div class="btn btn-full gradient-highlight py-1 mb-2">
        <img src="images/dni.png" width="40px">
        <span class="ps-2">Ingrese con su DNI</span>
      </div>
    </div>
  </qr_open>
</qr>
*/

export const QrOqt = ({children, auto_submit = "false", open, variable, context, verify, list = "false", list_count, debug = "false", decode = "true", beep = "true", beepData}) => {

  const dispatch = useDispatch();
  const key = useSelector(state => state.app.key);
  const variablesState = useSelector(state => state.app.variables[context]) || emptyObject;
  const openState = useMemo(() => objectPath.get(variablesState, open), [context, open, variablesState]);
  const audioRef = useRef();
  const formContext = useContext(FormContext);

  const handleClose = useCallback(() => {
    dispatch(appActions.setVariable(open, false, context));
  }, [open, context]);

  const handleOpen = useCallback(() => {
    dispatch(appActions.setVariable(open, true, context));
  }, [open, context]);

  const decrypt = useCallback((ciphertext) => {
    var bytes = CryptoJS.AES.decrypt(ciphertext, key);
    return bytes.toString(CryptoJS.enc.Utf8);
  }, [key])

  const handleReadDecode = useCallback((value, image) => {
    if(booleanString(list)){
      dispatch(appActions.setVariable(variable, value.map(item => decrypt(item)), context));
    }else{
      dispatch(appActions.setVariable(variable, decrypt(value), context));
    }      
    dispatch(appActions.setVariable(`${variable}_image`, image, context));
    audioRef.current?.play();
    if(booleanString(auto_submit)){
      setTimeout(() => {
        formContext.submit();
      }, 100);
    }
  }, [list, variable, context, auto_submit])

  const handleRead = useCallback((value, image) => {
    //console.log({value, image});
    dispatch(appActions.setVariable(variable, value, context));
    dispatch(appActions.setVariable(`${variable}_image`, image, context));
    audioRef.current?.play();
    if(booleanString(auto_submit)){
      setTimeout(() => {
        formContext.submit();
      }, 100);
    }
  }, [variable, context, auto_submit])

  const qrDF = useQrDefaultContext({
    openState, 
    errorState: useState(false), 
    torchState: useState(false), 
    valueState: useState([]), 
    imagesState: useState([]),
    setClose: handleClose, 
    setOpen: handleOpen, 
    onRead: booleanString(decode) ? handleReadDecode : handleRead, 
    verify,
    list: booleanString(list),
    listCount: parseInt(list_count),
    currentValue: objectPath.get(variablesState, variable),
  });
  return (  
    <QrContext.Provider value={qrDF}>
      {children}
      {booleanString(beep) &&
        <audio ref={audioRef} src={beepData || beepFX} />
      }
    </QrContext.Provider>
  )
}

const useQrDefaultContext = ({openState = false, currentValue, errorState, torchState, valueState, imagesState, setOpen, setClose, onRead, verify, list = false, listCount = 0}) => {
  const [error, setShowError] = errorState || [false, () => {}];
  const [torch, setTorch] = torchState || [false, () => {}];
  const [value, setValue] = valueState || [null, () => {}];
  const [images, setImages] = imagesState || [null, () => {}];

  const reset = () => {
    setShowError(false);
    setTorch(false);
    setValue([]);
  }

  const showError = useCallback(() => {
    setShowError(true);
  }, []);

  const hideError = useCallback(() => {
    setShowError(false);
  }, []);

  const torchOn = () => {
    setTorch(true);
  }

  const torchOff = () => {
    setTorch(false);
  }

  const read = useCallback((content, image) => {
    if(list){
      let newValue = value || [];
      if(!newValue.some(item => item === content)){
        const newImages = [...images, image];
        newValue.push(content);
        setValue(newValue);
        setImages(newImages);
        onRead(newValue, newImages);
        if(newValue.length === listCount){
          setClose();
        }
      }
      return;
    }
    if ((verify && verify===content) || !verify) {
      onRead(content, image);
      setClose();
      hideError();
    }else{
      showError();
    }
  }, [list, onRead, verify, onRead, hideError, showError, setClose])

  return {close: setClose, open: setOpen, openState, error, showError, hideError, read, torch, torchOn, torchOff, currentValue, list, reset};
}

export const QrContext = createContext();