Loading mobile/src/activity/components/activity_bottom_sheet.tsx 0 → 100644 +139 −0 Original line number Diff line number Diff line import { useRef, useState } from "react"; import BottomSheet, { BottomSheetFlatList, BottomSheetScrollView, BottomSheetView, } from "@gorhom/bottom-sheet"; import { Text, StyleSheet, ScrollView, View, TouchableOpacity, } from "react-native"; import { LIGHT_THEME } from "../../common/constants/theme"; import { router } from "expo-router"; import { ActivityInfoEntity } from "../domain/entities/activity_info_entity"; interface ActivityBottomSheetProps { startSnapPoint: number; snapPoints: string[]; onSnapPointChange?: (index: number) => void; activity: ActivityInfoEntity; } export const ActivityBottomSheet = ({ activity, startSnapPoint, snapPoints, onSnapPointChange, }: ActivityBottomSheetProps) => { const sheetRef = useRef<BottomSheet>(null); const [isDescriptionExpanded, setIsDescriptionExpanded] = useState(false); return ( <BottomSheet ref={sheetRef} index={startSnapPoint} onChange={onSnapPointChange} snapPoints={snapPoints} > <BottomSheetScrollView style={{ height: "100%", padding: 10 }} scrollToOverflowEnabled nestedScrollEnabled > <View style={styles.activity_name_container}> <Text style={styles.name_text}>{activity.name}</Text> <TouchableOpacity onPress={() => { router.push("/scan"); }} style={styles.do_activity_button} > <Text style={styles.do_activity_text}>Do Activity</Text> </TouchableOpacity> </View> {activity.tags && ( <BottomSheetFlatList style={{ marginTop: 20, paddingVertical: 10 }} horizontal data={activity.tags} showsHorizontalScrollIndicator={false} keyExtractor={(item) => item} ItemSeparatorComponent={() => <View style={{ width: 10 }} />} renderItem={({ item }) => ( <View style={{ height: 40, borderRadius: 15, backgroundColor: "violet", paddingHorizontal: 10, justifyContent: "center", }} > <Text>{item}</Text> </View> )} /> )} <View style={styles.description_container}> <Text style={styles.description_text}>Description</Text> <Text numberOfLines={!isDescriptionExpanded ? 5 : undefined}> {activity.description} </Text> <TouchableOpacity onPress={() => setIsDescriptionExpanded(!isDescriptionExpanded)} > <Text style={styles.show_more_text}> {isDescriptionExpanded ? "Show less" : "Show more..."} </Text> </TouchableOpacity> </View> </BottomSheetScrollView> </BottomSheet> ); }; const styles = StyleSheet.create({ activity_info_container: { borderTopLeftRadius: 20, borderTopRightRadius: 20, borderWidth: 5, padding: 20, }, activity_name_container: { flexDirection: "row", justifyContent: "space-between", }, name_text: { fontSize: 24, fontWeight: "bold", width: "70%", }, do_activity_button: { height: 40, elevation: 5, backgroundColor: LIGHT_THEME.color.primary, padding: 10, borderRadius: 20, justifyContent: "center", alignItems: "center", paddingHorizontal: 20, }, do_activity_text: { color: LIGHT_THEME.color.white, fontWeight: "bold", }, description_container: { marginTop: 20, gap: 5, }, description_text: { fontSize: 18, fontWeight: "bold", }, show_more_text: { fontWeight: "600", }, }); mobile/src/activity/components/activity_tile.tsx 0 → 100644 +62 −0 Original line number Diff line number Diff line import { View, Text, StyleSheet, Image, TouchableOpacity } from "react-native"; import { LIGHT_THEME } from "../../common/constants/theme"; import { ScrollView } from "react-native-gesture-handler"; import { ActivityInfoEntity } from "../domain/entities/activity_info_entity"; interface ActivityTileProps { activity: ActivityInfoEntity; onPress?: (id: number) => void; } export const ActivityTile = ({ activity, onPress }: ActivityTileProps) => { return ( <TouchableOpacity onPress={() => { onPress && onPress(activity.id); }} activeOpacity={0.7} style={styles.container}> <View> <Image source={{ uri: activity.imageUri }} style={{ width: "100%", height: 200 }} /> </View> <View style={styles.info_container}> <Text style={styles.activity_name} >{activity.name}</Text> <Text>{activity.location}</Text> <ScrollView horizontal> { activity.tags?.map(tag => <Text key={tag} style={styles.tag}>{tag}</Text>) } </ScrollView> </View> </TouchableOpacity> ); }; const styles = StyleSheet.create({ container: { height: 320, width: "100%", backgroundColor: LIGHT_THEME.color.white, elevation: 5, borderWidth: 1, borderRadius: 10, overflow: "hidden", }, info_container: { padding: 10, gap: 5, }, activity_name: { 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, }, }); mobile/src/activity/domain/datasources/activity_datasource.ts 0 → 100644 +6 −0 Original line number Diff line number Diff line import { ActivityPlaceEntity } from "../entities/activity_place_entity"; export interface ActivityDataSource { getPlaceActivity(activityId: number, townId: number, stateId: number, placeNumber: number): Promise<ActivityPlaceEntity>; rankActivity(activityId: number, rank: number): Promise<void>; } No newline at end of file mobile/src/activity/domain/entities/activity_info_entity.ts 0 → 100644 +22 −0 Original line number Diff line number Diff line import { PlaceInfoEntity } from "../../../domain/entities/place_info_entity"; export interface ActivityInfoEntity extends PlaceInfoEntity { available: string; townId: number; location: string; tags?: string[]; } export interface ActivityRouteEntity extends PlaceInfoEntity { townId: number; location: string; coordinates: { latitude: number; longitude: number; }; tags?: string[]; startTime: Date; endTime: Date; done: boolean; } No newline at end of file mobile/src/activity/domain/entities/activity_place_entity.ts 0 → 100644 +19 −0 Original line number Diff line number Diff line 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 Loading
mobile/src/activity/components/activity_bottom_sheet.tsx 0 → 100644 +139 −0 Original line number Diff line number Diff line import { useRef, useState } from "react"; import BottomSheet, { BottomSheetFlatList, BottomSheetScrollView, BottomSheetView, } from "@gorhom/bottom-sheet"; import { Text, StyleSheet, ScrollView, View, TouchableOpacity, } from "react-native"; import { LIGHT_THEME } from "../../common/constants/theme"; import { router } from "expo-router"; import { ActivityInfoEntity } from "../domain/entities/activity_info_entity"; interface ActivityBottomSheetProps { startSnapPoint: number; snapPoints: string[]; onSnapPointChange?: (index: number) => void; activity: ActivityInfoEntity; } export const ActivityBottomSheet = ({ activity, startSnapPoint, snapPoints, onSnapPointChange, }: ActivityBottomSheetProps) => { const sheetRef = useRef<BottomSheet>(null); const [isDescriptionExpanded, setIsDescriptionExpanded] = useState(false); return ( <BottomSheet ref={sheetRef} index={startSnapPoint} onChange={onSnapPointChange} snapPoints={snapPoints} > <BottomSheetScrollView style={{ height: "100%", padding: 10 }} scrollToOverflowEnabled nestedScrollEnabled > <View style={styles.activity_name_container}> <Text style={styles.name_text}>{activity.name}</Text> <TouchableOpacity onPress={() => { router.push("/scan"); }} style={styles.do_activity_button} > <Text style={styles.do_activity_text}>Do Activity</Text> </TouchableOpacity> </View> {activity.tags && ( <BottomSheetFlatList style={{ marginTop: 20, paddingVertical: 10 }} horizontal data={activity.tags} showsHorizontalScrollIndicator={false} keyExtractor={(item) => item} ItemSeparatorComponent={() => <View style={{ width: 10 }} />} renderItem={({ item }) => ( <View style={{ height: 40, borderRadius: 15, backgroundColor: "violet", paddingHorizontal: 10, justifyContent: "center", }} > <Text>{item}</Text> </View> )} /> )} <View style={styles.description_container}> <Text style={styles.description_text}>Description</Text> <Text numberOfLines={!isDescriptionExpanded ? 5 : undefined}> {activity.description} </Text> <TouchableOpacity onPress={() => setIsDescriptionExpanded(!isDescriptionExpanded)} > <Text style={styles.show_more_text}> {isDescriptionExpanded ? "Show less" : "Show more..."} </Text> </TouchableOpacity> </View> </BottomSheetScrollView> </BottomSheet> ); }; const styles = StyleSheet.create({ activity_info_container: { borderTopLeftRadius: 20, borderTopRightRadius: 20, borderWidth: 5, padding: 20, }, activity_name_container: { flexDirection: "row", justifyContent: "space-between", }, name_text: { fontSize: 24, fontWeight: "bold", width: "70%", }, do_activity_button: { height: 40, elevation: 5, backgroundColor: LIGHT_THEME.color.primary, padding: 10, borderRadius: 20, justifyContent: "center", alignItems: "center", paddingHorizontal: 20, }, do_activity_text: { color: LIGHT_THEME.color.white, fontWeight: "bold", }, description_container: { marginTop: 20, gap: 5, }, description_text: { fontSize: 18, fontWeight: "bold", }, show_more_text: { fontWeight: "600", }, });
mobile/src/activity/components/activity_tile.tsx 0 → 100644 +62 −0 Original line number Diff line number Diff line import { View, Text, StyleSheet, Image, TouchableOpacity } from "react-native"; import { LIGHT_THEME } from "../../common/constants/theme"; import { ScrollView } from "react-native-gesture-handler"; import { ActivityInfoEntity } from "../domain/entities/activity_info_entity"; interface ActivityTileProps { activity: ActivityInfoEntity; onPress?: (id: number) => void; } export const ActivityTile = ({ activity, onPress }: ActivityTileProps) => { return ( <TouchableOpacity onPress={() => { onPress && onPress(activity.id); }} activeOpacity={0.7} style={styles.container}> <View> <Image source={{ uri: activity.imageUri }} style={{ width: "100%", height: 200 }} /> </View> <View style={styles.info_container}> <Text style={styles.activity_name} >{activity.name}</Text> <Text>{activity.location}</Text> <ScrollView horizontal> { activity.tags?.map(tag => <Text key={tag} style={styles.tag}>{tag}</Text>) } </ScrollView> </View> </TouchableOpacity> ); }; const styles = StyleSheet.create({ container: { height: 320, width: "100%", backgroundColor: LIGHT_THEME.color.white, elevation: 5, borderWidth: 1, borderRadius: 10, overflow: "hidden", }, info_container: { padding: 10, gap: 5, }, activity_name: { 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, }, });
mobile/src/activity/domain/datasources/activity_datasource.ts 0 → 100644 +6 −0 Original line number Diff line number Diff line import { ActivityPlaceEntity } from "../entities/activity_place_entity"; export interface ActivityDataSource { getPlaceActivity(activityId: number, townId: number, stateId: number, placeNumber: number): Promise<ActivityPlaceEntity>; rankActivity(activityId: number, rank: number): Promise<void>; } No newline at end of file
mobile/src/activity/domain/entities/activity_info_entity.ts 0 → 100644 +22 −0 Original line number Diff line number Diff line import { PlaceInfoEntity } from "../../../domain/entities/place_info_entity"; export interface ActivityInfoEntity extends PlaceInfoEntity { available: string; townId: number; location: string; tags?: string[]; } export interface ActivityRouteEntity extends PlaceInfoEntity { townId: number; location: string; coordinates: { latitude: number; longitude: number; }; tags?: string[]; startTime: Date; endTime: Date; done: boolean; } No newline at end of file
mobile/src/activity/domain/entities/activity_place_entity.ts 0 → 100644 +19 −0 Original line number Diff line number Diff line 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