@acvl/mobile-components 0.0.5 → 0.0.6
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 +14 -0
- package/lib/module/camara/CampoDeFotos.js +4 -2
- package/lib/module/camara/CampoDeFotos.js.map +1 -1
- package/lib/module/camara/elegir.js +4 -3
- package/lib/module/camara/elegir.js.map +1 -1
- package/lib/module/componentes/AccionesDelEncabezado.js +3 -1
- package/lib/module/componentes/AccionesDelEncabezado.js.map +1 -1
- package/lib/module/componentes/BotonDeAvatar.js +24 -20
- package/lib/module/componentes/BotonDeAvatar.js.map +1 -1
- package/lib/module/componentes/CabeceraEmpujada.js +3 -1
- package/lib/module/componentes/CabeceraEmpujada.js.map +1 -1
- package/lib/module/componentes/PantallaDeError.js +6 -4
- package/lib/module/componentes/PantallaDeError.js.map +1 -1
- package/lib/module/componentes/TiraDeFotos.js +4 -2
- package/lib/module/componentes/TiraDeFotos.js.map +1 -1
- package/lib/module/componentes/VisorDeFoto.js +6 -4
- package/lib/module/componentes/VisorDeFoto.js.map +1 -1
- package/lib/module/dialogos/HostDeDialogos.js +3 -1
- package/lib/module/dialogos/HostDeDialogos.js.map +1 -1
- package/lib/module/dialogos/dialogos.js +6 -4
- package/lib/module/dialogos/dialogos.js.map +1 -1
- package/lib/module/formularios/SelectorDeOpcion.js +13 -7
- package/lib/module/formularios/SelectorDeOpcion.js.map +1 -1
- package/lib/module/formularios/campos.js +9 -4
- package/lib/module/formularios/campos.js.map +1 -1
- package/lib/module/formularios/guardado.js +4 -2
- package/lib/module/formularios/guardado.js.map +1 -1
- package/lib/module/fotos/elegir.js +10 -8
- package/lib/module/fotos/elegir.js.map +1 -1
- package/lib/module/hoja/HojaDeLista.js +5 -3
- package/lib/module/hoja/HojaDeLista.js.map +1 -1
- package/lib/module/hoja/useMasDeLaFicha.js +3 -1
- package/lib/module/hoja/useMasDeLaFicha.js.map +1 -1
- package/lib/module/idioma/ProveedorDeIdioma.js +54 -0
- package/lib/module/idioma/ProveedorDeIdioma.js.map +1 -0
- package/lib/module/idioma/index.js +4 -0
- package/lib/module/idioma/index.js.map +1 -0
- package/lib/module/idioma/textos.js +237 -0
- package/lib/module/idioma/textos.js.map +1 -0
- package/lib/module/index.js +4 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/listas/HojaDeFiltros.js +11 -8
- package/lib/module/listas/HojaDeFiltros.js.map +1 -1
- package/lib/module/listas/ListaPaginada.js +6 -4
- package/lib/module/listas/ListaPaginada.js.map +1 -1
- package/lib/module/listas/piezas.js +13 -7
- package/lib/module/listas/piezas.js.map +1 -1
- package/lib/module/pantallas/PantallaDeAcceso.js +6 -4
- package/lib/module/pantallas/PantallaDeAcceso.js.map +1 -1
- package/lib/module/pantallas/PantallaDeEdicion.js +9 -6
- package/lib/module/pantallas/PantallaDeEdicion.js.map +1 -1
- package/lib/module/pantallas/PantallaDeFicha.js +7 -5
- package/lib/module/pantallas/PantallaDeFicha.js.map +1 -1
- package/lib/module/pantallas/PantallaDeHistorial.js +18 -14
- package/lib/module/pantallas/PantallaDeHistorial.js.map +1 -1
- package/lib/module/pantallas/PantallaDePerfil.js +12 -10
- package/lib/module/pantallas/PantallaDePerfil.js.map +1 -1
- package/lib/typescript/src/camara/CampoDeFotos.d.ts.map +1 -1
- package/lib/typescript/src/camara/elegir.d.ts.map +1 -1
- package/lib/typescript/src/componentes/AccionesDelEncabezado.d.ts.map +1 -1
- package/lib/typescript/src/componentes/BotonDeAvatar.d.ts.map +1 -1
- package/lib/typescript/src/componentes/CabeceraEmpujada.d.ts.map +1 -1
- package/lib/typescript/src/componentes/PantallaDeError.d.ts.map +1 -1
- package/lib/typescript/src/componentes/TiraDeFotos.d.ts.map +1 -1
- package/lib/typescript/src/componentes/VisorDeFoto.d.ts.map +1 -1
- package/lib/typescript/src/dialogos/HostDeDialogos.d.ts.map +1 -1
- package/lib/typescript/src/dialogos/dialogos.d.ts.map +1 -1
- package/lib/typescript/src/formularios/SelectorDeOpcion.d.ts.map +1 -1
- package/lib/typescript/src/formularios/campos.d.ts.map +1 -1
- package/lib/typescript/src/formularios/guardado.d.ts.map +1 -1
- package/lib/typescript/src/fotos/elegir.d.ts.map +1 -1
- package/lib/typescript/src/hoja/HojaDeLista.d.ts.map +1 -1
- package/lib/typescript/src/hoja/useMasDeLaFicha.d.ts.map +1 -1
- package/lib/typescript/src/idioma/ProveedorDeIdioma.d.ts +16 -0
- package/lib/typescript/src/idioma/ProveedorDeIdioma.d.ts.map +1 -0
- package/lib/typescript/src/idioma/index.d.ts +3 -0
- package/lib/typescript/src/idioma/index.d.ts.map +1 -0
- package/lib/typescript/src/idioma/textos.d.ts +124 -0
- package/lib/typescript/src/idioma/textos.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +1 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/listas/HojaDeFiltros.d.ts.map +1 -1
- package/lib/typescript/src/listas/ListaPaginada.d.ts.map +1 -1
- package/lib/typescript/src/listas/piezas.d.ts.map +1 -1
- package/lib/typescript/src/pantallas/PantallaDeAcceso.d.ts.map +1 -1
- package/lib/typescript/src/pantallas/PantallaDeEdicion.d.ts.map +1 -1
- package/lib/typescript/src/pantallas/PantallaDeFicha.d.ts.map +1 -1
- package/lib/typescript/src/pantallas/PantallaDeHistorial.d.ts.map +1 -1
- package/lib/typescript/src/pantallas/PantallaDePerfil.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/camara/CampoDeFotos.tsx +4 -2
- package/src/camara/elegir.ts +4 -3
- package/src/componentes/AccionesDelEncabezado.tsx +3 -1
- package/src/componentes/BotonDeAvatar.tsx +22 -18
- package/src/componentes/CabeceraEmpujada.tsx +3 -1
- package/src/componentes/PantallaDeError.tsx +6 -4
- package/src/componentes/TiraDeFotos.tsx +4 -2
- package/src/componentes/VisorDeFoto.tsx +7 -7
- package/src/dialogos/HostDeDialogos.tsx +3 -1
- package/src/dialogos/dialogos.ts +15 -4
- package/src/formularios/SelectorDeOpcion.tsx +22 -8
- package/src/formularios/campos.tsx +9 -4
- package/src/formularios/guardado.ts +4 -2
- package/src/fotos/elegir.ts +14 -10
- package/src/hoja/HojaDeLista.tsx +5 -5
- package/src/hoja/useMasDeLaFicha.tsx +3 -1
- package/src/idioma/ProveedorDeIdioma.tsx +51 -0
- package/src/idioma/index.ts +7 -0
- package/src/idioma/textos.ts +246 -0
- package/src/index.ts +11 -0
- package/src/listas/HojaDeFiltros.tsx +10 -9
- package/src/listas/ListaPaginada.tsx +6 -6
- package/src/listas/piezas.tsx +17 -10
- package/src/pantallas/PantallaDeAcceso.tsx +6 -4
- package/src/pantallas/PantallaDeEdicion.tsx +13 -6
- package/src/pantallas/PantallaDeFicha.tsx +7 -5
- package/src/pantallas/PantallaDeHistorial.tsx +22 -16
- package/src/pantallas/PantallaDePerfil.tsx +12 -10
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
type ArchivoElegido,
|
|
14
14
|
type PermisoDeCamara,
|
|
15
15
|
} from "./elegir";
|
|
16
|
+
import { useTextos } from "../idioma";
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* Una foto que ya está en el servidor (la de una captura que se vuelve a abrir): se enseña con su
|
|
@@ -68,6 +69,7 @@ export function CampoDeFotos({
|
|
|
68
69
|
disabled?: boolean;
|
|
69
70
|
}) {
|
|
70
71
|
const styles = useStyles();
|
|
72
|
+
const t = useTextos();
|
|
71
73
|
// Reducir una foto tarda (se decodifica completa): el botón gira mientras tanto.
|
|
72
74
|
const [eligiendo, setEligiendo] = React.useState<"camara" | "galeria" | null>(null);
|
|
73
75
|
|
|
@@ -116,7 +118,7 @@ export function CampoDeFotos({
|
|
|
116
118
|
disabled={disabled || eligiendo !== null}
|
|
117
119
|
onPress={() => void agregar("camara")}
|
|
118
120
|
>
|
|
119
|
-
|
|
121
|
+
{t.fotos.tomarFoto}
|
|
120
122
|
</BotonGrande>
|
|
121
123
|
<BotonGrande
|
|
122
124
|
icono="image-outline"
|
|
@@ -125,7 +127,7 @@ export function CampoDeFotos({
|
|
|
125
127
|
disabled={disabled || eligiendo !== null}
|
|
126
128
|
onPress={() => void agregar("galeria")}
|
|
127
129
|
>
|
|
128
|
-
|
|
130
|
+
{t.fotos.deLaGaleria}
|
|
129
131
|
</BotonGrande>
|
|
130
132
|
</View>
|
|
131
133
|
</View>
|
package/src/camara/elegir.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import * as ImagePicker from "expo-image-picker";
|
|
5
5
|
import { avisar } from "../dialogos/dialogos";
|
|
6
6
|
import { reducirFoto } from "./reducir";
|
|
7
|
+
import { textosActuales } from "../idioma";
|
|
7
8
|
|
|
8
9
|
/** Lo elegido: dónde está en el teléfono, su nombre, su tipo y si es una foto. */
|
|
9
10
|
export interface ArchivoElegido {
|
|
@@ -43,15 +44,15 @@ export async function pedirPermisoDeCamara({
|
|
|
43
44
|
if (actual.granted) return true;
|
|
44
45
|
if (!actual.canAskAgain) {
|
|
45
46
|
await avisar({
|
|
46
|
-
titulo:
|
|
47
|
-
mensaje:
|
|
47
|
+
titulo: textosActuales().fotos.sinPermisoDeCamara,
|
|
48
|
+
mensaje: textosActuales().fotos.activaLaCamara(para),
|
|
48
49
|
icono: "camera-off-outline",
|
|
49
50
|
});
|
|
50
51
|
return false;
|
|
51
52
|
}
|
|
52
53
|
if (explicacion) {
|
|
53
54
|
await avisar({
|
|
54
|
-
titulo:
|
|
55
|
+
titulo: textosActuales().fotos.laCamara,
|
|
55
56
|
mensaje: explicacion,
|
|
56
57
|
icono: "camera-outline",
|
|
57
58
|
cancelable: false,
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
type LayoutChangeEvent,
|
|
12
12
|
} from "react-native";
|
|
13
13
|
import { Menu } from "react-native-paper";
|
|
14
|
+
import { useTextos } from "../idioma";
|
|
14
15
|
|
|
15
16
|
/** Lo que mide un botón y lo que se separan: con eso se cuenta cuántos caben. */
|
|
16
17
|
const LADO = 40;
|
|
@@ -106,6 +107,7 @@ const Boton = ({
|
|
|
106
107
|
*/
|
|
107
108
|
export default function AccionesDelEncabezado({ acciones }: { acciones: AccionDelEncabezado[] }) {
|
|
108
109
|
const c = usePaleta();
|
|
110
|
+
const t = useTextos();
|
|
109
111
|
const styles = useStyles();
|
|
110
112
|
const [hueco, setHueco] = React.useState<number | null>(null);
|
|
111
113
|
const [menuAbierto, setMenuAbierto] = React.useState(false);
|
|
@@ -160,7 +162,7 @@ export default function AccionesDelEncabezado({ acciones }: { acciones: AccionDe
|
|
|
160
162
|
anchor={
|
|
161
163
|
<Boton
|
|
162
164
|
accion={{
|
|
163
|
-
etiqueta:
|
|
165
|
+
etiqueta: t.listas.masAcciones,
|
|
164
166
|
icono: "dots-vertical",
|
|
165
167
|
insignia: escondidas,
|
|
166
168
|
}}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Pressable, StyleSheet } from "react-native";
|
|
2
2
|
import { Avatar } from "react-native-paper";
|
|
3
3
|
import { tipografia } from "../tema";
|
|
4
|
+
import { useTextos } from "../idioma";
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* La inicial de cada parte, hasta dos: `inicialesDe(nombre, apellidos)` da «JP»; con sólo el nombre
|
|
@@ -22,29 +23,32 @@ export const inicialesDe = (...partes: (string | null | undefined)[]): string =>
|
|
|
22
23
|
const BotonDeAvatar = ({
|
|
23
24
|
iniciales,
|
|
24
25
|
onPress,
|
|
25
|
-
etiqueta
|
|
26
|
+
etiqueta,
|
|
26
27
|
}: {
|
|
27
28
|
iniciales: string;
|
|
28
29
|
onPress: () => void;
|
|
29
30
|
/** Lo que lee el lector de pantalla. */
|
|
30
31
|
etiqueta?: string;
|
|
31
|
-
}) =>
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
32
|
+
}) => {
|
|
33
|
+
const t = useTextos();
|
|
34
|
+
return (
|
|
35
|
+
<Pressable
|
|
36
|
+
onPress={onPress}
|
|
37
|
+
hitSlop={8}
|
|
38
|
+
accessibilityRole="button"
|
|
39
|
+
accessibilityLabel={etiqueta ?? t.perfil.titulo}
|
|
40
|
+
style={({ pressed }) => [styles.btn, pressed && { opacity: 0.6 }]}
|
|
41
|
+
>
|
|
42
|
+
<Avatar.Text
|
|
43
|
+
size={36}
|
|
44
|
+
label={iniciales.slice(0, 2).toUpperCase()}
|
|
45
|
+
// Blanca sobre el rojo en los dos modos: el tema oscuro de Paper la ponía oscura.
|
|
46
|
+
color="#ffffff"
|
|
47
|
+
labelStyle={styles.initial}
|
|
48
|
+
/>
|
|
49
|
+
</Pressable>
|
|
50
|
+
);
|
|
51
|
+
};
|
|
48
52
|
|
|
49
53
|
const styles = StyleSheet.create({
|
|
50
54
|
btn: {
|
|
@@ -8,6 +8,7 @@ import { router } from "expo-router";
|
|
|
8
8
|
import type React from "react";
|
|
9
9
|
import { TouchableOpacity, View } from "react-native";
|
|
10
10
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
11
|
+
import { useTextos } from "../idioma";
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* La cabecera de una pantalla empujada encima de las pestañas: atrás, el rótulo, el título y, si
|
|
@@ -31,10 +32,11 @@ const CabeceraEmpujada = ({
|
|
|
31
32
|
const c = usePaleta();
|
|
32
33
|
const styles = useStyles();
|
|
33
34
|
const insets = useSafeAreaInsets();
|
|
35
|
+
const t = useTextos();
|
|
34
36
|
return (
|
|
35
37
|
<View style={[styles.hdr, { paddingTop: insets.top + 10 }]}>
|
|
36
38
|
<View style={styles.hdrRow}>
|
|
37
|
-
<TouchableOpacity onPress={() => router.back()} accessibilityLabel=
|
|
39
|
+
<TouchableOpacity onPress={() => router.back()} accessibilityLabel={t.comun.atras}>
|
|
38
40
|
<MaterialIcons name="arrow-back" size={45} color={c.primary} />
|
|
39
41
|
</TouchableOpacity>
|
|
40
42
|
{/* `flexShrink` y no `flex`: con acciones, el título les deja su hueco. */}
|
|
@@ -5,6 +5,7 @@ import { SafeAreaView } from "react-native-safe-area-context";
|
|
|
5
5
|
import { Text } from "../texto";
|
|
6
6
|
import { crearEstilos, tipografia, usePaleta } from "../tema";
|
|
7
7
|
import BotonGrande from "./BotonGrande";
|
|
8
|
+
import { useTextos } from "../idioma";
|
|
8
9
|
|
|
9
10
|
/** Lo que expo-router le pasa al `ErrorBoundary` de un layout. */
|
|
10
11
|
export interface PropsDePantallaDeError {
|
|
@@ -27,7 +28,7 @@ const PantallaDeError = ({
|
|
|
27
28
|
error,
|
|
28
29
|
retry,
|
|
29
30
|
onError,
|
|
30
|
-
mensaje
|
|
31
|
+
mensaje,
|
|
31
32
|
}: PropsDePantallaDeError & {
|
|
32
33
|
onError?: (error: Error) => void;
|
|
33
34
|
/** Lo que se le dice a quien la ve: qué no se perdió y a quién avisar. */
|
|
@@ -35,6 +36,7 @@ const PantallaDeError = ({
|
|
|
35
36
|
}) => {
|
|
36
37
|
const c = usePaleta();
|
|
37
38
|
const styles = useStyles();
|
|
39
|
+
const t = useTextos();
|
|
38
40
|
useEffect(() => {
|
|
39
41
|
onError?.(error);
|
|
40
42
|
// Se reporta una vez por error, aunque quien llama pase un `onError` nuevo en cada render.
|
|
@@ -45,10 +47,10 @@ const PantallaDeError = ({
|
|
|
45
47
|
<SafeAreaView style={styles.root}>
|
|
46
48
|
<ScrollView contentContainerStyle={styles.contenido}>
|
|
47
49
|
<MaterialCommunityIcons name="alert-octagon-outline" size={64} color={c.primary} />
|
|
48
|
-
<Text style={styles.titulo}>
|
|
49
|
-
<Text style={styles.texto}>{mensaje}</Text>
|
|
50
|
+
<Text style={styles.titulo}>{t.error.titulo}</Text>
|
|
51
|
+
<Text style={styles.texto}>{mensaje ?? t.error.mensaje}</Text>
|
|
50
52
|
<BotonGrande icono="refresh" onPress={() => void retry()}>
|
|
51
|
-
|
|
53
|
+
{t.comun.reintentar}
|
|
52
54
|
</BotonGrande>
|
|
53
55
|
</ScrollView>
|
|
54
56
|
</SafeAreaView>
|
|
@@ -5,6 +5,7 @@ import { crearEstilos, tipografia, usePaleta } from "../tema";
|
|
|
5
5
|
import { Text } from "../texto";
|
|
6
6
|
import Foto, { type FotoAVer } from "./Foto";
|
|
7
7
|
import VisorDeFoto from "./VisorDeFoto";
|
|
8
|
+
import { useTextos } from "../idioma";
|
|
8
9
|
|
|
9
10
|
/** Una miniatura de la tira: qué se pinta, qué abre el visor y qué se puede hacer con ella. */
|
|
10
11
|
export interface FotoDeLaTira {
|
|
@@ -41,6 +42,7 @@ interface Props {
|
|
|
41
42
|
*/
|
|
42
43
|
const TiraDeFotos = ({ fotos: piezas, lado, cuadricula = false }: Props) => {
|
|
43
44
|
const styles = useStyles();
|
|
45
|
+
const t = useTextos();
|
|
44
46
|
const [viendo, setViendo] = React.useState<number | null>(null);
|
|
45
47
|
if (piezas.length === 0) return null;
|
|
46
48
|
|
|
@@ -56,7 +58,7 @@ const TiraDeFotos = ({ fotos: piezas, lado, cuadricula = false }: Props) => {
|
|
|
56
58
|
onPress={() => setViendo(fotos.indexOf(p))}
|
|
57
59
|
accessibilityRole={p.esFoto ? "imagebutton" : undefined}
|
|
58
60
|
accessibilityLabel={
|
|
59
|
-
p.etiqueta ?? (p.esFoto ?
|
|
61
|
+
p.etiqueta ?? (p.esFoto ? t.fotos.verLaFoto : t.fotos.archivo(p.nombre ?? ""))
|
|
60
62
|
}
|
|
61
63
|
>
|
|
62
64
|
{p.esFoto ? (
|
|
@@ -84,7 +86,7 @@ const TiraDeFotos = ({ fotos: piezas, lado, cuadricula = false }: Props) => {
|
|
|
84
86
|
onPress={p.onQuitar}
|
|
85
87
|
hitSlop={10}
|
|
86
88
|
accessibilityRole="button"
|
|
87
|
-
accessibilityLabel={p.esFoto ?
|
|
89
|
+
accessibilityLabel={p.esFoto ? t.fotos.quitarFoto : t.fotos.quitarArchivo}
|
|
88
90
|
style={styles.quitar}
|
|
89
91
|
>
|
|
90
92
|
<MaterialCommunityIcons name="close" size={24} color="#fff" />
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
} from "react-native";
|
|
14
14
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
15
15
|
import Foto, { type FotoAVer } from "./Foto";
|
|
16
|
+
import { useTextos } from "../idioma";
|
|
16
17
|
|
|
17
18
|
export type { FotoAVer };
|
|
18
19
|
|
|
@@ -78,6 +79,7 @@ function Carrusel({
|
|
|
78
79
|
}) {
|
|
79
80
|
const insets = useSafeAreaInsets();
|
|
80
81
|
const { width, height } = useWindowDimensions();
|
|
82
|
+
const t = useTextos();
|
|
81
83
|
const primera = Math.min(inicio, Math.max(0, fotos.length - 1));
|
|
82
84
|
const [actual, setActual] = React.useState(primera);
|
|
83
85
|
const [descarga, setDescarga] = React.useState<Descarga>("nada");
|
|
@@ -121,9 +123,7 @@ function Carrusel({
|
|
|
121
123
|
|
|
122
124
|
<View style={[styles.barra, { top: insets.top + 12 }]}>
|
|
123
125
|
{fotos.length > 1 ? (
|
|
124
|
-
<Text style={styles.cuenta}>
|
|
125
|
-
{actual + 1} de {fotos.length}
|
|
126
|
-
</Text>
|
|
126
|
+
<Text style={styles.cuenta}>{t.fotos.deTotal(actual + 1, fotos.length)}</Text>
|
|
127
127
|
) : (
|
|
128
128
|
<View />
|
|
129
129
|
)}
|
|
@@ -135,10 +135,10 @@ function Carrusel({
|
|
|
135
135
|
accessibilityRole="button"
|
|
136
136
|
accessibilityLabel={
|
|
137
137
|
descarga === "lista"
|
|
138
|
-
?
|
|
138
|
+
? t.fotos.guardada
|
|
139
139
|
: descarga === "fallo"
|
|
140
|
-
?
|
|
141
|
-
:
|
|
140
|
+
? t.fotos.noSeGuardo
|
|
141
|
+
: t.fotos.guardarEnGaleria
|
|
142
142
|
}
|
|
143
143
|
accessibilityState={{ busy: descarga === "bajando" }}
|
|
144
144
|
>
|
|
@@ -153,7 +153,7 @@ function Carrusel({
|
|
|
153
153
|
)}
|
|
154
154
|
</Pressable>
|
|
155
155
|
) : null}
|
|
156
|
-
<Pressable onPress={onCerrar} hitSlop={12} accessibilityLabel=
|
|
156
|
+
<Pressable onPress={onCerrar} hitSlop={12} accessibilityLabel={t.comun.cerrar}>
|
|
157
157
|
<MaterialCommunityIcons name="close" size={32} color="#fff" />
|
|
158
158
|
</Pressable>
|
|
159
159
|
</View>
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
import MaterialCommunityIcons from "@expo/vector-icons/MaterialCommunityIcons";
|
|
11
11
|
import React from "react";
|
|
12
12
|
import { Modal, Pressable, ScrollView, useWindowDimensions, View } from "react-native";
|
|
13
|
+
import { useTextos } from "../idioma";
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Pinta los diálogos que abre `mostrarDialogo` (`dialogos.ts`), de uno en uno. Se monta una
|
|
@@ -23,6 +24,7 @@ import { Modal, Pressable, ScrollView, useWindowDimensions, View } from "react-n
|
|
|
23
24
|
*/
|
|
24
25
|
const HostDeDialogos = () => {
|
|
25
26
|
const c = usePaleta();
|
|
27
|
+
const t = useTextos();
|
|
26
28
|
const styles = useStyles();
|
|
27
29
|
const dialogo = React.useSyncExternalStore(suscribirDialogos, dialogoActual);
|
|
28
30
|
const { height } = useWindowDimensions();
|
|
@@ -45,7 +47,7 @@ const HostDeDialogos = () => {
|
|
|
45
47
|
navigationBarTranslucent
|
|
46
48
|
onRequestClose={cerrar}
|
|
47
49
|
>
|
|
48
|
-
<Pressable style={styles.fondo} onPress={cerrar} accessibilityLabel=
|
|
50
|
+
<Pressable style={styles.fondo} onPress={cerrar} accessibilityLabel={t.comun.cerrar}>
|
|
49
51
|
{/* El `Pressable` vacío de la tarjeta se queda con el toque: tocar adentro no cierra. */}
|
|
50
52
|
<Pressable
|
|
51
53
|
style={[styles.tarjeta, { maxHeight: height * 0.85 }]}
|
package/src/dialogos/dialogos.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// navegación). Los pinta `HostDeDialogos`, montado una vez en el layout raíz.
|
|
4
4
|
import type MaterialCommunityIcons from "@expo/vector-icons/MaterialCommunityIcons";
|
|
5
5
|
import type React from "react";
|
|
6
|
+
import { textosActuales } from "../idioma";
|
|
6
7
|
|
|
7
8
|
type NombreDeIcono = React.ComponentProps<typeof MaterialCommunityIcons>["name"];
|
|
8
9
|
|
|
@@ -73,7 +74,7 @@ export async function confirmar({
|
|
|
73
74
|
titulo,
|
|
74
75
|
mensaje,
|
|
75
76
|
textoConfirmar,
|
|
76
|
-
textoCancelar
|
|
77
|
+
textoCancelar,
|
|
77
78
|
destructivo = false,
|
|
78
79
|
icono,
|
|
79
80
|
}: {
|
|
@@ -94,7 +95,11 @@ export async function confirmar({
|
|
|
94
95
|
estilo: destructivo ? "destructivo" : "principal",
|
|
95
96
|
valor: true,
|
|
96
97
|
},
|
|
97
|
-
{
|
|
98
|
+
{
|
|
99
|
+
texto: textoCancelar ?? textosActuales().comun.cancelar,
|
|
100
|
+
estilo: "cancelar",
|
|
101
|
+
valor: false,
|
|
102
|
+
},
|
|
98
103
|
],
|
|
99
104
|
});
|
|
100
105
|
return respuesta === true;
|
|
@@ -105,7 +110,7 @@ export async function avisar({
|
|
|
105
110
|
titulo,
|
|
106
111
|
mensaje,
|
|
107
112
|
icono = "information-outline",
|
|
108
|
-
textoBoton
|
|
113
|
+
textoBoton,
|
|
109
114
|
cancelable = true,
|
|
110
115
|
}: {
|
|
111
116
|
titulo: string;
|
|
@@ -119,7 +124,13 @@ export async function avisar({
|
|
|
119
124
|
mensaje,
|
|
120
125
|
icono,
|
|
121
126
|
cancelable,
|
|
122
|
-
botones: [
|
|
127
|
+
botones: [
|
|
128
|
+
{
|
|
129
|
+
texto: textoBoton ?? textosActuales().comun.entendido,
|
|
130
|
+
estilo: "principal",
|
|
131
|
+
valor: true,
|
|
132
|
+
},
|
|
133
|
+
],
|
|
123
134
|
});
|
|
124
135
|
}
|
|
125
136
|
|
|
@@ -15,6 +15,7 @@ import IconoDeRenglon from "../componentes/IconoDeRenglon";
|
|
|
15
15
|
import { crearEstilos, renglon, tipografia, usePaleta } from "../tema";
|
|
16
16
|
import { Text, TextInput } from "../texto";
|
|
17
17
|
import { CajaDeCampo } from "./campos";
|
|
18
|
+
import { useTextos } from "../idioma";
|
|
18
19
|
|
|
19
20
|
/** Una opción de una lista: lo que se guarda (`value`) y lo que se lee (`label`). */
|
|
20
21
|
export interface Opcion {
|
|
@@ -111,7 +112,7 @@ function HojaDeOpciones<T extends Opcion>({
|
|
|
111
112
|
onTocar,
|
|
112
113
|
detalle,
|
|
113
114
|
grupo,
|
|
114
|
-
vacio
|
|
115
|
+
vacio,
|
|
115
116
|
encabezado,
|
|
116
117
|
pie,
|
|
117
118
|
}: {
|
|
@@ -133,12 +134,13 @@ function HojaDeOpciones<T extends Opcion>({
|
|
|
133
134
|
const c = usePaleta();
|
|
134
135
|
const styles = useStyles();
|
|
135
136
|
const insets = useSafeAreaInsets();
|
|
137
|
+
const t = useTextos();
|
|
136
138
|
|
|
137
139
|
return (
|
|
138
140
|
<Modal visible={abierto} animationType="slide" onRequestClose={onCerrar}>
|
|
139
141
|
<View style={[styles.hoja, { paddingTop: insets.top + 10 }]}>
|
|
140
142
|
<View style={styles.cabecera}>
|
|
141
|
-
<TouchableOpacity onPress={onCerrar}>
|
|
143
|
+
<TouchableOpacity onPress={onCerrar} accessibilityLabel={t.comun.atras}>
|
|
142
144
|
<MaterialIcons name="arrow-back" size={45} color={c.primary} />
|
|
143
145
|
</TouchableOpacity>
|
|
144
146
|
<Text style={styles.titulo}>{etiqueta}</Text>
|
|
@@ -146,7 +148,7 @@ function HojaDeOpciones<T extends Opcion>({
|
|
|
146
148
|
<TextInput
|
|
147
149
|
value={texto}
|
|
148
150
|
onChangeText={onBuscar}
|
|
149
|
-
placeholder=
|
|
151
|
+
placeholder={t.comun.buscar}
|
|
150
152
|
placeholderTextColor={c.secondaryLight}
|
|
151
153
|
autoCorrect={false}
|
|
152
154
|
autoCapitalize="characters"
|
|
@@ -163,7 +165,12 @@ function HojaDeOpciones<T extends Opcion>({
|
|
|
163
165
|
}}
|
|
164
166
|
ListHeaderComponent={encabezado}
|
|
165
167
|
ListEmptyComponent={
|
|
166
|
-
cargando ? null :
|
|
168
|
+
cargando ? null : (
|
|
169
|
+
<EstadoVacio
|
|
170
|
+
icono="magnify"
|
|
171
|
+
titulo={vacio ?? t.listas.nadaQueCoincida}
|
|
172
|
+
/>
|
|
173
|
+
)
|
|
167
174
|
}
|
|
168
175
|
renderItem={({ item, index }) => {
|
|
169
176
|
const rotulo = grupo?.(item);
|
|
@@ -217,6 +224,7 @@ function RenglonDelSelector({
|
|
|
217
224
|
}) {
|
|
218
225
|
const c = usePaleta();
|
|
219
226
|
const styles = useStyles();
|
|
227
|
+
const t = useTextos();
|
|
220
228
|
return (
|
|
221
229
|
<CajaDeCampo
|
|
222
230
|
etiqueta={etiqueta}
|
|
@@ -227,7 +235,9 @@ function RenglonDelSelector({
|
|
|
227
235
|
pendiente={pendiente}
|
|
228
236
|
>
|
|
229
237
|
<View style={styles.renglon}>
|
|
230
|
-
<Text style={[styles.valor, !texto && styles.sinValor]}>
|
|
238
|
+
<Text style={[styles.valor, !texto && styles.sinValor]}>
|
|
239
|
+
{texto ?? t.comun.elegir}
|
|
240
|
+
</Text>
|
|
231
241
|
<IconoDeRenglon
|
|
232
242
|
name="chevron-down"
|
|
233
243
|
fontSize={tipografia.subtitulo}
|
|
@@ -253,13 +263,14 @@ export default function SelectorDeOpcion<T extends Opcion>(props: PropsDeSelecto
|
|
|
253
263
|
detalle,
|
|
254
264
|
grupo,
|
|
255
265
|
opcional,
|
|
256
|
-
textoDeNinguno
|
|
266
|
+
textoDeNinguno,
|
|
257
267
|
vacio,
|
|
258
268
|
disabled,
|
|
259
269
|
accesorio,
|
|
260
270
|
resaltarVacio,
|
|
261
271
|
} = props;
|
|
262
272
|
const styles = useStyles();
|
|
273
|
+
const t = useTextos();
|
|
263
274
|
const [abierto, setAbierto] = React.useState(false);
|
|
264
275
|
const { texto, buscar, visibles } = useBuscador(props);
|
|
265
276
|
|
|
@@ -296,7 +307,9 @@ export default function SelectorDeOpcion<T extends Opcion>(props: PropsDeSelecto
|
|
|
296
307
|
encabezado={
|
|
297
308
|
opcional ? (
|
|
298
309
|
<Pressable onPress={() => elegir(null)} style={styles.opcion}>
|
|
299
|
-
<Text style={styles.opcionNombre}>
|
|
310
|
+
<Text style={styles.opcionNombre}>
|
|
311
|
+
{textoDeNinguno ?? t.comun.ninguno}
|
|
312
|
+
</Text>
|
|
300
313
|
</Pressable>
|
|
301
314
|
) : null
|
|
302
315
|
}
|
|
@@ -329,6 +342,7 @@ export function SelectorDeVarias<T extends Opcion>(props: PropsDeSelectorDeVaria
|
|
|
329
342
|
} = props;
|
|
330
343
|
const [abierto, setAbierto] = React.useState(false);
|
|
331
344
|
const [marcadas, setMarcadas] = React.useState<T[]>(valor);
|
|
345
|
+
const t = useTextos();
|
|
332
346
|
const { texto, buscar, visibles } = useBuscador(props);
|
|
333
347
|
|
|
334
348
|
const abrir = () => {
|
|
@@ -377,7 +391,7 @@ export function SelectorDeVarias<T extends Opcion>(props: PropsDeSelectorDeVaria
|
|
|
377
391
|
cerrar();
|
|
378
392
|
}}
|
|
379
393
|
>
|
|
380
|
-
{marcadas.length ?
|
|
394
|
+
{marcadas.length ? t.listas.listoCon(marcadas.length) : t.comun.listo}
|
|
381
395
|
</BotonGrande>
|
|
382
396
|
}
|
|
383
397
|
/>
|
|
@@ -12,6 +12,7 @@ import IconoDeRenglon from "../componentes/IconoDeRenglon";
|
|
|
12
12
|
import OpcionesExcluyentes from "../componentes/OpcionesExcluyentes";
|
|
13
13
|
import { crearEstilos, renglon, tipografia, usePaleta } from "../tema";
|
|
14
14
|
import { Text, TextInput } from "../texto";
|
|
15
|
+
import { useTextos } from "../idioma";
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* Los campos de un formulario (un viaje, un tracto, un operador). Cada uno es una caja con su
|
|
@@ -245,6 +246,7 @@ export function CampoDeFechaHora({
|
|
|
245
246
|
const c = usePaleta();
|
|
246
247
|
const styles = useStyles();
|
|
247
248
|
const [eligiendo, setEligiendo] = React.useState<"date" | "time" | null>(null);
|
|
249
|
+
const t = useTextos();
|
|
248
250
|
// Vacío, el selector arranca en la próxima hora en punto: la hora de ahora no le sirve a nadie.
|
|
249
251
|
const base = valor ?? dayjs().add(1, "hour").startOf("hour").toDate();
|
|
250
252
|
|
|
@@ -296,7 +298,7 @@ export function CampoDeFechaHora({
|
|
|
296
298
|
fontSize={tipografia.subtitulo}
|
|
297
299
|
color={c.secondaryLight}
|
|
298
300
|
/>
|
|
299
|
-
<Text style={[styles.textoFecha, styles.sinValor]}>
|
|
301
|
+
<Text style={[styles.textoFecha, styles.sinValor]}>{t.comun.elegir}</Text>
|
|
300
302
|
</View>
|
|
301
303
|
)}
|
|
302
304
|
</CajaDeCampo>
|
|
@@ -335,12 +337,13 @@ export function CampoDeFechaHora({
|
|
|
335
337
|
/** El «Quitar» de una fecha opcional: la ✕ junto al rótulo, dentro de la caja. */
|
|
336
338
|
function BotonDeQuitar({ onPress }: { onPress: () => void }) {
|
|
337
339
|
const c = usePaleta();
|
|
340
|
+
const t = useTextos();
|
|
338
341
|
return (
|
|
339
342
|
<Pressable
|
|
340
343
|
onPress={onPress}
|
|
341
344
|
hitSlop={10}
|
|
342
345
|
accessibilityRole="button"
|
|
343
|
-
accessibilityLabel=
|
|
346
|
+
accessibilityLabel={t.comun.quitar}
|
|
344
347
|
>
|
|
345
348
|
<IconoDeRenglon
|
|
346
349
|
name="close-circle"
|
|
@@ -418,6 +421,7 @@ export function CampoDeFecha({
|
|
|
418
421
|
const c = usePaleta();
|
|
419
422
|
const styles = useStyles();
|
|
420
423
|
const [eligiendo, setEligiendo] = React.useState(false);
|
|
424
|
+
const t = useTextos();
|
|
421
425
|
const fecha = valor ? dayjs(valor) : null;
|
|
422
426
|
|
|
423
427
|
return (
|
|
@@ -438,7 +442,7 @@ export function CampoDeFecha({
|
|
|
438
442
|
color={fecha ? c.secondary : c.secondaryLight}
|
|
439
443
|
/>
|
|
440
444
|
<Text style={[styles.textoFecha, !fecha && styles.sinValor]}>
|
|
441
|
-
{fecha ? fecha.format("D MMM YYYY") :
|
|
445
|
+
{fecha ? fecha.format("D MMM YYYY") : t.comun.elegir}
|
|
442
446
|
</Text>
|
|
443
447
|
</View>
|
|
444
448
|
</CajaDeCampo>
|
|
@@ -528,6 +532,7 @@ export function CampoDeTelefono({
|
|
|
528
532
|
const styles = useStyles();
|
|
529
533
|
const { entrada, enfocada, enfocar, alEnfocar, alSalir } = useEntradaEnCaja();
|
|
530
534
|
const { lada, digitos } = partirTelefono(valor);
|
|
535
|
+
const t = useTextos();
|
|
531
536
|
const armar = (nuevaLada: string, nuevosDigitos: string) => {
|
|
532
537
|
const solo = nuevosDigitos.replace(/\D/g, "");
|
|
533
538
|
onCambiar(solo ? `${nuevaLada}${solo}` : null);
|
|
@@ -569,7 +574,7 @@ export function CampoDeTelefono({
|
|
|
569
574
|
onBlur={alSalir}
|
|
570
575
|
keyboardType="phone-pad"
|
|
571
576
|
maxLength={10}
|
|
572
|
-
placeholder=
|
|
577
|
+
placeholder={t.campos.diezDigitos}
|
|
573
578
|
accessibilityLabel={etiqueta}
|
|
574
579
|
style={styles.texto}
|
|
575
580
|
/>
|
|
@@ -3,6 +3,7 @@ import React from "react";
|
|
|
3
3
|
import type { FieldValues, UseFormReturn } from "react-hook-form";
|
|
4
4
|
import { avisar } from "../dialogos/dialogos";
|
|
5
5
|
import { mensajeDeError, setErrors } from "./errores";
|
|
6
|
+
import { textosActuales } from "../idioma";
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Lo que comparten los formularios de edición: cómo se compara lo capturado contra lo que vino,
|
|
@@ -54,9 +55,10 @@ export function useGuardado<T extends FieldValues>(form: UseFormReturn<T>) {
|
|
|
54
55
|
router.back();
|
|
55
56
|
} catch (falla) {
|
|
56
57
|
volcarErrores(form, falla);
|
|
57
|
-
const
|
|
58
|
+
const t = textosActuales();
|
|
59
|
+
const mensaje = mensajeDeError(falla, t.comun.revisaLaSenal);
|
|
58
60
|
setError(mensaje);
|
|
59
|
-
await avisar({ titulo:
|
|
61
|
+
await avisar({ titulo: t.edicion.noSeGuardo, mensaje, icono: "alert-circle-outline" });
|
|
60
62
|
}
|
|
61
63
|
};
|
|
62
64
|
return { error, guardar };
|
package/src/fotos/elegir.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
} from "../camara/elegir";
|
|
12
12
|
import { reducirImagenAdjunta } from "../camara/reducir";
|
|
13
13
|
import { avisar, mostrarDialogo } from "../dialogos/dialogos";
|
|
14
|
+
import { textosActuales } from "../idioma";
|
|
14
15
|
|
|
15
16
|
/** El tope de un archivo en el servidor (`ConArchivoSubido.TAMANO_MAXIMO`): 25 MB. */
|
|
16
17
|
export const TAMANO_MAXIMO_BYTES = 25 * 1024 * 1024;
|
|
@@ -48,10 +49,12 @@ export async function elegirDocumentos({
|
|
|
48
49
|
const pesa = doc.size ?? new File(doc.uri).size;
|
|
49
50
|
if (pesa > tamanoMaximo) {
|
|
50
51
|
await avisar({
|
|
51
|
-
titulo:
|
|
52
|
-
mensaje:
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
titulo: textosActuales().fotos.demasiadoPesado,
|
|
53
|
+
mensaje: textosActuales().fotos.pesaDeMas(
|
|
54
|
+
doc.name,
|
|
55
|
+
(pesa / (1024 * 1024)).toFixed(1),
|
|
56
|
+
tamanoMaximo / (1024 * 1024),
|
|
57
|
+
),
|
|
55
58
|
icono: "file-alert-outline",
|
|
56
59
|
});
|
|
57
60
|
continue;
|
|
@@ -71,7 +74,7 @@ export async function elegirDocumentos({
|
|
|
71
74
|
* cualquier paso o el archivo no se puede subir (y ya se dijo por qué).
|
|
72
75
|
*/
|
|
73
76
|
export async function elegirAdjunto({
|
|
74
|
-
titulo
|
|
77
|
+
titulo,
|
|
75
78
|
permiso,
|
|
76
79
|
tipos,
|
|
77
80
|
}: {
|
|
@@ -79,29 +82,30 @@ export async function elegirAdjunto({
|
|
|
79
82
|
permiso: PermisoDeCamara;
|
|
80
83
|
tipos?: string[];
|
|
81
84
|
}): Promise<ArchivoElegido | null> {
|
|
85
|
+
const t = textosActuales();
|
|
82
86
|
const origen = await mostrarDialogo<"camara" | "galeria" | "documento" | null>({
|
|
83
|
-
titulo,
|
|
87
|
+
titulo: titulo ?? t.fotos.subirArchivo,
|
|
84
88
|
icono: "paperclip",
|
|
85
89
|
botones: [
|
|
86
90
|
{
|
|
87
|
-
texto:
|
|
91
|
+
texto: t.fotos.tomarUnaFoto,
|
|
88
92
|
icono: "camera-outline",
|
|
89
93
|
estilo: "principal",
|
|
90
94
|
valor: "camara",
|
|
91
95
|
},
|
|
92
96
|
{
|
|
93
|
-
texto:
|
|
97
|
+
texto: t.fotos.deLaGaleria,
|
|
94
98
|
icono: "image-outline",
|
|
95
99
|
estilo: "secundario",
|
|
96
100
|
valor: "galeria",
|
|
97
101
|
},
|
|
98
102
|
{
|
|
99
|
-
texto:
|
|
103
|
+
texto: t.fotos.unDocumento,
|
|
100
104
|
icono: "file-document-outline",
|
|
101
105
|
estilo: "secundario",
|
|
102
106
|
valor: "documento",
|
|
103
107
|
},
|
|
104
|
-
{ texto:
|
|
108
|
+
{ texto: t.comun.cancelar, estilo: "cancelar", valor: null },
|
|
105
109
|
],
|
|
106
110
|
});
|
|
107
111
|
const elegidos =
|