@acvl/mobile-components 0.0.0-stage → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +147 -2
- package/assets/fonts/Inter_400Regular.ttf +0 -0
- package/assets/fonts/Inter_500Medium.ttf +0 -0
- package/assets/fonts/Inter_600SemiBold.ttf +0 -0
- package/assets/fonts/Inter_700Bold.ttf +0 -0
- package/assets/fonts/Inter_800ExtraBold.ttf +0 -0
- package/assets/fonts/Inter_900Black.ttf +0 -0
- package/assets/fonts/OFL.txt +93 -0
- package/lib/module/camara/CampoDeFotos.js +124 -0
- package/lib/module/camara/CampoDeFotos.js.map +1 -0
- package/lib/module/camara/elegir.js +104 -0
- package/lib/module/camara/elegir.js.map +1 -0
- package/lib/module/camara/index.js +10 -0
- package/lib/module/camara/index.js.map +1 -0
- package/lib/module/camara/reducir.js +93 -0
- package/lib/module/camara/reducir.js.map +1 -0
- package/lib/module/componentes/AccionesDelEncabezado.js +212 -0
- package/lib/module/componentes/AccionesDelEncabezado.js.map +1 -0
- package/lib/module/componentes/BotonDeAvatar.js +55 -0
- package/lib/module/componentes/BotonDeAvatar.js.map +1 -0
- package/lib/module/componentes/BotonGrande.js +105 -0
- package/lib/module/componentes/BotonGrande.js.map +1 -0
- package/lib/module/componentes/CabeceraEmpujada.js +101 -0
- package/lib/module/componentes/CabeceraEmpujada.js.map +1 -0
- package/lib/module/componentes/Chip.js +77 -0
- package/lib/module/componentes/Chip.js.map +1 -0
- package/lib/module/componentes/ContenidoDePantalla.js +56 -0
- package/lib/module/componentes/ContenidoDePantalla.js.map +1 -0
- package/lib/module/componentes/EstadoVacio.js +65 -0
- package/lib/module/componentes/EstadoVacio.js.map +1 -0
- package/lib/module/componentes/Foto.js +106 -0
- package/lib/module/componentes/Foto.js.map +1 -0
- package/lib/module/componentes/IconoDeRenglon.js +44 -0
- package/lib/module/componentes/IconoDeRenglon.js.map +1 -0
- package/lib/module/componentes/Logo.js +44 -0
- package/lib/module/componentes/Logo.js.map +1 -0
- package/lib/module/componentes/Nota.js +51 -0
- package/lib/module/componentes/Nota.js.map +1 -0
- package/lib/module/componentes/OpcionesExcluyentes.js +69 -0
- package/lib/module/componentes/OpcionesExcluyentes.js.map +1 -0
- package/lib/module/componentes/PantallaDeError.js +84 -0
- package/lib/module/componentes/PantallaDeError.js.map +1 -0
- package/lib/module/componentes/Pestanas.js +87 -0
- package/lib/module/componentes/Pestanas.js.map +1 -0
- package/lib/module/componentes/Rotulo.js +28 -0
- package/lib/module/componentes/Rotulo.js.map +1 -0
- package/lib/module/componentes/RotuloDeSeccion.js +87 -0
- package/lib/module/componentes/RotuloDeSeccion.js.map +1 -0
- package/lib/module/componentes/Tarjeta.js +63 -0
- package/lib/module/componentes/Tarjeta.js.map +1 -0
- package/lib/module/componentes/TarjetaDeDatos.js +79 -0
- package/lib/module/componentes/TarjetaDeDatos.js.map +1 -0
- package/lib/module/componentes/TiraDeFotos.js +184 -0
- package/lib/module/componentes/TiraDeFotos.js.map +1 -0
- package/lib/module/componentes/VisorDeFoto.js +171 -0
- package/lib/module/componentes/VisorDeFoto.js.map +1 -0
- package/lib/module/componentes/logos/bola-blanca.png +0 -0
- package/lib/module/componentes/logos/bola.png +0 -0
- package/lib/module/componentes/logos/logo-blanco.png +0 -0
- package/lib/module/componentes/logos/logo.png +0 -0
- package/lib/module/dialogos/HostDeDialogos.js +170 -0
- package/lib/module/dialogos/HostDeDialogos.js.map +1 -0
- package/lib/module/dialogos/dialogos.js +112 -0
- package/lib/module/dialogos/dialogos.js.map +1 -0
- package/lib/module/formularios/CampoDeSeleccion.js +72 -0
- package/lib/module/formularios/CampoDeSeleccion.js.map +1 -0
- package/lib/module/formularios/SelectorDeOpcion.js +406 -0
- package/lib/module/formularios/SelectorDeOpcion.js.map +1 -0
- package/lib/module/formularios/TextoDeAccesoControlado.js +73 -0
- package/lib/module/formularios/TextoDeAccesoControlado.js.map +1 -0
- package/lib/module/formularios/campos.js +501 -0
- package/lib/module/formularios/campos.js.map +1 -0
- package/lib/module/formularios/controlados.js +199 -0
- package/lib/module/formularios/controlados.js.map +1 -0
- package/lib/module/formularios/errores.js +91 -0
- package/lib/module/formularios/errores.js.map +1 -0
- package/lib/module/formularios/guardado.js +78 -0
- package/lib/module/formularios/guardado.js.map +1 -0
- package/lib/module/formularios/useBusqueda.js +22 -0
- package/lib/module/formularios/useBusqueda.js.map +1 -0
- package/lib/module/fotos/elegir.js +105 -0
- package/lib/module/fotos/elegir.js.map +1 -0
- package/lib/module/fotos/index.js +9 -0
- package/lib/module/fotos/index.js.map +1 -0
- package/lib/module/hoja/HojaDeLista.js +211 -0
- package/lib/module/hoja/HojaDeLista.js.map +1 -0
- package/lib/module/hoja/HojaInferior.js +112 -0
- package/lib/module/hoja/HojaInferior.js.map +1 -0
- package/lib/module/hoja/index.js +8 -0
- package/lib/module/hoja/index.js.map +1 -0
- package/lib/module/hoja/useMasDeLaFicha.js +48 -0
- package/lib/module/hoja/useMasDeLaFicha.js.map +1 -0
- package/lib/module/index.js +67 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/listas/HojaDeFiltros.js +270 -0
- package/lib/module/listas/HojaDeFiltros.js.map +1 -0
- package/lib/module/listas/ListaPaginada.js +103 -0
- package/lib/module/listas/ListaPaginada.js.map +1 -0
- package/lib/module/listas/TarjetaDeslizable.js +118 -0
- package/lib/module/listas/TarjetaDeslizable.js.map +1 -0
- package/lib/module/listas/paginado.js +11 -0
- package/lib/module/listas/paginado.js.map +1 -0
- package/lib/module/listas/piezas.js +666 -0
- package/lib/module/listas/piezas.js.map +1 -0
- package/lib/module/navegacion/BarraDePestanas.js +162 -0
- package/lib/module/navegacion/BarraDePestanas.js.map +1 -0
- package/lib/module/navegacion/BotonDeBarra.js +58 -0
- package/lib/module/navegacion/BotonDeBarra.js.map +1 -0
- package/lib/module/navegacion/encabezado.js +103 -0
- package/lib/module/navegacion/encabezado.js.map +1 -0
- package/lib/module/pantallas/PantallaDeAcceso.js +195 -0
- package/lib/module/pantallas/PantallaDeAcceso.js.map +1 -0
- package/lib/module/pantallas/PantallaDeEdicion.js +121 -0
- package/lib/module/pantallas/PantallaDeEdicion.js.map +1 -0
- package/lib/module/pantallas/PantallaDeFicha.js +94 -0
- package/lib/module/pantallas/PantallaDeFicha.js.map +1 -0
- package/lib/module/pantallas/PantallaDeHistorial.js +328 -0
- package/lib/module/pantallas/PantallaDeHistorial.js.map +1 -0
- package/lib/module/pantallas/PantallaDePerfil.js +137 -0
- package/lib/module/pantallas/PantallaDePerfil.js.map +1 -0
- package/lib/module/tema/ProveedorDeTema.js +122 -0
- package/lib/module/tema/ProveedorDeTema.js.map +1 -0
- package/lib/module/tema/estilos.js +54 -0
- package/lib/module/tema/estilos.js.map +1 -0
- package/lib/module/tema/fuentes.js +28 -0
- package/lib/module/tema/fuentes.js.map +1 -0
- package/lib/module/tema/index.js +8 -0
- package/lib/module/tema/index.js.map +1 -0
- package/lib/module/tema/paleta.js +107 -0
- package/lib/module/tema/paleta.js.map +1 -0
- package/lib/module/tema/tipografia.js +32 -0
- package/lib/module/tema/tipografia.js.map +1 -0
- package/lib/module/texto.js +48 -0
- package/lib/module/texto.js.map +1 -0
- package/lib/typescript/src/camara/CampoDeFotos.d.ts +30 -0
- package/lib/typescript/src/camara/CampoDeFotos.d.ts.map +1 -0
- package/lib/typescript/src/camara/elegir.d.ts +47 -0
- package/lib/typescript/src/camara/elegir.d.ts.map +1 -0
- package/lib/typescript/src/camara/index.d.ts +4 -0
- package/lib/typescript/src/camara/index.d.ts.map +1 -0
- package/lib/typescript/src/camara/reducir.d.ts +32 -0
- package/lib/typescript/src/camara/reducir.d.ts.map +1 -0
- package/lib/typescript/src/componentes/AccionesDelEncabezado.d.ts +34 -0
- package/lib/typescript/src/componentes/AccionesDelEncabezado.d.ts.map +1 -0
- package/lib/typescript/src/componentes/BotonDeAvatar.d.ts +17 -0
- package/lib/typescript/src/componentes/BotonDeAvatar.d.ts.map +1 -0
- package/lib/typescript/src/componentes/BotonGrande.d.ts +26 -0
- package/lib/typescript/src/componentes/BotonGrande.d.ts.map +1 -0
- package/lib/typescript/src/componentes/CabeceraEmpujada.d.ts +18 -0
- package/lib/typescript/src/componentes/CabeceraEmpujada.d.ts.map +1 -0
- package/lib/typescript/src/componentes/Chip.d.ts +14 -0
- package/lib/typescript/src/componentes/Chip.d.ts.map +1 -0
- package/lib/typescript/src/componentes/ContenidoDePantalla.d.ts +17 -0
- package/lib/typescript/src/componentes/ContenidoDePantalla.d.ts.map +1 -0
- package/lib/typescript/src/componentes/EstadoVacio.d.ts +17 -0
- package/lib/typescript/src/componentes/EstadoVacio.d.ts.map +1 -0
- package/lib/typescript/src/componentes/Foto.d.ts +41 -0
- package/lib/typescript/src/componentes/Foto.d.ts.map +1 -0
- package/lib/typescript/src/componentes/IconoDeRenglon.d.ts +25 -0
- package/lib/typescript/src/componentes/IconoDeRenglon.d.ts.map +1 -0
- package/lib/typescript/src/componentes/Logo.d.ts +13 -0
- package/lib/typescript/src/componentes/Logo.d.ts.map +1 -0
- package/lib/typescript/src/componentes/Nota.d.ts +7 -0
- package/lib/typescript/src/componentes/Nota.d.ts.map +1 -0
- package/lib/typescript/src/componentes/OpcionesExcluyentes.d.ts +14 -0
- package/lib/typescript/src/componentes/OpcionesExcluyentes.d.ts.map +1 -0
- package/lib/typescript/src/componentes/PantallaDeError.d.ts +23 -0
- package/lib/typescript/src/componentes/PantallaDeError.d.ts.map +1 -0
- package/lib/typescript/src/componentes/Pestanas.d.ts +28 -0
- package/lib/typescript/src/componentes/Pestanas.d.ts.map +1 -0
- package/lib/typescript/src/componentes/Rotulo.d.ts +6 -0
- package/lib/typescript/src/componentes/Rotulo.d.ts.map +1 -0
- package/lib/typescript/src/componentes/RotuloDeSeccion.d.ts +20 -0
- package/lib/typescript/src/componentes/RotuloDeSeccion.d.ts.map +1 -0
- package/lib/typescript/src/componentes/Tarjeta.d.ts +25 -0
- package/lib/typescript/src/componentes/Tarjeta.d.ts.map +1 -0
- package/lib/typescript/src/componentes/TarjetaDeDatos.d.ts +13 -0
- package/lib/typescript/src/componentes/TarjetaDeDatos.d.ts.map +1 -0
- package/lib/typescript/src/componentes/TiraDeFotos.d.ts +36 -0
- package/lib/typescript/src/componentes/TiraDeFotos.d.ts.map +1 -0
- package/lib/typescript/src/componentes/VisorDeFoto.d.ts +23 -0
- package/lib/typescript/src/componentes/VisorDeFoto.d.ts.map +1 -0
- package/lib/typescript/src/dialogos/HostDeDialogos.d.ts +14 -0
- package/lib/typescript/src/dialogos/HostDeDialogos.d.ts.map +1 -0
- package/lib/typescript/src/dialogos/dialogos.d.ts +65 -0
- package/lib/typescript/src/dialogos/dialogos.d.ts.map +1 -0
- package/lib/typescript/src/formularios/CampoDeSeleccion.d.ts +24 -0
- package/lib/typescript/src/formularios/CampoDeSeleccion.d.ts.map +1 -0
- package/lib/typescript/src/formularios/SelectorDeOpcion.d.ts +59 -0
- package/lib/typescript/src/formularios/SelectorDeOpcion.d.ts.map +1 -0
- package/lib/typescript/src/formularios/TextoDeAccesoControlado.d.ts +18 -0
- package/lib/typescript/src/formularios/TextoDeAccesoControlado.d.ts.map +1 -0
- package/lib/typescript/src/formularios/campos.d.ts +113 -0
- package/lib/typescript/src/formularios/campos.d.ts.map +1 -0
- package/lib/typescript/src/formularios/controlados.d.ts +29 -0
- package/lib/typescript/src/formularios/controlados.d.ts.map +1 -0
- package/lib/typescript/src/formularios/errores.d.ts +36 -0
- package/lib/typescript/src/formularios/errores.d.ts.map +1 -0
- package/lib/typescript/src/formularios/guardado.d.ts +36 -0
- package/lib/typescript/src/formularios/guardado.d.ts.map +1 -0
- package/lib/typescript/src/formularios/useBusqueda.d.ts +7 -0
- package/lib/typescript/src/formularios/useBusqueda.d.ts.map +1 -0
- package/lib/typescript/src/fotos/elegir.d.ts +28 -0
- package/lib/typescript/src/fotos/elegir.d.ts.map +1 -0
- package/lib/typescript/src/fotos/index.d.ts +3 -0
- package/lib/typescript/src/fotos/index.d.ts.map +1 -0
- package/lib/typescript/src/hoja/HojaDeLista.d.ts +50 -0
- package/lib/typescript/src/hoja/HojaDeLista.d.ts.map +1 -0
- package/lib/typescript/src/hoja/HojaInferior.d.ts +21 -0
- package/lib/typescript/src/hoja/HojaInferior.d.ts.map +1 -0
- package/lib/typescript/src/hoja/index.d.ts +4 -0
- package/lib/typescript/src/hoja/index.d.ts.map +1 -0
- package/lib/typescript/src/hoja/useMasDeLaFicha.d.ts +27 -0
- package/lib/typescript/src/hoja/useMasDeLaFicha.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +46 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/listas/HojaDeFiltros.d.ts +76 -0
- package/lib/typescript/src/listas/HojaDeFiltros.d.ts.map +1 -0
- package/lib/typescript/src/listas/ListaPaginada.d.ts +39 -0
- package/lib/typescript/src/listas/ListaPaginada.d.ts.map +1 -0
- package/lib/typescript/src/listas/TarjetaDeslizable.d.ts +33 -0
- package/lib/typescript/src/listas/TarjetaDeslizable.d.ts.map +1 -0
- package/lib/typescript/src/listas/paginado.d.ts +16 -0
- package/lib/typescript/src/listas/paginado.d.ts.map +1 -0
- package/lib/typescript/src/listas/piezas.d.ts +157 -0
- package/lib/typescript/src/listas/piezas.d.ts.map +1 -0
- package/lib/typescript/src/navegacion/BarraDePestanas.d.ts +57 -0
- package/lib/typescript/src/navegacion/BarraDePestanas.d.ts.map +1 -0
- package/lib/typescript/src/navegacion/BotonDeBarra.d.ts +15 -0
- package/lib/typescript/src/navegacion/BotonDeBarra.d.ts.map +1 -0
- package/lib/typescript/src/navegacion/encabezado.d.ts +44 -0
- package/lib/typescript/src/navegacion/encabezado.d.ts.map +1 -0
- package/lib/typescript/src/pantallas/PantallaDeAcceso.d.ts +32 -0
- package/lib/typescript/src/pantallas/PantallaDeAcceso.d.ts.map +1 -0
- package/lib/typescript/src/pantallas/PantallaDeEdicion.d.ts +38 -0
- package/lib/typescript/src/pantallas/PantallaDeEdicion.d.ts.map +1 -0
- package/lib/typescript/src/pantallas/PantallaDeFicha.d.ts +35 -0
- package/lib/typescript/src/pantallas/PantallaDeFicha.d.ts.map +1 -0
- package/lib/typescript/src/pantallas/PantallaDeHistorial.d.ts +65 -0
- package/lib/typescript/src/pantallas/PantallaDeHistorial.d.ts.map +1 -0
- package/lib/typescript/src/pantallas/PantallaDePerfil.d.ts +19 -0
- package/lib/typescript/src/pantallas/PantallaDePerfil.d.ts.map +1 -0
- package/lib/typescript/src/tema/ProveedorDeTema.d.ts +32 -0
- package/lib/typescript/src/tema/ProveedorDeTema.d.ts.map +1 -0
- package/lib/typescript/src/tema/estilos.d.ts +31 -0
- package/lib/typescript/src/tema/estilos.d.ts.map +1 -0
- package/lib/typescript/src/tema/fuentes.d.ts +20 -0
- package/lib/typescript/src/tema/fuentes.d.ts.map +1 -0
- package/lib/typescript/src/tema/index.d.ts +6 -0
- package/lib/typescript/src/tema/index.d.ts.map +1 -0
- package/lib/typescript/src/tema/paleta.d.ts +86 -0
- package/lib/typescript/src/tema/paleta.d.ts.map +1 -0
- package/lib/typescript/src/tema/tipografia.d.ts +29 -0
- package/lib/typescript/src/tema/tipografia.d.ts.map +1 -0
- package/lib/typescript/src/texto.d.ts +16 -0
- package/lib/typescript/src/texto.d.ts.map +1 -0
- package/package.json +140 -5
- package/src/camara/CampoDeFotos.tsx +132 -0
- package/src/camara/elegir.ts +113 -0
- package/src/camara/index.ts +15 -0
- package/src/camara/reducir.ts +87 -0
- package/src/componentes/AccionesDelEncabezado.tsx +247 -0
- package/src/componentes/BotonDeAvatar.tsx +59 -0
- package/src/componentes/BotonGrande.tsx +100 -0
- package/src/componentes/CabeceraEmpujada.tsx +69 -0
- package/src/componentes/Chip.tsx +60 -0
- package/src/componentes/ContenidoDePantalla.tsx +53 -0
- package/src/componentes/EstadoVacio.tsx +45 -0
- package/src/componentes/Foto.tsx +92 -0
- package/src/componentes/IconoDeRenglon.tsx +42 -0
- package/src/componentes/Logo.tsx +34 -0
- package/src/componentes/Nota.tsx +37 -0
- package/src/componentes/OpcionesExcluyentes.tsx +62 -0
- package/src/componentes/PantallaDeError.tsx +76 -0
- package/src/componentes/Pestanas.tsx +86 -0
- package/src/componentes/Rotulo.tsx +21 -0
- package/src/componentes/RotuloDeSeccion.tsx +71 -0
- package/src/componentes/Tarjeta.tsx +58 -0
- package/src/componentes/TarjetaDeDatos.tsx +62 -0
- package/src/componentes/TiraDeFotos.tsx +189 -0
- package/src/componentes/VisorDeFoto.tsx +181 -0
- package/src/componentes/logos/bola-blanca.png +0 -0
- package/src/componentes/logos/bola.png +0 -0
- package/src/componentes/logos/logo-blanco.png +0 -0
- package/src/componentes/logos/logo.png +0 -0
- package/src/dialogos/HostDeDialogos.tsx +172 -0
- package/src/dialogos/dialogos.ts +148 -0
- package/src/formularios/CampoDeSeleccion.tsx +60 -0
- package/src/formularios/SelectorDeOpcion.tsx +407 -0
- package/src/formularios/TextoDeAccesoControlado.tsx +72 -0
- package/src/formularios/campos.tsx +497 -0
- package/src/formularios/controlados.tsx +200 -0
- package/src/formularios/errores.ts +105 -0
- package/src/formularios/guardado.ts +76 -0
- package/src/formularios/useBusqueda.ts +19 -0
- package/src/fotos/elegir.ts +116 -0
- package/src/fotos/index.ts +6 -0
- package/src/hoja/HojaDeLista.tsx +214 -0
- package/src/hoja/HojaInferior.tsx +103 -0
- package/src/hoja/index.ts +5 -0
- package/src/hoja/useMasDeLaFicha.tsx +63 -0
- package/src/index.ts +151 -0
- package/src/listas/HojaDeFiltros.tsx +286 -0
- package/src/listas/ListaPaginada.tsx +117 -0
- package/src/listas/TarjetaDeslizable.tsx +126 -0
- package/src/listas/paginado.ts +15 -0
- package/src/listas/piezas.tsx +670 -0
- package/src/navegacion/BarraDePestanas.tsx +198 -0
- package/src/navegacion/BotonDeBarra.tsx +61 -0
- package/src/navegacion/encabezado.tsx +88 -0
- package/src/pantallas/PantallaDeAcceso.tsx +184 -0
- package/src/pantallas/PantallaDeEdicion.tsx +116 -0
- package/src/pantallas/PantallaDeFicha.tsx +115 -0
- package/src/pantallas/PantallaDeHistorial.tsx +293 -0
- package/src/pantallas/PantallaDePerfil.tsx +126 -0
- package/src/tema/ProveedorDeTema.tsx +150 -0
- package/src/tema/estilos.ts +51 -0
- package/src/tema/fuentes.ts +20 -0
- package/src/tema/index.ts +10 -0
- package/src/tema/paleta.ts +147 -0
- package/src/tema/tipografia.ts +29 -0
- package/src/texto.tsx +44 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// Tomar una foto o elegirla de la galería para subirla. Contesta lo elegido ya listo para el
|
|
2
|
+
// `FormData` (reducido a 1920 px); qué se hace con él —subirlo en el momento, guardarlo para una
|
|
3
|
+
// cola— es de quien llama. Los documentos (un PDF) están en `fotos/`, que pide expo-document-picker.
|
|
4
|
+
import * as ImagePicker from "expo-image-picker";
|
|
5
|
+
import { avisar } from "../dialogos/dialogos";
|
|
6
|
+
import { reducirFoto } from "./reducir";
|
|
7
|
+
|
|
8
|
+
/** Lo elegido: dónde está en el teléfono, su nombre, su tipo y si es una foto. */
|
|
9
|
+
export interface ArchivoElegido {
|
|
10
|
+
uri: string;
|
|
11
|
+
nombre: string;
|
|
12
|
+
tipo: string;
|
|
13
|
+
esFoto: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Para qué se pide la cámara, dicho a quien la usa. */
|
|
17
|
+
export interface PermisoDeCamara {
|
|
18
|
+
/** «tomar las fotos de evidencia»: va en «Para …, activa la cámara en los ajustes». */
|
|
19
|
+
para: string;
|
|
20
|
+
/**
|
|
21
|
+
* Lo que se explica antes del diálogo del sistema la primera vez: por qué y cuándo se usa. Sin
|
|
22
|
+
* esto, va directo al del sistema.
|
|
23
|
+
*/
|
|
24
|
+
explicacion?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Lo que se hace con la foto **original** de la cámara antes de reducirla: guardarla en la galería
|
|
29
|
+
* del teléfono, por ejemplo, como respaldo de quien la tomó si la subida falla. Lo que falle aquí
|
|
30
|
+
* no detiene la foto.
|
|
31
|
+
*/
|
|
32
|
+
export type AlTomarFoto = (original: ImagePicker.ImagePickerAsset) => unknown;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* El permiso de la cámara. Si ya se negó para siempre, se dice dónde activarlo; si nunca se ha
|
|
36
|
+
* pedido, primero la explicación de la app (si hay) y luego el diálogo del sistema.
|
|
37
|
+
*/
|
|
38
|
+
export async function pedirPermisoDeCamara({
|
|
39
|
+
para,
|
|
40
|
+
explicacion,
|
|
41
|
+
}: PermisoDeCamara): Promise<boolean> {
|
|
42
|
+
const actual = await ImagePicker.getCameraPermissionsAsync();
|
|
43
|
+
if (actual.granted) return true;
|
|
44
|
+
if (!actual.canAskAgain) {
|
|
45
|
+
await avisar({
|
|
46
|
+
titulo: "Sin permiso de cámara",
|
|
47
|
+
mensaje: `Para ${para}, activa la cámara para la app en los ajustes del teléfono.`,
|
|
48
|
+
icono: "camera-off-outline",
|
|
49
|
+
});
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
if (explicacion) {
|
|
53
|
+
await avisar({
|
|
54
|
+
titulo: "La cámara",
|
|
55
|
+
mensaje: explicacion,
|
|
56
|
+
icono: "camera-outline",
|
|
57
|
+
cancelable: false,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
return (await ImagePicker.requestCameraPermissionsAsync()).granted;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Las imágenes elegidas, reducidas a 1920 px en JPEG (`reducirFoto`). Una por una y no con
|
|
65
|
+
* `Promise.all`: cada foto se decodifica completa en memoria, y veinte a la vez desde la galería
|
|
66
|
+
* pueden tumbar la app en un Android modesto.
|
|
67
|
+
*/
|
|
68
|
+
export async function deFotos(assets: ImagePicker.ImagePickerAsset[]): Promise<ArchivoElegido[]> {
|
|
69
|
+
const elegidos: ArchivoElegido[] = [];
|
|
70
|
+
for (const asset of assets) {
|
|
71
|
+
const reducida = await reducirFoto(asset);
|
|
72
|
+
elegidos.push({
|
|
73
|
+
uri: reducida.uri,
|
|
74
|
+
nombre: reducida.fileName ?? "foto.jpg",
|
|
75
|
+
tipo: reducida.mimeType ?? "image/jpeg",
|
|
76
|
+
esFoto: true,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
return elegidos;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Una foto con la cámara, con su permiso. Vacío si no hubo permiso o se canceló. */
|
|
83
|
+
export async function tomarFoto(
|
|
84
|
+
permiso: PermisoDeCamara,
|
|
85
|
+
{ alTomar }: { alTomar?: AlTomarFoto } = {},
|
|
86
|
+
): Promise<ArchivoElegido[]> {
|
|
87
|
+
if (!(await pedirPermisoDeCamara(permiso))) return [];
|
|
88
|
+
const resultado = await ImagePicker.launchCameraAsync({ mediaTypes: ["images"], quality: 1 });
|
|
89
|
+
if (resultado.canceled) return [];
|
|
90
|
+
if (alTomar) {
|
|
91
|
+
for (const original of resultado.assets) {
|
|
92
|
+
try {
|
|
93
|
+
await alTomar(original);
|
|
94
|
+
} catch (error) {
|
|
95
|
+
console.warn("No se pudo guardar la original de la foto.", error);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return deFotos(resultado.assets);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* De la galería. En Android el selector de fotos del sistema no pide permiso de almacenamiento,
|
|
104
|
+
* así que no hay explicación previa que dar.
|
|
105
|
+
*/
|
|
106
|
+
export async function elegirDeGaleria({ varias = false } = {}): Promise<ArchivoElegido[]> {
|
|
107
|
+
const resultado = await ImagePicker.launchImageLibraryAsync({
|
|
108
|
+
mediaTypes: ["images"],
|
|
109
|
+
allowsMultipleSelection: varias,
|
|
110
|
+
quality: 1,
|
|
111
|
+
});
|
|
112
|
+
return resultado.canceled ? [] : deFotos(resultado.assets);
|
|
113
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// `@acvl/mobile-components/camara`: tomar fotos o elegirlas de la galería, y el campo de fotos de un
|
|
2
|
+
// formulario. Va aparte de la entrada principal porque usa expo-image-picker y
|
|
3
|
+
// expo-image-manipulator, que sólo instala la app que sube fotos; y aparte de `fotos` porque ésa
|
|
4
|
+
// además elige documentos, con expo-document-picker.
|
|
5
|
+
export { CampoDeFotos, FotosControladas } from "./CampoDeFotos";
|
|
6
|
+
export {
|
|
7
|
+
deFotos,
|
|
8
|
+
elegirDeGaleria,
|
|
9
|
+
pedirPermisoDeCamara,
|
|
10
|
+
tomarFoto,
|
|
11
|
+
type AlTomarFoto,
|
|
12
|
+
type ArchivoElegido,
|
|
13
|
+
type PermisoDeCamara,
|
|
14
|
+
} from "./elegir";
|
|
15
|
+
export { LADO_MAXIMO, reducirFoto, reducirImagenAdjunta } from "./reducir";
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// Achicar las fotos en el teléfono antes de subirlas.
|
|
2
|
+
import { ImageManipulator, SaveFormat } from "expo-image-manipulator";
|
|
3
|
+
import type { ImagePickerAsset } from "expo-image-picker";
|
|
4
|
+
import { Image } from "react-native";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Lado más largo con el que se sube una foto. Es el mismo con el que el backend la guarda
|
|
8
|
+
* (`MAX_DIMENSION` en `core/shared/fields.py`): arriba de eso los píxeles viajan para que el
|
|
9
|
+
* servidor los tire.
|
|
10
|
+
*/
|
|
11
|
+
export const LADO_MAXIMO = 1920;
|
|
12
|
+
|
|
13
|
+
/** Calidad JPEG de la copia reducida; el backend recodifica a 82 al guardarla. */
|
|
14
|
+
const CALIDAD = 0.8;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* La foto con su lado más largo en `LADO_MAXIMO` y codificada en JPEG.
|
|
18
|
+
*
|
|
19
|
+
* Una foto de la cámara (12 MP o más) pesa de 2 a 5 MB, y se sube con la señal que haya en el
|
|
20
|
+
* camino o en el patio: reducida pesa unos cientos de KB, sale antes y no se pasa del tope
|
|
21
|
+
* de tamaño del servidor. El servidor no pierde nada, porque de todos modos la guarda a 1920 px.
|
|
22
|
+
*
|
|
23
|
+
* Falla en blando: si no se puede reducir, se guarda la original —pesa más, pero es mejor que
|
|
24
|
+
* perder la foto que se acaba de tomar—.
|
|
25
|
+
*/
|
|
26
|
+
export async function reducirFoto(asset: ImagePickerAsset): Promise<ImagePickerAsset> {
|
|
27
|
+
const { width, height } = asset;
|
|
28
|
+
const cabe = width > 0 && height > 0 && Math.max(width, height) <= LADO_MAXIMO;
|
|
29
|
+
if (cabe && asset.mimeType === "image/jpeg") return asset;
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
const contexto = ImageManipulator.manipulate(asset.uri);
|
|
33
|
+
if (!cabe) {
|
|
34
|
+
// Se da un solo lado y el otro sale de la proporción.
|
|
35
|
+
contexto.resize(width >= height ? { width: LADO_MAXIMO } : { height: LADO_MAXIMO });
|
|
36
|
+
}
|
|
37
|
+
const imagen = await contexto.renderAsync();
|
|
38
|
+
const reducida = await imagen.saveAsync({ compress: CALIDAD, format: SaveFormat.JPEG });
|
|
39
|
+
|
|
40
|
+
const base = (asset.fileName ?? reducida.uri.split("/").pop() ?? "foto").replace(
|
|
41
|
+
/\.[^.]+$/,
|
|
42
|
+
"",
|
|
43
|
+
);
|
|
44
|
+
return {
|
|
45
|
+
...asset,
|
|
46
|
+
uri: reducida.uri,
|
|
47
|
+
width: reducida.width,
|
|
48
|
+
height: reducida.height,
|
|
49
|
+
fileName: `${base}.jpg`,
|
|
50
|
+
mimeType: "image/jpeg",
|
|
51
|
+
fileSize: undefined,
|
|
52
|
+
};
|
|
53
|
+
} catch (error) {
|
|
54
|
+
console.warn("No se pudo reducir la foto; se sube la original.", error);
|
|
55
|
+
return asset;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Lo mismo para una imagen adjuntada como archivo (`DocumentPicker`), que no trae sus medidas: se
|
|
61
|
+
* leen del archivo y se reduce como una foto. Si no se pueden leer, se guarda la original.
|
|
62
|
+
*/
|
|
63
|
+
export async function reducirImagenAdjunta(archivo: {
|
|
64
|
+
uri: string;
|
|
65
|
+
nombre: string;
|
|
66
|
+
tipo: string;
|
|
67
|
+
}): Promise<{ uri: string; nombre: string; tipo: string }> {
|
|
68
|
+
try {
|
|
69
|
+
const { width, height } = await Image.getSize(archivo.uri);
|
|
70
|
+
const reducida = await reducirFoto({
|
|
71
|
+
uri: archivo.uri,
|
|
72
|
+
width,
|
|
73
|
+
height,
|
|
74
|
+
fileName: archivo.nombre,
|
|
75
|
+
mimeType: archivo.tipo,
|
|
76
|
+
type: "image",
|
|
77
|
+
});
|
|
78
|
+
return {
|
|
79
|
+
uri: reducida.uri,
|
|
80
|
+
nombre: reducida.fileName ?? archivo.nombre,
|
|
81
|
+
tipo: reducida.mimeType ?? archivo.tipo,
|
|
82
|
+
};
|
|
83
|
+
} catch (error) {
|
|
84
|
+
console.warn("No se pudieron leer las medidas de la imagen; se sube la original.", error);
|
|
85
|
+
return archivo;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import { Text } from "../texto";
|
|
2
|
+
import { crearEstilos, renglon, tipografia, usePaleta } from "../tema";
|
|
3
|
+
import MaterialCommunityIcons from "@expo/vector-icons/MaterialCommunityIcons";
|
|
4
|
+
import React from "react";
|
|
5
|
+
import {
|
|
6
|
+
ActivityIndicator,
|
|
7
|
+
BackHandler,
|
|
8
|
+
Pressable,
|
|
9
|
+
useWindowDimensions,
|
|
10
|
+
View,
|
|
11
|
+
type LayoutChangeEvent,
|
|
12
|
+
} from "react-native";
|
|
13
|
+
import { Menu } from "react-native-paper";
|
|
14
|
+
|
|
15
|
+
/** Lo que mide un botón y lo que se separan: con eso se cuenta cuántos caben. */
|
|
16
|
+
const LADO = 40;
|
|
17
|
+
const ENTRE = 8;
|
|
18
|
+
/** Los que se ven a lo más, aunque quepan más: más botones ya no se distinguen de un vistazo. */
|
|
19
|
+
const MAXIMO_A_LA_VISTA = 3;
|
|
20
|
+
|
|
21
|
+
export interface AccionDelEncabezado {
|
|
22
|
+
/** El texto del renglón del menú y lo que lee el lector de pantalla. */
|
|
23
|
+
etiqueta: string;
|
|
24
|
+
icono: React.ComponentProps<typeof MaterialCommunityIcons>["name"];
|
|
25
|
+
onPress: () => void;
|
|
26
|
+
disabled?: boolean;
|
|
27
|
+
/** Un indicador en vez del icono, y la acción bloqueada mientras corre. */
|
|
28
|
+
cargando?: boolean;
|
|
29
|
+
/** Para la que borra o cancela: en rojo, en el botón y en el menú. */
|
|
30
|
+
destructiva?: boolean;
|
|
31
|
+
/** Las fijadas son las últimas en irse al menú (Editar). */
|
|
32
|
+
fijada?: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Una cifra pendiente de la acción: los mensajes sin leer de un hilo. Va sobre su botón o, si
|
|
35
|
+
* la acción se fue al menú, en su renglón — y entonces los tres puntos la suman, porque una
|
|
36
|
+
* cifra escondida no avisa de nada.
|
|
37
|
+
*/
|
|
38
|
+
insignia?: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* La cifra sobre un botón: «9+» de diez para arriba, que más dígitos no caben en el círculo. Con la
|
|
43
|
+
* letra de la casa (`etiqueta`) y el círculo crece con la del sistema, como el de `RotuloDeSeccion`.
|
|
44
|
+
*/
|
|
45
|
+
const Insignia = ({ cuantas }: { cuantas: number }) => {
|
|
46
|
+
const styles = useStyles();
|
|
47
|
+
const { fontScale } = useWindowDimensions();
|
|
48
|
+
const alto = renglon(tipografia.etiqueta).lineHeight * fontScale;
|
|
49
|
+
return (
|
|
50
|
+
<View
|
|
51
|
+
style={[styles.insignia, { minWidth: alto, height: alto, borderRadius: alto / 2 }]}
|
|
52
|
+
pointerEvents="none"
|
|
53
|
+
>
|
|
54
|
+
<Text style={styles.insigniaTexto}>{cuantas > 9 ? "9+" : cuantas}</Text>
|
|
55
|
+
</View>
|
|
56
|
+
);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const Boton = ({
|
|
60
|
+
accion,
|
|
61
|
+
onPress,
|
|
62
|
+
}: {
|
|
63
|
+
accion: Pick<
|
|
64
|
+
AccionDelEncabezado,
|
|
65
|
+
"etiqueta" | "icono" | "disabled" | "cargando" | "destructiva" | "insignia"
|
|
66
|
+
>;
|
|
67
|
+
onPress: () => void;
|
|
68
|
+
}) => {
|
|
69
|
+
const c = usePaleta();
|
|
70
|
+
const styles = useStyles();
|
|
71
|
+
return (
|
|
72
|
+
<Pressable
|
|
73
|
+
onPress={onPress}
|
|
74
|
+
disabled={accion.disabled || accion.cargando}
|
|
75
|
+
accessibilityRole="button"
|
|
76
|
+
accessibilityLabel={accion.etiqueta}
|
|
77
|
+
hitSlop={4}
|
|
78
|
+
style={({ pressed }) => [
|
|
79
|
+
styles.boton,
|
|
80
|
+
accion.disabled && styles.apagado,
|
|
81
|
+
pressed && styles.presionado,
|
|
82
|
+
]}
|
|
83
|
+
>
|
|
84
|
+
{accion.cargando ? (
|
|
85
|
+
<ActivityIndicator size="small" color={c.primary} />
|
|
86
|
+
) : (
|
|
87
|
+
<MaterialCommunityIcons
|
|
88
|
+
name={accion.icono}
|
|
89
|
+
size={22}
|
|
90
|
+
color={accion.destructiva ? c.primary : c.secondaryDark}
|
|
91
|
+
/>
|
|
92
|
+
)}
|
|
93
|
+
{accion.insignia ? <Insignia cuantas={accion.insignia} /> : null}
|
|
94
|
+
</Pressable>
|
|
95
|
+
);
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Las acciones de una ficha en su encabezado: **las que quepan como botones y las demás en el menú
|
|
100
|
+
* de los tres puntos**. Es el `ResponsiveActions` de la app de acv-wms con la paleta de la casa.
|
|
101
|
+
*
|
|
102
|
+
* No mide los botones: mide el hueco que le deja el título (`onLayout`) y cuenta cuántos de
|
|
103
|
+
* `LADO` caben. Si caben todas, van todas; si no, una menos y el menú. Las `fijadas` son las
|
|
104
|
+
* últimas en irse, sin cambiar el orden en que se ven. Quien la pone le da al título
|
|
105
|
+
* `flexShrink: 1` y no `flex: 1`, para que le deje espacio.
|
|
106
|
+
*/
|
|
107
|
+
export default function AccionesDelEncabezado({ acciones }: { acciones: AccionDelEncabezado[] }) {
|
|
108
|
+
const c = usePaleta();
|
|
109
|
+
const styles = useStyles();
|
|
110
|
+
const [hueco, setHueco] = React.useState<number | null>(null);
|
|
111
|
+
const [menuAbierto, setMenuAbierto] = React.useState(false);
|
|
112
|
+
|
|
113
|
+
// Atrás con el menú abierto sólo lo cierra: si no, salía de la pantalla con el menú encima.
|
|
114
|
+
React.useEffect(() => {
|
|
115
|
+
if (!menuAbierto) return;
|
|
116
|
+
const sub = BackHandler.addEventListener("hardwareBackPress", () => {
|
|
117
|
+
setMenuAbierto(false);
|
|
118
|
+
return true;
|
|
119
|
+
});
|
|
120
|
+
return () => sub.remove();
|
|
121
|
+
}, [menuAbierto]);
|
|
122
|
+
|
|
123
|
+
const medir = (e: LayoutChangeEvent) => {
|
|
124
|
+
const ancho = e.nativeEvent.layout.width;
|
|
125
|
+
if (ancho === hueco) return;
|
|
126
|
+
setHueco(ancho);
|
|
127
|
+
setMenuAbierto(false);
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
if (!acciones.length) return null;
|
|
131
|
+
|
|
132
|
+
const caben = hueco === null ? 0 : Math.floor((hueco + ENTRE) / (LADO + ENTRE));
|
|
133
|
+
const todas = acciones.length <= Math.min(caben, MAXIMO_A_LA_VISTA);
|
|
134
|
+
const botones = todas ? acciones.length : Math.max(Math.min(caben - 1, MAXIMO_A_LA_VISTA), 0);
|
|
135
|
+
const aLaVista = new Set(
|
|
136
|
+
[...acciones]
|
|
137
|
+
.sort((a, b) => Number(Boolean(b.fijada)) - Number(Boolean(a.fijada)))
|
|
138
|
+
.slice(0, botones),
|
|
139
|
+
);
|
|
140
|
+
const visibles = acciones.filter((a) => aLaVista.has(a));
|
|
141
|
+
const enElMenu = acciones.filter((a) => !aLaVista.has(a));
|
|
142
|
+
const escondidas = enElMenu.reduce((suma, a) => suma + (a.insignia ?? 0), 0);
|
|
143
|
+
const cerrar = () => setMenuAbierto(false);
|
|
144
|
+
|
|
145
|
+
return (
|
|
146
|
+
<View style={styles.barra} onLayout={medir}>
|
|
147
|
+
{hueco !== null ? (
|
|
148
|
+
<>
|
|
149
|
+
{visibles.map((a) => (
|
|
150
|
+
<Boton key={a.etiqueta} accion={a} onPress={a.onPress} />
|
|
151
|
+
))}
|
|
152
|
+
{enElMenu.length ? (
|
|
153
|
+
<Menu
|
|
154
|
+
visible={menuAbierto}
|
|
155
|
+
onDismiss={cerrar}
|
|
156
|
+
anchorPosition="bottom"
|
|
157
|
+
// Edge-to-edge: sin esto Paper cuenta dos veces la barra de estado.
|
|
158
|
+
statusBarHeight={0}
|
|
159
|
+
contentStyle={styles.menu}
|
|
160
|
+
anchor={
|
|
161
|
+
<Boton
|
|
162
|
+
accion={{
|
|
163
|
+
etiqueta: "Más acciones",
|
|
164
|
+
icono: "dots-vertical",
|
|
165
|
+
insignia: escondidas,
|
|
166
|
+
}}
|
|
167
|
+
onPress={() => setMenuAbierto(true)}
|
|
168
|
+
/>
|
|
169
|
+
}
|
|
170
|
+
>
|
|
171
|
+
{enElMenu.map((a) => (
|
|
172
|
+
<Menu.Item
|
|
173
|
+
key={a.etiqueta}
|
|
174
|
+
title={
|
|
175
|
+
<Text
|
|
176
|
+
style={[styles.menuTexto, a.destructiva && styles.rojo]}
|
|
177
|
+
>
|
|
178
|
+
{a.etiqueta}
|
|
179
|
+
{a.insignia ? ` (${a.insignia})` : ""}
|
|
180
|
+
</Text>
|
|
181
|
+
}
|
|
182
|
+
disabled={a.disabled || a.cargando}
|
|
183
|
+
leadingIcon={() =>
|
|
184
|
+
a.cargando ? (
|
|
185
|
+
<ActivityIndicator size="small" color={c.primary} />
|
|
186
|
+
) : (
|
|
187
|
+
<MaterialCommunityIcons
|
|
188
|
+
name={a.icono}
|
|
189
|
+
size={22}
|
|
190
|
+
color={a.destructiva ? c.primary : c.secondaryDark}
|
|
191
|
+
/>
|
|
192
|
+
)
|
|
193
|
+
}
|
|
194
|
+
onPress={() => {
|
|
195
|
+
cerrar();
|
|
196
|
+
a.onPress();
|
|
197
|
+
}}
|
|
198
|
+
/>
|
|
199
|
+
))}
|
|
200
|
+
</Menu>
|
|
201
|
+
) : null}
|
|
202
|
+
</>
|
|
203
|
+
) : null}
|
|
204
|
+
</View>
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const useStyles = crearEstilos((c) => ({
|
|
209
|
+
barra: {
|
|
210
|
+
flexGrow: 1,
|
|
211
|
+
flexBasis: 0,
|
|
212
|
+
minWidth: LADO,
|
|
213
|
+
flexDirection: "row",
|
|
214
|
+
justifyContent: "flex-end",
|
|
215
|
+
gap: ENTRE,
|
|
216
|
+
alignSelf: "center",
|
|
217
|
+
},
|
|
218
|
+
boton: {
|
|
219
|
+
width: LADO,
|
|
220
|
+
height: LADO,
|
|
221
|
+
borderRadius: LADO / 2,
|
|
222
|
+
borderWidth: 1.5,
|
|
223
|
+
borderColor: c.secondaryLighter,
|
|
224
|
+
backgroundColor: c.superficieTenue,
|
|
225
|
+
alignItems: "center",
|
|
226
|
+
justifyContent: "center",
|
|
227
|
+
},
|
|
228
|
+
apagado: { opacity: 0.45 },
|
|
229
|
+
presionado: { opacity: 0.7 },
|
|
230
|
+
menu: { backgroundColor: c.superficie, borderRadius: 12 },
|
|
231
|
+
insignia: {
|
|
232
|
+
position: "absolute",
|
|
233
|
+
// Dentro del círculo y no colgada de su esquina: en Android lo que se sale del padre se
|
|
234
|
+
// recorta, y la cifra quedaba a medias.
|
|
235
|
+
top: 0,
|
|
236
|
+
right: 0,
|
|
237
|
+
paddingHorizontal: 4,
|
|
238
|
+
alignItems: "center",
|
|
239
|
+
justifyContent: "center",
|
|
240
|
+
backgroundColor: c.primary,
|
|
241
|
+
borderWidth: 2,
|
|
242
|
+
borderColor: c.superficie,
|
|
243
|
+
},
|
|
244
|
+
insigniaTexto: { ...renglon(tipografia.etiqueta), fontWeight: "800", color: c.sobrePrimario },
|
|
245
|
+
menuTexto: { fontSize: 17, fontWeight: "600" },
|
|
246
|
+
rojo: { color: c.primary },
|
|
247
|
+
}));
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Pressable, StyleSheet } from "react-native";
|
|
2
|
+
import { Avatar } from "react-native-paper";
|
|
3
|
+
import { tipografia } from "../tema";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* La inicial de cada parte, hasta dos: `inicialesDe(nombre, apellidos)` da «JP»; con sólo el nombre
|
|
7
|
+
* de la cuenta, su primera letra; sin nada, una inicial neutra.
|
|
8
|
+
*/
|
|
9
|
+
export const inicialesDe = (...partes: (string | null | undefined)[]): string =>
|
|
10
|
+
(
|
|
11
|
+
partes
|
|
12
|
+
.map((p) => (p ?? "").trim().charAt(0))
|
|
13
|
+
.filter(Boolean)
|
|
14
|
+
.slice(0, 2)
|
|
15
|
+
.join("") || "?"
|
|
16
|
+
).toUpperCase();
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* El avatar de quien entró, en el header de las pestañas: el acceso al perfil (y a cerrar sesión).
|
|
20
|
+
* Quien lo pone decide las iniciales y a dónde lleva.
|
|
21
|
+
*/
|
|
22
|
+
const BotonDeAvatar = ({
|
|
23
|
+
iniciales,
|
|
24
|
+
onPress,
|
|
25
|
+
etiqueta = "Perfil",
|
|
26
|
+
}: {
|
|
27
|
+
iniciales: string;
|
|
28
|
+
onPress: () => void;
|
|
29
|
+
/** Lo que lee el lector de pantalla. */
|
|
30
|
+
etiqueta?: string;
|
|
31
|
+
}) => (
|
|
32
|
+
<Pressable
|
|
33
|
+
onPress={onPress}
|
|
34
|
+
hitSlop={8}
|
|
35
|
+
accessibilityRole="button"
|
|
36
|
+
accessibilityLabel={etiqueta}
|
|
37
|
+
style={({ pressed }) => [styles.btn, pressed && { opacity: 0.6 }]}
|
|
38
|
+
>
|
|
39
|
+
<Avatar.Text
|
|
40
|
+
size={36}
|
|
41
|
+
label={iniciales.slice(0, 2).toUpperCase()}
|
|
42
|
+
// Blanca sobre el rojo en los dos modos: el tema oscuro de Paper la ponía oscura.
|
|
43
|
+
color="#ffffff"
|
|
44
|
+
labelStyle={styles.initial}
|
|
45
|
+
/>
|
|
46
|
+
</Pressable>
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const styles = StyleSheet.create({
|
|
50
|
+
btn: {
|
|
51
|
+
width: 40,
|
|
52
|
+
height: 40,
|
|
53
|
+
alignItems: "center",
|
|
54
|
+
justifyContent: "center",
|
|
55
|
+
},
|
|
56
|
+
initial: { fontSize: tipografia.etiqueta, fontWeight: "700", lineHeight: 20 },
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
export default BotonDeAvatar;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { crearEstilos, renglon, tipografia, usePaleta } from "../tema";
|
|
2
|
+
import IconoDeRenglon from "../componentes/IconoDeRenglon";
|
|
3
|
+
import { Text } from "../texto";
|
|
4
|
+
import MaterialCommunityIcons from "@expo/vector-icons/MaterialCommunityIcons";
|
|
5
|
+
import React from "react";
|
|
6
|
+
import { ActivityIndicator, Pressable, View } from "react-native";
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
children: string;
|
|
10
|
+
icono?: React.ComponentProps<typeof MaterialCommunityIcons>["name"];
|
|
11
|
+
onPress: () => void;
|
|
12
|
+
disabled?: boolean;
|
|
13
|
+
cargando?: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* `relleno` para la acción principal; `contorno` para las de apoyo; `destructivo` para la que
|
|
16
|
+
* borra o deshace algo (Deshacer, Descartar, Cerrar sesión), en rojo oscuro; `listo` para algo
|
|
17
|
+
* ya cumplido que todavía se puede abrir (la evidencia completa), en verde.
|
|
18
|
+
*/
|
|
19
|
+
modo?: "relleno" | "contorno" | "destructivo" | "listo";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* El botón de una acción del flujo (Iniciar, Completar, Reintentar): ancho completo, alto
|
|
24
|
+
* generoso y letra de subtítulo, para atinarle de pie y con la letra del teléfono al 180%.
|
|
25
|
+
*
|
|
26
|
+
* Es propio y no el `Button` de Paper porque ése corta su texto a un renglón: con la letra grande
|
|
27
|
+
* «Iniciar asignación desde donde estás» quedaba en «Iniciar asignac…». Aquí el texto parte en los
|
|
28
|
+
* renglones que haga falta y el botón crece con él.
|
|
29
|
+
*/
|
|
30
|
+
const BotonGrande = ({ children, icono, onPress, disabled, cargando, modo = "relleno" }: Props) => {
|
|
31
|
+
const c = usePaleta();
|
|
32
|
+
const styles = useStyles();
|
|
33
|
+
const inactivo = disabled || cargando;
|
|
34
|
+
// El destructivo es un relleno de otro color: misma tinta blanca, mismo apagado.
|
|
35
|
+
const relleno = modo !== "contorno";
|
|
36
|
+
const tinta = relleno ? c.sobrePrimario : c.primary;
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<Pressable
|
|
40
|
+
onPress={onPress}
|
|
41
|
+
disabled={inactivo}
|
|
42
|
+
accessibilityRole="button"
|
|
43
|
+
accessibilityState={{ disabled: !!inactivo, busy: !!cargando }}
|
|
44
|
+
style={({ pressed }) => [
|
|
45
|
+
styles.boton,
|
|
46
|
+
relleno
|
|
47
|
+
? modo === "destructivo"
|
|
48
|
+
? styles.destructivo
|
|
49
|
+
: modo === "listo"
|
|
50
|
+
? styles.listo
|
|
51
|
+
: styles.relleno
|
|
52
|
+
: styles.contorno,
|
|
53
|
+
inactivo && (relleno ? styles.rellenoInactivo : styles.contornoInactivo),
|
|
54
|
+
pressed && { opacity: 0.85 },
|
|
55
|
+
]}
|
|
56
|
+
>
|
|
57
|
+
<View style={styles.fila}>
|
|
58
|
+
{cargando ? (
|
|
59
|
+
<ActivityIndicator color={tinta} />
|
|
60
|
+
) : icono ? (
|
|
61
|
+
<IconoDeRenglon
|
|
62
|
+
name={icono}
|
|
63
|
+
fontSize={tipografia.subtitulo}
|
|
64
|
+
size={26}
|
|
65
|
+
color={inactivo ? c.secondaryLight : tinta}
|
|
66
|
+
/>
|
|
67
|
+
) : null}
|
|
68
|
+
<Text style={[styles.texto, { color: inactivo ? c.secondaryLight : tinta }]}>
|
|
69
|
+
{children}
|
|
70
|
+
</Text>
|
|
71
|
+
</View>
|
|
72
|
+
</Pressable>
|
|
73
|
+
);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const useStyles = crearEstilos((c) => ({
|
|
77
|
+
boton: {
|
|
78
|
+
minHeight: 64,
|
|
79
|
+
borderRadius: 14,
|
|
80
|
+
paddingVertical: 14,
|
|
81
|
+
paddingHorizontal: 18,
|
|
82
|
+
justifyContent: "center",
|
|
83
|
+
},
|
|
84
|
+
relleno: { backgroundColor: c.primary },
|
|
85
|
+
destructivo: { backgroundColor: c.primaryDark },
|
|
86
|
+
// Un relleno con letra blanca: el verde oscuro se lee igual en los dos modos.
|
|
87
|
+
listo: { backgroundColor: "#2e7d32" },
|
|
88
|
+
rellenoInactivo: { backgroundColor: c.secondaryLighter },
|
|
89
|
+
contorno: { borderWidth: 2, borderColor: c.primary, backgroundColor: c.superficie },
|
|
90
|
+
contornoInactivo: { borderColor: c.secondaryLighter },
|
|
91
|
+
fila: { flexDirection: "row", alignItems: "flex-start", justifyContent: "center", gap: 10 },
|
|
92
|
+
texto: {
|
|
93
|
+
flexShrink: 1,
|
|
94
|
+
...renglon(tipografia.subtitulo),
|
|
95
|
+
fontWeight: "800",
|
|
96
|
+
textAlign: "center",
|
|
97
|
+
},
|
|
98
|
+
}));
|
|
99
|
+
|
|
100
|
+
export default BotonGrande;
|