diff --git a/mobile/package-lock.json b/mobile/package-lock.json index e1b43956608d5e033814f97d6858ddeed19e005c..0145b231234c29d0f5f771069b70279cbf49d14a 100644 --- a/mobile/package-lock.json +++ b/mobile/package-lock.json @@ -21,6 +21,7 @@ "expo-checkbox": "~2.7.0", "expo-constants": "~15.4.5", "expo-image-picker": "~14.7.1", + "expo-linear-gradient": "~12.7.2", "expo-linking": "~6.2.2", "expo-localization": "~14.8.4", "expo-router": "~3.4.8", @@ -8869,6 +8870,14 @@ "expo": "*" } }, + "node_modules/expo-linear-gradient": { + "version": "12.7.2", + "resolved": "https://registry.npmjs.org/expo-linear-gradient/-/expo-linear-gradient-12.7.2.tgz", + "integrity": "sha512-Wwb2EF18ywgrlTodcXJ6Yt/UEcKitRMdXPNyP/IokmeKh4emoq9DxZJpZdkXm3HUTLlbRpi6/t32jrFVqXB9AQ==", + "peerDependencies": { + "expo": "*" + } + }, "node_modules/expo-linking": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/expo-linking/-/expo-linking-6.2.2.tgz", diff --git a/mobile/package.json b/mobile/package.json index bb5ccad657f3d29e351b0c11e79b759fd6ee215f..1afc8f7383e522475aa86d41c87f72abdcfc8d93 100644 --- a/mobile/package.json +++ b/mobile/package.json @@ -43,7 +43,8 @@ "react-native-screens": "~3.29.0", "react-native-svg": "14.1.0", "@react-native-picker/picker": "2.6.1", - "@react-native-async-storage/async-storage": "1.21.0" + "@react-native-async-storage/async-storage": "1.21.0", + "expo-linear-gradient": "~12.7.2" }, "devDependencies": { "@babel/core": "^7.20.0", diff --git a/mobile/src/common/components/animated_background.tsx b/mobile/src/common/components/animated_background.tsx index 7c208a9eb3827e4690e671af31cabf456eee19b9..b06ea8e6c9cdc769ce8196df42407581e12a382b 100644 --- a/mobile/src/common/components/animated_background.tsx +++ b/mobile/src/common/components/animated_background.tsx @@ -1,13 +1,18 @@ import { Animated, Image, View } from "react-native"; import { PlaceInfoEntity } from "../../domain/entities/place_info_entity"; import { useAnimatedSelectedIndex } from "../../hooks/useAnimatedSelectedIndex"; +import { LinearGradient } from "expo-linear-gradient"; +import { LIGHT_THEME } from "../constants/theme"; interface AnimatedBackgroundProps { - imageUri?: string; - backgroundImageAnimation: Animated.Value; -}; + imageUri?: string; + backgroundImageAnimation: Animated.Value; +} -export const AnimatedBackground = ({ imageUri, backgroundImageAnimation }: AnimatedBackgroundProps) => { +export const AnimatedBackground = ({ + imageUri, + backgroundImageAnimation, +}: AnimatedBackgroundProps) => { return ( - ); }; diff --git a/mobile/src/common/components/caroussel/caroussel.tsx b/mobile/src/common/components/caroussel/caroussel.tsx index b78eee7555a84506652f13e3214f9fdd5465727d..edcdd508f0b83550dca90193f696a6e7f4be2761 100644 --- a/mobile/src/common/components/caroussel/caroussel.tsx +++ b/mobile/src/common/components/caroussel/caroussel.tsx @@ -1,9 +1,18 @@ -import { View, Animated, Text, Dimensions, Image, NativeSyntheticEvent, NativeScrollEvent } from "react-native"; +import { + View, + Animated, + Text, + Dimensions, + Image, + NativeSyntheticEvent, + NativeScrollEvent, +} from "react-native"; import { PlaceInfoEntity } from "../../../domain/entities/place_info_entity"; import { useRef } from "react"; import { StateDataSource } from "../../../domain/datasources/state_datasource"; import { CarousselTile } from "./caroussel_tile"; import { router } from "expo-router"; +import { BLANK_ITEM_WIDTH, ITEM_WIDTH } from "../../constants/caroussel"; interface CarousselProps { data: PlaceInfoEntity[]; @@ -11,9 +20,6 @@ interface CarousselProps { onIndexChange?: (index: number) => void; } -const ITEM_WIDTH = Dimensions.get("window").width * 0.7; -const BLANK_ITEM_WIDTH = Dimensions.get("window").width * 0.15; - export const Caroussel = ({ data, onPress, onIndexChange }: CarousselProps) => { const scrollX = useRef(new Animated.Value(0)).current; const finalData = [ @@ -36,57 +42,58 @@ export const Caroussel = ({ data, onPress, onIndexChange }: CarousselProps) => { onIndexChange && onIndexChange(index); }); return ( - item.id.toString()} - horizontal - showsHorizontalScrollIndicator={false} - decelerationRate={0} - initialNumToRender={1} - - bounces={false} - snapToInterval={ITEM_WIDTH} - onScroll={Animated.event([{ nativeEvent: { contentOffset: { x: scrollX } } }], { - useNativeDriver: true, - })} - renderItem={({ item, index }) => { - if (item.id === -1 || item.id === -2) { - return ; - } - const inputRange = [ - (index - 2) * ITEM_WIDTH, - (index - 1) * ITEM_WIDTH, - index * ITEM_WIDTH, - ]; + item.id.toString()} + horizontal + showsHorizontalScrollIndicator={false} + decelerationRate={0} + initialNumToRender={1} + bounces={false} + snapToInterval={ITEM_WIDTH} + onScroll={Animated.event( + [{ nativeEvent: { contentOffset: { x: scrollX } } }], + { + useNativeDriver: true, + } + )} + renderItem={({ item, index }) => { + if (item.id === -1 || item.id === -2) { + return ; + } + const inputRange = [ + (index - 2) * ITEM_WIDTH, + (index - 1) * ITEM_WIDTH, + index * ITEM_WIDTH, + ]; - const translateY = scrollX.interpolate({ - inputRange, - outputRange: [100, 0, 100], - }); + const translateY = scrollX.interpolate({ + inputRange, + outputRange: [100, 0, 100], + }); - const opacity = scrollX.interpolate({ - inputRange, - outputRange: [0.6, 1, 0.6], - }); - const scale = scrollX.interpolate({ - inputRange, - outputRange: [0.8, 1, 0.8], - }); - return ( - { - if (item.id !== -1 && item.id !== -2) { - onPress && onPress(item.id); - } - } - } - /> - ); - }} - /> + const opacity = scrollX.interpolate({ + inputRange, + outputRange: [0.6, 1, 0.6], + }); + const scale = scrollX.interpolate({ + inputRange, + outputRange: [0.8, 1, 0.8], + }); + return ( + { + if (item.id !== -1 && item.id !== -2) { + onPress && onPress(item.id); + } + }} + /> + ); + }} + /> ); }; diff --git a/mobile/src/common/components/caroussel/caroussel_tile.tsx b/mobile/src/common/components/caroussel/caroussel_tile.tsx index 65a61985b1f67f3e6baf10c4c84eb179028374b0..e4caa023f7801776d0f44e65649a6bbcef75380a 100644 --- a/mobile/src/common/components/caroussel/caroussel_tile.tsx +++ b/mobile/src/common/components/caroussel/caroussel_tile.tsx @@ -9,6 +9,7 @@ import { } from "react-native"; import { PlaceInfoEntity } from "../../../domain/entities/place_info_entity"; import { Link } from "expo-router"; +import { ITEM_HEIGHT, ITEM_WIDTH } from "../../constants/caroussel"; interface CarousselTileProps { onPress?: () => void; @@ -18,10 +19,6 @@ interface CarousselTileProps { opacity: Animated.AnimatedInterpolation; } -const ITEM_WIDTH = Dimensions.get("window").width * 0.7; -const BLANK_ITEM_WIDTH = Dimensions.get("window").width * 0.15; -const ITEM_HEIGHT = Dimensions.get("window").height * 0.7; - export const CarousselTile = memo( ({ translateY, scale, opacity, item, onPress }: CarousselTileProps) => { return ( diff --git a/mobile/src/common/constants/caroussel.ts b/mobile/src/common/constants/caroussel.ts new file mode 100644 index 0000000000000000000000000000000000000000..b508be41a7e60094dd02ecc87175a3a008746ac0 --- /dev/null +++ b/mobile/src/common/constants/caroussel.ts @@ -0,0 +1,7 @@ +import { Dimensions } from "react-native"; + +export const CARROUSEL_HEIGHT = Dimensions.get("window").height * 0.5; +export const CARROUSEL_WIDTH = Dimensions.get("window").width; +export const ITEM_WIDTH = CARROUSEL_WIDTH * 0.8; +export const BLANK_ITEM_WIDTH = CARROUSEL_WIDTH * 0.1; +export const ITEM_HEIGHT = CARROUSEL_HEIGHT * 0.7; \ No newline at end of file diff --git a/mobile/src/common/contexts/data_context.tsx b/mobile/src/common/contexts/data_context.tsx index 85b25dee5c0735ca0f2359efbbf062b9703f4059..1dc6dc0878ea9a02eab65e062ebf765dd1c49009 100644 --- a/mobile/src/common/contexts/data_context.tsx +++ b/mobile/src/common/contexts/data_context.tsx @@ -43,37 +43,32 @@ const DataContext = createContext({ profileRepository: null }); +const getProductionContext = (language: string): DataContextType => { + return { + statesRepository: new StateRepositoryImpl(new StateDataSourceProd(language)), + authRepository: new AuthRepositoryImpl(new AuthDatasourceProd()), + activityRepository: new ActivityRepositoryDev(new ActivityDatasourceProd()), + travelRepository: new TravelRepositoryImpl(new TravelDatasourceDev()), + routeRepository: new RouteRepositoryImpl(new RouteDataSourceDev()), + profileRepository: new ProfileRepositoryImpl(new ProfileDataSourceProd(language)) + }; +} + +const getDevelopmentContext = (): DataContextType => { + return { + statesRepository: new StateRepositoryImpl(new StateDataSourceDev()), + authRepository: new AuthRepositoryImpl(new AuthDataSourceDev()), + activityRepository: new ActivityRepositoryDev(new ActivityDatasourceDev()), + travelRepository: new TravelRepositoryImpl(new TravelDatasourceDev()), + routeRepository: new RouteRepositoryImpl(new RouteDataSourceDev()), + profileRepository: new ProfileRepositoryImpl(new ProfileDataSourceDev()) + }; +} + export const DataContextProvider = ({ children }: DataContextProviderProps) => { const { i18n:{ language } } = useTranslation(); - const statesDataSource = new StateDataSourceDev(); - //const statesDataSource = new StateDataSourceProd(language); - const statesRepository = new StateRepositoryImpl(statesDataSource); - // - const authDataSource = new AuthDataSourceDev(); - //const authDataSource = new AuthDatasourceProd(); - const authRepository = new AuthRepositoryImpl(authDataSource); - // - const activityDataSource = new ActivityDatasourceDev(); - //const activityDataSource = new ActivityDatasourceProd(); - const activityRepository = new ActivityRepositoryDev(activityDataSource); - // - const travelDatasource = new TravelDatasourceDev(); - const travelRepository = new TravelRepositoryImpl(travelDatasource); - // - const routeDatasource = new RouteDataSourceDev(); - const routeRepository = new RouteRepositoryImpl(routeDatasource); - // - const profileDataSource = new ProfileDataSourceProd(); - const profileRepository = new ProfileRepositoryImpl(profileDataSource); - const value = { - statesRepository, - authRepository, - activityRepository, - travelRepository, - routeRepository, - profileRepository - }; + const value = getDevelopmentContext(); return ( 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 4fd826d2db91d43e9e67751b779fc1dec657aee3..ae97e81a2265e0dabfe3eab13f1ce7a1fe0172ef 100644 --- a/mobile/src/components/activity_bottom_sheet/activity_bottom_sheet.tsx +++ b/mobile/src/components/activity_bottom_sheet/activity_bottom_sheet.tsx @@ -58,7 +58,7 @@ export const ActivityBottomSheet = ({ item} ItemSeparatorComponent={() => } diff --git a/mobile/src/components/activity_tile/activity_tile.tsx b/mobile/src/components/activity_tile/activity_tile.tsx index bea8a19d35b63f186d85f39a36e0931a76a69c1c..73471a4d04cc6fcf419c3eaad585c9b1454449cf 100644 --- a/mobile/src/components/activity_tile/activity_tile.tsx +++ b/mobile/src/components/activity_tile/activity_tile.tsx @@ -1,6 +1,7 @@ import { View, Text, StyleSheet, Image, TouchableOpacity } from "react-native"; import { ActivityInfoEntity } from "../../domain/entities/activity_info_entity"; import { LIGHT_THEME } from "../../common/constants/theme"; +import { ScrollView } from "react-native-gesture-handler"; interface ActivityTileProps { activity: ActivityInfoEntity; @@ -21,6 +22,11 @@ export const ActivityTile = ({ activity, onPress }: ActivityTileProps) => { {activity.name} {activity.location} + + { + activity.tags?.map(tag => {tag}) + } + ); @@ -28,7 +34,7 @@ export const ActivityTile = ({ activity, onPress }: ActivityTileProps) => { const styles = StyleSheet.create({ container: { - height: 300, + height: 320, width: "100%", backgroundColor: LIGHT_THEME.color.white, elevation: 5, @@ -44,4 +50,13 @@ const styles = StyleSheet.create({ fontSize: 18, fontWeight: "bold", }, + tag: { + backgroundColor: LIGHT_THEME.color.primary, + color: LIGHT_THEME.color.white, + height: 30, + paddingVertical: 5, + paddingHorizontal: 10, + borderRadius: 15, + marginHorizontal: 5, + }, }); diff --git a/mobile/src/hooks/useAnimatedSelectedIndex.ts b/mobile/src/hooks/useAnimatedSelectedIndex.ts index 4f56c218173cc032ae861133406ee887351b5355..4c2f8549e5c195644ec5f9ce99ea117e063cfdd5 100644 --- a/mobile/src/hooks/useAnimatedSelectedIndex.ts +++ b/mobile/src/hooks/useAnimatedSelectedIndex.ts @@ -9,6 +9,8 @@ export const useAnimatedSelectedIndex = (startMilliseconds: number = 500, endMil const onIndexChange = useCallback((index: number) => { if (index !== selectedState.current) { + setTimeout(() => {}, 100); + if (index == selectedState.current) return; console.log("Index changed to: ", index); selectedState.current = index; Animated.timing(backgroundImageAnimation, { diff --git a/mobile/src/infrastructure/models/prod/activity_model.ts b/mobile/src/infrastructure/models/prod/activity_model.ts index 228e187d89a96d037f2fea9c2c01415ec13bf59a..6327e300c0e60ef1701279960257df88d62e49cc 100644 --- a/mobile/src/infrastructure/models/prod/activity_model.ts +++ b/mobile/src/infrastructure/models/prod/activity_model.ts @@ -10,4 +10,11 @@ export interface ActivityModel { closeAt: number; startDate: Date | null; endDate: Date | null; + categories?: TagModel[]; +} + +export interface TagModel { + idCategory: number; + language: string; + name: string; } diff --git a/mobile/src/infrastructure/utils/place_utils.ts b/mobile/src/infrastructure/utils/place_utils.ts index 3e16b116e32ad5376abe06599128da895d73aaca..d0937a3bccec62471452498556d67203651a16b9 100644 --- a/mobile/src/infrastructure/utils/place_utils.ts +++ b/mobile/src/infrastructure/utils/place_utils.ts @@ -10,6 +10,6 @@ export const placeModelToEntity = (placeModel: ActivityModel): ActivityInfoEntit location: placeModel.coords, townId: placeModel.idTown, available: 'open', - tags: undefined, + tags: placeModel.categories?.map(tag => tag.name) || undefined, } } \ No newline at end of file diff --git a/mobile/src/profile/domain/datasources/profile_datasource.ts b/mobile/src/profile/domain/datasources/profile_datasource.ts index 50ae766950dc075a071fabca060abc5eb284ca07..6a9e0a3af089a747874ee23433698e8610ded31e 100644 --- a/mobile/src/profile/domain/datasources/profile_datasource.ts +++ b/mobile/src/profile/domain/datasources/profile_datasource.ts @@ -2,6 +2,7 @@ import { IOption } from "../../../common/domain/entities/option"; export interface ProfileDataSource { getInterests: () => Promise; + getUserInterests: () => Promise; saveInterests: (options: IOption[]) => Promise; setUpProfile: (birthdate: string, interests: IOption[]) => Promise; changePassword: (oldPassword: string, newPassword: string) => Promise; diff --git a/mobile/src/profile/domain/repositories/profile_repository.ts b/mobile/src/profile/domain/repositories/profile_repository.ts index 98634810fd001fb0b1ee3e233bc24fbbce53d4f7..728307e27caa3e3783e4915cb399dc9db8aa0992 100644 --- a/mobile/src/profile/domain/repositories/profile_repository.ts +++ b/mobile/src/profile/domain/repositories/profile_repository.ts @@ -2,6 +2,7 @@ import { IOption } from "../../../common/domain/entities/option"; export interface ProfileRepository { getInterests: () => Promise; + getUserInterests: () => Promise; saveInterests: (options: IOption[]) => Promise; setUpProfile: (birthdate: string, interests: IOption[]) => Promise; changePassword: (oldPassword: string, newPassword: string) => Promise; diff --git a/mobile/src/profile/hooks/useChangeInterests.ts b/mobile/src/profile/hooks/useChangeInterests.ts new file mode 100644 index 0000000000000000000000000000000000000000..cb1449f7cfa192347ecdc2bce63d5cc8c3f54ebf --- /dev/null +++ b/mobile/src/profile/hooks/useChangeInterests.ts @@ -0,0 +1,33 @@ +import { useEffect, useState } from "react"; +import { useGetInterests } from "./useGetInterests"; +import { IOption } from "../../common/domain/entities/option"; +import { ApiRequestStatus } from "../../common/constants/api_request_states"; +import { useDataContext } from "../../common/contexts/data_context"; + + +export const useChangeInterests = () => { + const { profileRepository } = useDataContext(); + const [interests, setInterests] = useState([]); + + const toogleInterest = (id: number) => { + const newInterests = interests.map((interest) => { + if (interest.id === id) { + return { ...interest, isSelected: !interest.isSelected }; + } + return interest; + }); + setInterests(newInterests); + }; + + const onSubmit = async () => { + const selectedInterests = interests + .filter((interest) => interest.isSelected); + await profileRepository!.saveInterests(selectedInterests); + }; + + const setAllInterests = (interests: IOption[]) => { + setInterests(interests); + }; + + return { toogleInterest, interests, onSubmit, setAllInterests }; +}; diff --git a/mobile/src/profile/hooks/useGetUserInterests.ts b/mobile/src/profile/hooks/useGetUserInterests.ts new file mode 100644 index 0000000000000000000000000000000000000000..a14556c1acff6f3b1635389032299ca16a089afb --- /dev/null +++ b/mobile/src/profile/hooks/useGetUserInterests.ts @@ -0,0 +1,12 @@ +import { useDataContext } from "../../common/contexts/data_context" +import { IOption } from "../../common/domain/entities/option" +import { useGet } from "../../hooks/useGet" + +export const useGetUserInterests = () => { + const { profileRepository } = useDataContext() + const callback = async () => { + return profileRepository!.getUserInterests(); + } + const { data, requestStatus } = useGet(callback); + return { data, requestStatus }; +} \ No newline at end of file diff --git a/mobile/src/profile/hooks/useSelectInterests.ts b/mobile/src/profile/hooks/useSelectInterests.ts deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/mobile/src/profile/hooks/useSetUpProfile.ts b/mobile/src/profile/hooks/useSetUpProfile.ts index 9acb88383871614d8500d1efbb94b3e4f5ccf8b1..bc04cd8a3c9f83941598d5807696d0194770c103 100644 --- a/mobile/src/profile/hooks/useSetUpProfile.ts +++ b/mobile/src/profile/hooks/useSetUpProfile.ts @@ -6,6 +6,7 @@ import { ApiRequestStatus } from "../../common/constants/api_request_states"; import { useDataContext } from "../../common/contexts/data_context"; import { useSetUp } from "../../common/contexts/set_up_context"; import { router } from "expo-router"; +import { useChangeInterests } from "./useChangeInterests"; export type SetUpProfileFormValues = { interests: number[]; @@ -13,38 +14,34 @@ export type SetUpProfileFormValues = { }; export const useSetUpProfile = () => { - const { control, handleSubmit, setValue } = useForm(); - const { setFirstTime } = useSetUp(); - const { interests: allCategories, requestStatus } = useGetInterests(); - // TODO: Si se agregan mas campos que requieran datos de la API, debemos procurar que todos los request status esten en success - const [interests, setInterests] = useState([]); + const { interests: allCategories, requestStatus: changeInterestsRequest } = useGetInterests(); + const { interests, toogleInterest, onSubmit: onSubmitInterests, setAllInterests } = useChangeInterests(); + const [requestStatus, setRequestStatus] = useState(ApiRequestStatus.LOADING); + // const { control, handleSubmit, setValue, getValues } = useForm(); + const { setFirstTime } = useSetUp(); + useEffect(() => { - if (requestStatus === ApiRequestStatus.SUCCESS && allCategories && allCategories.length > 0) { - setInterests([...allCategories]); + const requests = [changeInterestsRequest]; + if (requests.every((request) => request === ApiRequestStatus.SUCCESS)) { + setAllInterests(allCategories!); + setRequestStatus(ApiRequestStatus.SUCCESS); } - }, [requestStatus]); - - const toogleInterest = (id: number) => { - console.log("toogle"); - const newInterests = interests.map((interest) => { - if (interest.id === id) { - return { ...interest, isSelected: !interest.isSelected }; - } - return interest; - }); - setInterests(newInterests); - }; + }, [changeInterestsRequest]); + const onSubmit = async () => { - setValue("interests", interests.filter((interest) => interest.isSelected).map((interest) => interest.id)); - await handleSubmit((data) => { - //TODO: Implementar la logica de envio de datos - //TODO: Verificar si se enviará la fecha de nacimiento y los intereses en el mismo request o por separado - console.log(data); - })(); - await setFirstTime(); - router.replace("(tabs)"); +/* await handleSubmit((data) => { + + })(); */ + + try { + await onSubmitInterests(); + await setFirstTime(); + router.replace("/(tabs)"); + } catch (error) { + console.log(error); + } }; - return { control, onSubmit, toogleInterest, interests, requestStatus }; + return { onSubmit, toogleInterest, interests, requestStatus }; }; diff --git a/mobile/src/profile/hooks/useUpdateInterests.ts b/mobile/src/profile/hooks/useUpdateInterests.ts new file mode 100644 index 0000000000000000000000000000000000000000..fdee8444c5bc4de5e9f41b846b3c60c8b25ee8e0 --- /dev/null +++ b/mobile/src/profile/hooks/useUpdateInterests.ts @@ -0,0 +1,51 @@ +import { useEffect, useState } from "react"; +import { useChangeInterests } from "./useChangeInterests"; +import { useGetUserInterests } from "./useGetUserInterests"; +import { ApiRequestStatus } from "../../common/constants/api_request_states"; +import { useGetInterests } from "./useGetInterests"; +import { router } from "expo-router"; + +export const useUpdateInterests = () => { + const { data: userCategories, requestStatus: userInterestsRequest } = + useGetUserInterests(); + const { interests: allCategories, requestStatus: changeInterestsRequest } = + useGetInterests(); + const { + interests, + toogleInterest, + onSubmit: onSubmitInterests, + setAllInterests, + } = useChangeInterests(); + const [requestStatus, setRequestStatus] = useState( + ApiRequestStatus.LOADING + ); + + useEffect(() => { + const requests = [changeInterestsRequest, userInterestsRequest]; + if (requests.every((request) => request === ApiRequestStatus.SUCCESS)) { + setAllInterests( + allCategories!.map((category) => ({ + ...category, + isSelected: userCategories!.some( + (userCategory) => userCategory.id === category.id + ), + })) + ); + setRequestStatus(ApiRequestStatus.SUCCESS); + } + }, [changeInterestsRequest, userInterestsRequest]); + + const onSubmit = async () => { + /* await handleSubmit((data) => { + + })(); */ + + try { + await onSubmitInterests(); + router.back(); + } catch (error) { + console.log(error); + } + }; + return { onSubmit, toogleInterest, interests, requestStatus }; +}; diff --git a/mobile/src/profile/infrastructure/datasources/dev/profile_datasource.ts b/mobile/src/profile/infrastructure/datasources/dev/profile_datasource.ts index f7e598af23a97f74f462b8167a28d66fedbf5288..863a94fef02a15d1a708398ac6cb5fbb81bbe66d 100644 --- a/mobile/src/profile/infrastructure/datasources/dev/profile_datasource.ts +++ b/mobile/src/profile/infrastructure/datasources/dev/profile_datasource.ts @@ -1,6 +1,13 @@ import { IOption } from '../../../../common/domain/entities/option'; import { ProfileDataSource } from '../../../domain/datasources/profile_datasource'; export class ProfileDataSourceDev implements ProfileDataSource { + async getUserInterests(): Promise { + const interests = await this.getInterests(); + interests[0].isSelected = true; + interests[1].isSelected = true; + interests[2].isSelected = true; + return interests; + } async getInterests(): Promise { return [ { id: 1, name: 'Tecnología', isSelected: false }, diff --git a/mobile/src/profile/infrastructure/datasources/prod/profile_datasource.ts b/mobile/src/profile/infrastructure/datasources/prod/profile_datasource.ts index eee7b181c9fb28d18f058986906113a78a0eb9ea..7384f5e73e5e6f898a62cad588776d6348853bdf 100644 --- a/mobile/src/profile/infrastructure/datasources/prod/profile_datasource.ts +++ b/mobile/src/profile/infrastructure/datasources/prod/profile_datasource.ts @@ -2,25 +2,65 @@ import axios from "axios"; import { IOption } from "../../../../common/domain/entities/option"; import { ProfileDataSource } from "../../../domain/datasources/profile_datasource"; import { API_URL } from "../../../../common/constants/api"; +import { CategoryModel } from "../../model/categorie_model"; +import { categoryModelToOption } from "../../utils/category"; export class ProfileDataSourceProd implements ProfileDataSource { - async getInterests(): Promise { - throw new Error('Method not implemented.'); + private readonly lang: string; + constructor(lang: string) { + this.lang = lang; + } + async getUserInterests(): Promise { + const {data, status} = await axios.get(`${API_URL}/user/prefered-categories?lang=${this.lang}`); + if (status !== 200) { + throw new Error("Error getting user interests"); } - - async saveInterests(options: IOption[]): Promise { - throw new Error('Method not implemented.'); + const categories = data.map(categoryModelToOption); + return categories.map((category) => ({ + ...category, + isSelected: true, + })); + } + async getInterests(): Promise { + //TODO: Implement this method + const { status, data } = await axios.get( + `${API_URL}/category?lang=${this.lang}` + ); + if (status !== 200) { + throw new Error("Error getting interests"); } + return data.map(categoryModelToOption); + } - async setUpProfile(birthdate: string, interests: IOption[]): Promise { - throw new Error('Method not implemented.'); - } - - async changePassword(oldPassword: string, newPassword: string): Promise { - const {status, data} = await axios.patch(`${API_URL}/user/change-password`, { - prevPassword: oldPassword, - newPassword: newPassword - }) - console.log(status, data); + async saveInterests(options: IOption[]): Promise { + const payload = { + idCategories: options.map((option) => option.id), + }; + const { status } = await axios.patch( + `${API_URL}/user/update-prefered-categories`, + payload + ); + + if (status !== 200) { + throw new Error("Error updating interests"); } + } + + async setUpProfile(birthdate: string, interests: IOption[]): Promise { + throw new Error("Method not implemented."); + } + + async changePassword( + oldPassword: string, + newPassword: string + ): Promise { + const { status, data } = await axios.patch( + `${API_URL}/user/change-password`, + { + prevPassword: oldPassword, + newPassword: newPassword, + } + ); + console.log(status, data); + } } diff --git a/mobile/src/profile/infrastructure/model/categorie_model.ts b/mobile/src/profile/infrastructure/model/categorie_model.ts new file mode 100644 index 0000000000000000000000000000000000000000..c46975097a5439d552106a590f7e1dfd2bcc54a9 --- /dev/null +++ b/mobile/src/profile/infrastructure/model/categorie_model.ts @@ -0,0 +1,4 @@ +export interface CategoryModel { + idCategory: number; + name: string; +} diff --git a/mobile/src/profile/infrastructure/repositories/profile_repository.ts b/mobile/src/profile/infrastructure/repositories/profile_repository.ts index 4dea398544246e0c303255c7fff907195e023e3c..8ca0c03334a4cab61fb81687396593a2d3ba08a0 100644 --- a/mobile/src/profile/infrastructure/repositories/profile_repository.ts +++ b/mobile/src/profile/infrastructure/repositories/profile_repository.ts @@ -6,6 +6,9 @@ export class ProfileRepositoryImpl implements ProfileRepository { constructor( private dataSource: ProfileDataSource ){} + getUserInterests(): Promise{ + return this.dataSource.getUserInterests(); + } async changePassword (oldPassword: string, newPassword: string): Promise { return this.dataSource.changePassword(oldPassword, newPassword); }; diff --git a/mobile/src/profile/infrastructure/utils/category.ts b/mobile/src/profile/infrastructure/utils/category.ts new file mode 100644 index 0000000000000000000000000000000000000000..2b51d315b5627bdb8245eb39326b9e6d7e6645e6 --- /dev/null +++ b/mobile/src/profile/infrastructure/utils/category.ts @@ -0,0 +1,8 @@ +import { IOption } from "../../../common/domain/entities/option"; +import { CategoryModel } from "../model/categorie_model"; + +export const categoryModelToOption = (category: CategoryModel): IOption => ({ + id: category.idCategory, + name: category.name, + isSelected: false, +}); diff --git a/mobile/src/profile/screens/change_interests_screen.tsx b/mobile/src/profile/screens/change_interests_screen.tsx index c08f54de835a14d60bef76b967a746322beb55c5..0f647bac691df27a9e6b7ebac19fa98fedd2426c 100644 --- a/mobile/src/profile/screens/change_interests_screen.tsx +++ b/mobile/src/profile/screens/change_interests_screen.tsx @@ -3,98 +3,30 @@ import { LIGHT_THEME } from "../../common/constants/theme"; import { ScrollView } from "react-native-gesture-handler"; import { useState } from "react"; import { MultipleOptionPicker } from "../components/multiple_option_pickeer"; +import { useUpdateInterests } from "../hooks/useUpdateInterests"; +import { ApiRequestStatus } from "../../common/constants/api_request_states"; +import { InterestSlide } from "../components/interest_slide"; +import { SlideControl } from "../../common/components/slide_control"; export const ChangeInterestsScreen = () => { - const [options, setOptions] = useState( [ - { - id: 1, - name: "Arquitectura", - isSelected: false, - }, - { - id: 2, - name: "Ciencia", - isSelected: false, - }, - { - id: 3, - name: "Cultura", - isSelected: false, - }, - { - id: 4, - name: "Deportes", - isSelected: false, - }, - { - id: 5, - name: "Economía", - isSelected: false, - }, - { - id: 6, - name: "Educación", - isSelected: false, - }, - { - id: 7, - name: "Entretenimiento", - isSelected: false, - }, - { - id: 8, - name: "Gastronomía", - isSelected: false, - }, - { - id: 9, - name: "Historia", - isSelected: false, - }, - { - id: 10, - name: "Moda", - isSelected: false, - }, - { - id: 11, - name: "Música", - isSelected: false, - }, - { - id: 12, - name: "Política", - isSelected: false, - }, - { - id: 13, - name: "Salud", - isSelected: false, - }, - { - id: 14, - name: "Tecnología", - isSelected: false, - }, - ]); + const { toogleInterest: onToogleOption, interests: options, requestStatus, onSubmit } = useUpdateInterests(); + if (requestStatus === ApiRequestStatus.LOADING) { + return Loading...; + } - const onToogleOption = (optionId: number) => { - setOptions( - options.map((option) => - option.id === optionId - ? { ...option, isSelected: !option.isSelected } - : option - ) - ); - }; + if (requestStatus === ApiRequestStatus.ERROR) { + return Error; + } + + const onFinish = () => { + onSubmit(); + } return ( - - {`Selecciona las categorías que te interesan, puedes cambiarlos en la pestaña de cuenta > Cambiar intereses`} - - + + {}} onPrevious={() => {}} isFirst isLast /> ); }; diff --git a/mobile/src/profile/screens/first_time_config_screen.tsx b/mobile/src/profile/screens/first_time_config_screen.tsx index d5fa60225e0417b2ea131b2bb5250099dec110a8..16b7bf2184421373c60b54387b82cf01daaabd6c 100644 --- a/mobile/src/profile/screens/first_time_config_screen.tsx +++ b/mobile/src/profile/screens/first_time_config_screen.tsx @@ -15,15 +15,13 @@ import { useSlideControl } from "../hooks/useSlideControl"; import { router } from "expo-router"; export const FirstTimeComfigScreen = () => { - const { onSubmit, toogleInterest, interests, control } = useSetUpProfile(); + const { onSubmit, toogleInterest, interests } = useSetUpProfile(); const onFinishCallback = () => { onSubmit(); }; - console.log('render'); const { onFinish, onNext, onPrevious, currentSlide } = useSlideControl(onFinishCallback); const slides = [ , - , ]; return ( diff --git a/mobile/src/screens/state_selection/state_selection_page.tsx b/mobile/src/screens/state_selection/state_selection_page.tsx index 358e2ab73c37f7e6b98548455ad796ee1ffdb58d..c46901e1061c92921716b1fd476c65cde50f3bb2 100644 --- a/mobile/src/screens/state_selection/state_selection_page.tsx +++ b/mobile/src/screens/state_selection/state_selection_page.tsx @@ -2,16 +2,24 @@ import { View, ActivityIndicator, Text } from "react-native"; import { pageStyles } from "../page_styles"; import { useGetStates } from "../../hooks/useGetStates"; import { ApiRequestStatus } from "../../common/constants/api_request_states"; -import { Caroussel } from "../../common/components/caroussel/caroussel"; +import { + Caroussel +} from "../../common/components/caroussel/caroussel"; import { router } from "expo-router"; import { useAnimatedSelectedIndex } from "../../hooks/useAnimatedSelectedIndex"; import { AnimatedBackground } from "../../common/components/animated_background"; import { ErrorPage } from "../error_page/error_page"; +import { LinearGradient } from "expo-linear-gradient"; +import { LIGHT_THEME } from "../../common/constants/theme"; +import { useDataContext } from "../../common/contexts/data_context"; +import { useAuth } from "../../auth/contexts/auth_context"; +import { CARROUSEL_HEIGHT, CARROUSEL_WIDTH } from "../../common/constants/caroussel"; export const StateSelectionPage = () => { const { data, requestStatus, refresh } = useGetStates(); const { selectedStateIndex, onIndexChange, backgroundImageAnimation } = useAnimatedSelectedIndex(200, 200); + const { user } = useAuth(); if (requestStatus === ApiRequestStatus.LOADING) { return ( @@ -22,9 +30,7 @@ export const StateSelectionPage = () => { } if (requestStatus === ApiRequestStatus.ERROR || !data) { - return ( - - ); + return ; } const handleOnPress = (id: number) => { @@ -32,16 +38,43 @@ export const StateSelectionPage = () => { }; return ( - - - + + + {`Hola, ${user?.name} ${user?.lastName}!`} + + + + + + ); };