diff --git a/mobile/app.json b/mobile/app.json index 384972acebb6dcf4953420af816652e47ae7ad93..2329aa44d979dd3482badc525c712402a46098bd 100644 --- a/mobile/app.json +++ b/mobile/app.json @@ -50,7 +50,8 @@ } ], "expo-router", - "expo-secure-store" + "expo-secure-store", + "expo-localization" ], "extra": { "router": { diff --git a/mobile/app/state/[stateId]/town/[townId]/activity/[activityId]/travel.tsx b/mobile/app/state/[stateId]/town/[townId]/activity/[activityId]/travel.tsx index b5c9f27212b4bb504ea98296d52e328237efcd79..e75e46755d133a5e9777374443566c2cbf536b5f 100644 --- a/mobile/app/state/[stateId]/town/[townId]/activity/[activityId]/travel.tsx +++ b/mobile/app/state/[stateId]/town/[townId]/activity/[activityId]/travel.tsx @@ -1,9 +1,16 @@ +import { useLocalSearchParams } from "expo-router"; import { View, Text } from "react-native"; +import { ActivityPointScreen } from "../../../../../../../src/screens/activity_point/activity_point"; + export default function Travel() { + const { ...rest } = useLocalSearchParams<{ stateId: string, townId: string, activityId: string, id: string }>(); + + if (!rest.stateId || !rest.townId || !rest.activityId || !rest.id) { + return null; + } + return ( - - Travel - + ); } \ No newline at end of file diff --git a/mobile/app/state/[stateId]/town/[townId]/activity/_layout.tsx b/mobile/app/state/[stateId]/town/[townId]/activity/_layout.tsx index 02fde054082c18137b8722d15cf47b1958c531c7..496d77698aef63ff9bdd88047237d6ae17fb54c2 100644 --- a/mobile/app/state/[stateId]/town/[townId]/activity/_layout.tsx +++ b/mobile/app/state/[stateId]/town/[townId]/activity/_layout.tsx @@ -8,6 +8,11 @@ export default function ActivitySelectionScreen() { headerShown: false } } /> + ); } \ No newline at end of file diff --git a/mobile/package-lock.json b/mobile/package-lock.json index 764d18dfadc74a474f2a281d7f7f3682833b8c89..b8242da5e3e4c2e2bbec9aa7a076062039a1d6b2 100644 --- a/mobile/package-lock.json +++ b/mobile/package-lock.json @@ -12,16 +12,19 @@ "@react-native-community/datetimepicker": "7.6.1", "axios": "^1.6.8", "expo": "~50.0.14", + "expo-av": "~13.10.6", "expo-barcode-scanner": "~12.9.3", "expo-camera": "~14.1.1", "expo-checkbox": "~2.7.0", "expo-constants": "~15.4.5", "expo-image-picker": "~14.7.1", "expo-linking": "~6.2.2", + "expo-localization": "~14.8.4", "expo-router": "~3.4.8", "expo-screen-orientation": "~6.4.1", "expo-secure-store": "~12.8.1", "expo-status-bar": "~1.11.1", + "i18n-js": "^4.4.3", "nativewind": "^2.0.11", "react": "18.2.0", "react-hook-form": "^7.51.2", @@ -6854,6 +6857,14 @@ "node": ">=0.6" } }, + "node_modules/bignumber.js": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", + "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==", + "engines": { + "node": "*" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -8182,6 +8193,14 @@ "md5-file": "^3.2.3" } }, + "node_modules/expo-av": { + "version": "13.10.6", + "resolved": "https://registry.npmjs.org/expo-av/-/expo-av-13.10.6.tgz", + "integrity": "sha512-h3c1fg5yhWnP0RIGO+fhgPx6cmh4B4lnKdXR2i69aC3vs5D5Cu+JlzBon1gLIu6eUo2IVfC0RjSLpfQbcJ4doQ==", + "peerDependencies": { + "expo": "*" + } + }, "node_modules/expo-barcode-scanner": { "version": "12.9.3", "resolved": "https://registry.npmjs.org/expo-barcode-scanner/-/expo-barcode-scanner-12.9.3.tgz", @@ -8275,6 +8294,17 @@ "invariant": "^2.2.4" } }, + "node_modules/expo-localization": { + "version": "14.8.4", + "resolved": "https://registry.npmjs.org/expo-localization/-/expo-localization-14.8.4.tgz", + "integrity": "sha512-WWcG6Bg9s8p9wk5BvdXfxBLCEcvzS75O+nN5O+kWA3+cGDKcbfvExWmXcCSz8hn8y8rRVIEx8wwSgysMTtptUA==", + "dependencies": { + "rtl-detect": "^1.0.2" + }, + "peerDependencies": { + "expo": "*" + } + }, "node_modules/expo-modules-autolinking": { "version": "1.10.3", "resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-1.10.3.tgz", @@ -9097,6 +9127,16 @@ "node": ">=10.17.0" } }, + "node_modules/i18n-js": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/i18n-js/-/i18n-js-4.4.3.tgz", + "integrity": "sha512-QIIyvJ+wOKdigL4BlgwiFFrpoXeGdlC8EYgori64YSWm1mnhNYYjIfRu5wETFrmiNP2fyD6xIjVG8dlzaiQr/A==", + "dependencies": { + "bignumber.js": "*", + "lodash": "*", + "make-plural": "*" + } + }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -10593,6 +10633,11 @@ "semver": "bin/semver" } }, + "node_modules/make-plural": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/make-plural/-/make-plural-7.3.0.tgz", + "integrity": "sha512-/K3BC0KIsO+WK2i94LkMPv3wslMrazrQhfi5We9fMbLlLjzoOSJWr7TAdupLlDWaJcWxwoNosBkhFDejiu5VDw==" + }, "node_modules/makeerror": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", @@ -13044,6 +13089,11 @@ "rimraf": "bin.js" } }, + "node_modules/rtl-detect": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/rtl-detect/-/rtl-detect-1.1.2.tgz", + "integrity": "sha512-PGMBq03+TTG/p/cRB7HCLKJ1MgDIi07+QU1faSjiYRfmY5UsAttV9Hs08jDAHVwcOwmVLcSJkpwyfXszVjWfIQ==" + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", diff --git a/mobile/package.json b/mobile/package.json index 567cd1d5d9c08096fcf5be276fb99a84335ef4c3..51a98de3c9092e2a63367007f9ff1b0a2280d59e 100644 --- a/mobile/package.json +++ b/mobile/package.json @@ -17,11 +17,14 @@ "expo-camera": "~14.1.1", "expo-checkbox": "~2.7.0", "expo-constants": "~15.4.5", + "expo-image-picker": "~14.7.1", "expo-linking": "~6.2.2", + "expo-localization": "~14.8.4", "expo-router": "~3.4.8", "expo-screen-orientation": "~6.4.1", "expo-secure-store": "~12.8.1", "expo-status-bar": "~1.11.1", + "i18n-js": "^4.4.3", "nativewind": "^2.0.11", "react": "18.2.0", "react-hook-form": "^7.51.2", @@ -31,7 +34,7 @@ "react-native-reanimated": "~3.6.2", "react-native-safe-area-context": "4.8.2", "react-native-screens": "~3.29.0", - "expo-image-picker": "~14.7.1" + "expo-av": "~13.10.6" }, "devDependencies": { "@babel/core": "^7.20.0", diff --git a/mobile/src/components/activity_bottom_sheet/activity_bottom_sheet.tsx b/mobile/src/components/activity_bottom_sheet/activity_bottom_sheet.tsx index d3c9cba62c47c5356a22c8702640c762a640efd9..440756af050ddf4a05d9dee2a1763098316f1eda 100644 --- a/mobile/src/components/activity_bottom_sheet/activity_bottom_sheet.tsx +++ b/mobile/src/components/activity_bottom_sheet/activity_bottom_sheet.tsx @@ -1,6 +1,6 @@ import { useRef, useState } from "react"; import BottomSheet, { - BottomSheetFlatList, + BottomSheetFlatList, BottomSheetScrollView, BottomSheetView, } from "@gorhom/bottom-sheet"; @@ -38,34 +38,45 @@ export const ActivityBottomSheet = ({ onChange={onSnapPointChange} snapPoints={snapPoints} > - + - {activity.name} - { - router.push('/scan') - }} - style={styles.do_activity_button}> - Do Activity - + {activity.name} + { + router.push("/scan"); + }} + style={styles.do_activity_button} + > + Do Activity + - { - activity.tags && ( - item} - ItemSeparatorComponent={() => } - renderItem={({ item }) => ( - - {item} - - )} - /> - ) - } + {activity.tags && ( + item} + ItemSeparatorComponent={() => } + renderItem={({ item }) => ( + + {item} + + )} + /> + )} Description @@ -92,12 +103,13 @@ const styles = StyleSheet.create({ padding: 20, }, activity_name_container: { - flexDirection: 'row', - justifyContent: 'space-between' + flexDirection: "row", + justifyContent: "space-between", }, name_text: { fontSize: 24, fontWeight: "bold", + width: "70%", }, do_activity_button: { height: 40, @@ -105,9 +117,9 @@ const styles = StyleSheet.create({ backgroundColor: LIGTHT_THEME.color.primary, padding: 10, borderRadius: 20, - justifyContent : 'center', - alignItems: 'center', - paddingHorizontal: 20 + justifyContent: "center", + alignItems: "center", + paddingHorizontal: 20, }, do_activity_text: { color: LIGTHT_THEME.color.white, diff --git a/mobile/src/components/audio_player/audio_player.tsx b/mobile/src/components/audio_player/audio_player.tsx new file mode 100644 index 0000000000000000000000000000000000000000..ac2c83d11c76a29733d11bc4919f5f311e083fd3 --- /dev/null +++ b/mobile/src/components/audio_player/audio_player.tsx @@ -0,0 +1,32 @@ +import { TouchableOpacity, View, StyleSheet } from "react-native"; +import { useAudio } from "../../hooks/useAudio"; +import { FontAwesome } from '@expo/vector-icons'; + +const audio = require('./../../../assets/audio_prueba.mp3'); + +interface AudioPlayerProps { + audioUrl: string; + title: string; + description: string; +} + +export const AudioPlayer = ({ audioUrl, title, description }: AudioPlayerProps) => { + const { togglePlay, isPlaying } = useAudio({ source: audio }); + + + return + + + + ; +} + +const styles = StyleSheet.create({ + container: { + height: 100, + width: '100%', + justifyContent: 'center', + alignItems: 'center', + backgroundColor: 'rgba(0,0,0,0.1)' + } +}); \ No newline at end of file diff --git a/mobile/src/components/language_icon/language_icon.tsx b/mobile/src/components/language_icon/language_icon.tsx new file mode 100644 index 0000000000000000000000000000000000000000..657cb3202b0c54312e72a4a117267278c8a8223d --- /dev/null +++ b/mobile/src/components/language_icon/language_icon.tsx @@ -0,0 +1,23 @@ +import { Entypo } from '@expo/vector-icons'; +import { TouchableOpacity } from 'react-native-gesture-handler'; +import { LANG } from '../../lang/translations'; + +export const LanguageIcon = () => { + const changeLanguage = () => { + if (LANG.locale === 'es') { + // Change to english RTL + LANG.locale = 'en'; + + + } + else { + LANG.locale = 'es'; + } + } + + return ( + + + + ); +} \ No newline at end of file diff --git a/mobile/src/components/login_form/login_form.tsx b/mobile/src/components/login_form/login_form.tsx index 39eb15fa5614eb8feebe36671b74c5f1721b2b94..1dd04ee7a77ca2ac8f218af683f353086e25587a 100644 --- a/mobile/src/components/login_form/login_form.tsx +++ b/mobile/src/components/login_form/login_form.tsx @@ -4,6 +4,7 @@ import { CustomTextInput } from "../text_input/text_input"; import { LIGTHT_THEME } from "../../constants/theme"; import { LoginFormValues } from "../../hooks/useLoggin"; import { OrDivision } from "../or_division/or_division"; +import { LANG } from "../../lang/translations"; interface LoginFormProps { control: Control; @@ -13,7 +14,7 @@ interface LoginFormProps { export const LoginForm = ({ control, onSubmit }: LoginFormProps) => { return ( - Login + {LANG.t('loginScreen.title')} { formState: { errors }, }) => ( { formState: { errors }, }) => ( { /> - Login + {LANG.t('loginScreen.loginButton')} diff --git a/mobile/src/components/or_division/or_division.tsx b/mobile/src/components/or_division/or_division.tsx index 95430d938c48499a032bcaf4da5dccb1fac2baef..af51421b3e6c4dd50e92715efc2fb0d5f018917e 100644 --- a/mobile/src/components/or_division/or_division.tsx +++ b/mobile/src/components/or_division/or_division.tsx @@ -1,10 +1,11 @@ import { View, Text, StyleSheet } from "react-native"; import { LIGTHT_THEME } from "../../constants/theme"; +import { LANG } from "../../lang/translations"; export const OrDivision = () => { return ( - OR + {LANG.t('common.or')} ); }; diff --git a/mobile/src/contexts/data_context.tsx b/mobile/src/contexts/data_context.tsx index e216b1c88d40cb59714756d2163de3f4880d290f..b5e0584cbd70f9d025ee62f7b90505fdd9b105f0 100644 --- a/mobile/src/contexts/data_context.tsx +++ b/mobile/src/contexts/data_context.tsx @@ -6,29 +6,36 @@ import { AuthRepository } from "../domain/repositories/auth_repository"; import { AuthDataSourceDev } from "../infrastructure/datasource/dev/auth_datasource"; import { AuthRepositoryImpl } from "../infrastructure/repositories/auth_repository"; import { AuthDatasourceProd } from "../infrastructure/datasource/prod/auth_datasource"; +import { ActivityRepository } from "../domain/repositories/activity_repository"; +import { ActivityDatasourceDev } from "../infrastructure/datasource/dev/activity_datasource"; +import { ActivityRepositoryDev } from '../infrastructure/repositories/activity_repository'; type DataContextType = { statesRepository: StateRepository | null; authRepository: AuthRepository | null; + activityRepository: ActivityRepository | null; }; type DataContextProviderProps = PropsWithChildren<{}>; const DataContext = createContext({ statesRepository: null, - authRepository: null + authRepository: null, + activityRepository: null }); export const DataContextProvider = ({ children }: DataContextProviderProps) => { const statesDataSource = new StateDataSourceDev(); const statesRepository = new StateRepositoryImpl(statesDataSource); - const authDataSource = new AuthDatasourceProd(); + const authDataSource = new AuthDataSourceDev(); const authRepository = new AuthRepositoryImpl(authDataSource); - + const activityDataSource = new ActivityDatasourceDev(); + const activityRepository = new ActivityRepositoryDev(activityDataSource); const value = { statesRepository, - authRepository + authRepository, + activityRepository }; return ( diff --git a/mobile/src/domain/datasources/activity_datasource.ts b/mobile/src/domain/datasources/activity_datasource.ts new file mode 100644 index 0000000000000000000000000000000000000000..9ae21e7a369defd0760c1a1d3187af5bfe8177fa --- /dev/null +++ b/mobile/src/domain/datasources/activity_datasource.ts @@ -0,0 +1,5 @@ +import { ActivityPlaceEntity } from "../entities/activity_place_entity"; + +export interface ActivityDataSource { + getPlaceActivity(activityId: number, townId: number, stateId: number, placeNumber: number): Promise; +} \ No newline at end of file diff --git a/mobile/src/domain/entities/activity_place_entity.ts b/mobile/src/domain/entities/activity_place_entity.ts new file mode 100644 index 0000000000000000000000000000000000000000..e23bfddc0c1c5ef69394600056386b18d1f06ce8 --- /dev/null +++ b/mobile/src/domain/entities/activity_place_entity.ts @@ -0,0 +1,19 @@ +export interface ActivityPlaceEntity { + idPlaceActivity: number; + name: string; + number: number; + idPlace: number; + imageUrl: string; + directions?: DirectionEntity; + content: ContentEntity +} + +interface DirectionEntity { + content: string; + speakUrl: string; +} + +interface ContentEntity { + content: string; + speakUrl: string; +} \ No newline at end of file diff --git a/mobile/src/domain/repositories/activity_repository.ts b/mobile/src/domain/repositories/activity_repository.ts new file mode 100644 index 0000000000000000000000000000000000000000..45706508e0122cf58755941178cdc9c7c05b0f1c --- /dev/null +++ b/mobile/src/domain/repositories/activity_repository.ts @@ -0,0 +1,5 @@ +import { ActivityPlaceEntity } from "../entities/activity_place_entity"; + +export interface ActivityRepository { + getPlaceActivity(activityId: number, townId: number, stateId: number, placeNumber: number): Promise; +} \ No newline at end of file diff --git a/mobile/src/hooks/useAudio.ts b/mobile/src/hooks/useAudio.ts new file mode 100644 index 0000000000000000000000000000000000000000..b166f2ff28ae65e263dda1068033d4100ae70e1e --- /dev/null +++ b/mobile/src/hooks/useAudio.ts @@ -0,0 +1,44 @@ +import { AVPlaybackSource, AVPlaybackStatus, Audio } from "expo-av"; +import { useEffect, useState } from "react"; + +interface AudioPlayerProps { + source: AVPlaybackSource; +} + +export const useAudio = ({ source }: AudioPlayerProps) => { + const [sound, setSound] = useState(null); + const [isPlaying, setIsPlaying] = useState(false); + + const loadAudio = async () => { + const { sound, status } = await Audio.Sound.createAsync(source, { + shouldPlay: false + }); + if (status.isLoaded) { + setSound(sound); + setIsPlaying(status.isPlaying); + } + } + + const togglePlay = async () => { + if (sound) { + if (isPlaying) { + await sound.pauseAsync(); + } else { + await sound.playAsync(); + } + setIsPlaying(!isPlaying); + } + } + + useEffect(() => { + loadAudio(); + return () => { + if (sound) { + sound.pauseAsync(); + sound.unloadAsync(); + } + } + }, [source]); + + return { togglePlay, isPlaying }; +} \ No newline at end of file diff --git a/mobile/src/hooks/useGetActivityPoint.ts b/mobile/src/hooks/useGetActivityPoint.ts new file mode 100644 index 0000000000000000000000000000000000000000..151b5f452c45fa5bec63a2f5cd460bbead7e6e9d --- /dev/null +++ b/mobile/src/hooks/useGetActivityPoint.ts @@ -0,0 +1,19 @@ +import { useDataContext } from "../contexts/data_context" +import { ActivityPlaceEntity } from "../domain/entities/activity_place_entity"; +import { useGet } from "./useGet"; + +interface UseGetActivityPointProps { + stateId: number; + townId: number; + activityId: number; + placeNumber: number; +} + +export const useGetActivityPoint = ({ activityId, townId, stateId, placeNumber }: UseGetActivityPointProps) => { + const { activityRepository } = useDataContext(); + const callback = () => { + return activityRepository!.getPlaceActivity(activityId, townId, stateId, placeNumber); + } + const { data, requestStatus } = useGet(callback); + return { data, requestStatus }; +} \ No newline at end of file diff --git a/mobile/src/hooks/useQRScanner.ts b/mobile/src/hooks/useQRScanner.ts index bddddd319393dd4911e91c67d93bc76a2d13b49c..a6af09ce986ffaff6e386926298167c92986d290 100644 --- a/mobile/src/hooks/useQRScanner.ts +++ b/mobile/src/hooks/useQRScanner.ts @@ -20,7 +20,8 @@ export const useQRScanner = () => { console.log(data.data); setQRData(data.data); setScanning(false); - router.replace(data.data); + router.back(); + router.push(data.data); }; useEffect(() => { if (status?.granted) { diff --git a/mobile/src/infrastructure/datasource/dev/activity_datasource.ts b/mobile/src/infrastructure/datasource/dev/activity_datasource.ts new file mode 100644 index 0000000000000000000000000000000000000000..21e709766ee0a85354f85d572203dcf603970fa6 --- /dev/null +++ b/mobile/src/infrastructure/datasource/dev/activity_datasource.ts @@ -0,0 +1,25 @@ +import { ActivityDataSource } from "../../../domain/datasources/activity_datasource"; +import { ActivityPlaceEntity } from "../../../domain/entities/activity_place_entity"; + +export class ActivityDatasourceDev implements ActivityDataSource { + async getPlaceActivity(activityId: number, townId: number, stateId: number, placeNumber: number): Promise { + return new Promise((resolve) => { + resolve(placeActivities.find(place => place.idPlace === activityId && place.number === placeNumber) as ActivityPlaceEntity); + }); + } + +} + +const placeActivities: ActivityPlaceEntity[] = [ + { + idPlaceActivity: 1, + name: "Puerta del santuario de Nuestra Señora de la Soledad", + number: 1, + idPlace: 1, + imageUrl: "https://fastly.4sqi.net/img/general/200x200/50314270_u7Rp3Fk5Z9eaNC24PzKmzZ2iBOublx1bVaqDHE8TNFM.jpg", + content: { + content: "La puerta del santuario de Nuestra Señora de la Soledad es un lugar de culto católico en Jerez de García Salinas, Zacatecas, México. Es un lugar de peregrinación y oración para los fieles católicos.", + speakUrl: "https://www.google.com" + } + }, +]; \ No newline at end of file diff --git a/mobile/src/infrastructure/datasource/dev/state_datasource.ts b/mobile/src/infrastructure/datasource/dev/state_datasource.ts index 555dda5c4589f81f4fef7513bae498a95e405332..0fb31de13e4dc689d1b2d32e1279efb2dafd74d5 100644 --- a/mobile/src/infrastructure/datasource/dev/state_datasource.ts +++ b/mobile/src/infrastructure/datasource/dev/state_datasource.ts @@ -78,12 +78,12 @@ const towns: TownEntity[] = [ const activities: ActivityInfoEntity[] = [ { id: 1, - name: 'Feria de la primavera', - description: 'Feria de la primavera en Jerez', + name: 'Santuario de Nuestra Señora de la Soledad', + description: 'Santuario de Nuestra Señora de la Soledad en Jerez', townId: 1, - available: 'Primavera', + available: 'Todo el año', location: 'Jerez, Zacatecas', - imageUri: 'https://www.liderempresarial.com/wp-content/uploads/2022/03/Feria-de-Primavera-Jerez-2022-1024x1024.jpg' + imageUri: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSAOt4JS0AzrMsxDp0caz26vuanMq692g17nbI6-_nycw&s' }, { id: 2, diff --git a/mobile/src/infrastructure/repositories/activity_repository.ts b/mobile/src/infrastructure/repositories/activity_repository.ts new file mode 100644 index 0000000000000000000000000000000000000000..3c4e2083ce2c342e2c9fa9de54eb0e27e26875a9 --- /dev/null +++ b/mobile/src/infrastructure/repositories/activity_repository.ts @@ -0,0 +1,12 @@ +import { ActivityDataSource } from "../../domain/datasources/activity_datasource"; +import { ActivityPlaceEntity } from "../../domain/entities/activity_place_entity"; +import { ActivityRepository } from "../../domain/repositories/activity_repository"; + +export class ActivityRepositoryDev implements ActivityRepository { + constructor( + private activityDataSource: ActivityDataSource + ) {} + async getPlaceActivity(activityId: number, townId: number, stateId: number, placeNumber: number): Promise { + return this.activityDataSource.getPlaceActivity(activityId, townId, stateId, placeNumber); + } +} \ No newline at end of file diff --git a/mobile/src/lang/english_lang.ts b/mobile/src/lang/english_lang.ts new file mode 100644 index 0000000000000000000000000000000000000000..991949b71c630317ef2b4f463277e8d6e89297b9 --- /dev/null +++ b/mobile/src/lang/english_lang.ts @@ -0,0 +1,31 @@ +import { Lang } from "./lang"; + +export const ENGLISH_LANG: Lang = { + loginScreen:{ + title: "Login", + loginButton: "Login", + registerButton: "Register", + }, + registerScreen:{ + title: "Register", + loginButton: "Login", + registerButton: "Register", + }, + forms : { + email: "Email", + password: "Password", + confirmPassword: "Confirm password", + name: "Name", + lastName: "Last name", + dateOfBirth: "Date of birth", + }, + formsErrors : { + invalidEmailFormat: "Invalid email format", + requiredField: "Required field", + passwordNotMatch: "Passwords do not match", + emailAlreadyInUse: "Email already in use", + }, + common : { + or: "or", + } +} \ No newline at end of file diff --git a/mobile/src/lang/lang.ts b/mobile/src/lang/lang.ts new file mode 100644 index 0000000000000000000000000000000000000000..22dd3943b3eb30f6574e6c7f1701eb2e7d9ec0f3 --- /dev/null +++ b/mobile/src/lang/lang.ts @@ -0,0 +1,30 @@ +export interface Lang { + loginScreen:{ + title: string; + loginButton: string; + registerButton: string; + }, + registerScreen:{ + title: string; + loginButton: string; + registerButton: string; + }, + forms : { + email: string; + password: string; + confirmPassword: string; + name: string; + lastName: string; + dateOfBirth: string; + }, + formsErrors : { + invalidEmailFormat: string; + requiredField: string; + passwordNotMatch: string; + emailAlreadyInUse: string; + }, + common : { + or: string; + } +} + \ No newline at end of file diff --git a/mobile/src/lang/spanish_lang.ts b/mobile/src/lang/spanish_lang.ts new file mode 100644 index 0000000000000000000000000000000000000000..9c7eb5fcdb383cff9db2a44ba535f4ae01483660 --- /dev/null +++ b/mobile/src/lang/spanish_lang.ts @@ -0,0 +1,31 @@ +import { Lang } from "./lang"; + +export const SPANISH_LANG: Lang = { + loginScreen:{ + title: "Iniciar sesión", + loginButton: "Iniciar sesión", + registerButton: "Registrarse", + }, + registerScreen:{ + title: "Registrarse", + loginButton: "Iniciar sesión", + registerButton: "Registrarse", + }, + forms : { + email: "Correo electrónico", + password: "Contraseña", + confirmPassword: "Confirmar contraseña", + name: "Nombre", + lastName: "Apellido", + dateOfBirth: "Fecha de nacimiento", + }, + formsErrors : { + invalidEmailFormat: "Formato de correo electrónico inválido", + requiredField: "Campo requerido", + passwordNotMatch: "Las contraseñas no coinciden", + emailAlreadyInUse: "Correo electrónico ya en uso", + }, + common : { + or: "o", + } +}; \ No newline at end of file diff --git a/mobile/src/lang/translations.ts b/mobile/src/lang/translations.ts new file mode 100644 index 0000000000000000000000000000000000000000..a21b25e350aa762239a30abc7ee3f4029683bf29 --- /dev/null +++ b/mobile/src/lang/translations.ts @@ -0,0 +1,18 @@ +import { ENGLISH_LANG } from "./english_lang"; +import { SPANISH_LANG } from "./spanish_lang"; +import { I18n } from 'i18n-js'; + +const translations = { + en : ENGLISH_LANG, + es : SPANISH_LANG +} + +const LANG = new I18n(translations); + +LANG.locale = 'es'; + +LANG.enableFallback = true; + +export { LANG }; + + diff --git a/mobile/src/screens/activity_point/activity_point.tsx b/mobile/src/screens/activity_point/activity_point.tsx new file mode 100644 index 0000000000000000000000000000000000000000..6769d6403aa2389ebcfe3464da3888e05ed0db83 --- /dev/null +++ b/mobile/src/screens/activity_point/activity_point.tsx @@ -0,0 +1,81 @@ +import { Image, Text, View, StyleSheet } from "react-native"; +import { FullPageLoader } from "../../components/full_page_loader/full_page_loader"; +import { ApiRequestStatus } from "../../constants/api_request_states"; +import { useGetActivityPoint } from "../../hooks/useGetActivityPoint"; +import { ScrollView } from "react-native-gesture-handler"; +import { AudioPlayer } from "../../components/audio_player/audio_player"; + +interface ActivityPointScreenProps { + stateId: number; + townId: number; + activityId: number; + id: number; +} + +export const ActivityPointScreen = ({ + stateId, + townId, + activityId, + id, +}: ActivityPointScreenProps) => { + const { data, requestStatus } = useGetActivityPoint({ + activityId, + townId, + stateId, + placeNumber: id, + }); + + if (requestStatus === ApiRequestStatus.LOADING) { + return ; + } + + if (requestStatus === ApiRequestStatus.ERROR || !data) { + return null; + } + return ( + + + {data.name} + + + + {data.content.content} + + + + + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + gap: 15, + justifyContent: "space-between", + }, + placeContainer: { + flex: 1, + gap: 20, + padding: 20, + }, + title: { + fontSize: 24, + fontWeight: "bold", + textAlign: "center", + }, + contentText: { + fontSize: 16, + }, + image: { + height: 300, + width: "100%", + }, +}); diff --git a/mobile/src/screens/login/login_page.tsx b/mobile/src/screens/login/login_page.tsx index 385cb637c7d5b1cf99d4bf728176f13509dbece0..68dacfb00cd4ba2f5c6901489092457da1395995 100644 --- a/mobile/src/screens/login/login_page.tsx +++ b/mobile/src/screens/login/login_page.tsx @@ -3,6 +3,8 @@ import { LoginForm } from "../../components/login_form/login_form"; import { useLoggin } from "../../hooks/useLoggin"; import { LIGTHT_THEME } from "../../constants/theme"; import { router } from "expo-router"; +import { LANG } from "../../lang/translations"; +import { LanguageIcon } from "../../components/language_icon/language_icon"; const loginImage = require("../../../assets/login-image.jpg"); export const LoginPage = () => { @@ -18,7 +20,7 @@ export const LoginPage = () => { -