diff --git a/app/(app)/session/[session].tsx b/app/(app)/session/[session].tsx index 497935e..f3d4776 100644 --- a/app/(app)/session/[session].tsx +++ b/app/(app)/session/[session].tsx @@ -1,40 +1,183 @@ -import { AntDesign } from '@expo/vector-icons'; +import { AntDesign, FontAwesome5, MaterialCommunityIcons } from '@expo/vector-icons'; import { useTheme } from '@react-navigation/native'; +import { Image } from 'expo-image'; import { Stack, useRouter, useSearchParams } from 'expo-router'; -import React from 'react'; -import { StyleSheet, View } from 'react-native'; +import React, { useState } from 'react'; +import { Pressable, StyleSheet, View } from 'react-native'; +import Row from '../../../components/common/Row'; +import Space from '../../../components/common/Space'; import StyledText from '../../../components/common/StyledText'; import MainContainer from '../../../components/container/MainContainer'; - -// TODO: Session page -/** - * - should show details of the event session: speaker name, title of session, description, level, speaker's twitter handle, and a floating action button to share the session info - */ -// TODO: Use real data from mock/sessions.ts +import { Sessions } from '../../../mock/sessions'; +import { getSessionTimesAndLocation, getTwitterHandle, truncate } from '../../../util/helpers'; const Session = () => { - const { colors } = useTheme(); + const { colors, dark } = useTheme(); const { slug } = useSearchParams(); const router = useRouter(); + // filter session by slug + const session = Sessions.data.filter((_session) => _session.slug === slug)[0]; + + const [showMoreBio, setShowMoreBio] = useState(false); + return ( - - router.back()} />, - }} - /> - - - session slug: {slug} - - + + + ( + router.back()} /> + ), + }} + /> + + + + + + + + + + {session?.speakers && session?.speakers.length > 1 ? 'Speakers' : 'Speaker'} + + + + {session?.speakers.map((speaker) => speaker.name).join(', ')} + + + {/** Bookmark session button. TODO: Add bookmark functionality */} + + + + + + + + + + {session?.title} + + + + + {session?.speakers && session?.speakers.length > 1 ? ( + session?.speakers.map((speaker, index) => ( + + + {speaker.name} - {''} + + {!showMoreBio ? truncate(140, speaker.biography) : speaker.biography} + {speaker.biography && speaker.biography.length > 140 && ( + setShowMoreBio(!showMoreBio)} + > + {showMoreBio ? ' ...Show less' : ' Show more'} + + )} + + + + + )) + ) : ( + + {!showMoreBio ? truncate(140, session?.speakers[0]?.biography) : session?.speakers[0]?.biography} + {session?.speakers[0]?.biography && session?.speakers[0]?.biography.length > 140 && ( + setShowMoreBio(!showMoreBio)} + > + {showMoreBio ? ' ...Show less' : ' Show more'} + + )} + + )} + + + + + + + + {getSessionTimesAndLocation(session?.slug || '')} + + + + + + #{session?.session_level} + + + + + + {/** TODO: Add twitter redirect functionality */} + {session?.speakers && session?.speakers.length > 1 ? ( + + Twitter Handles + + + + + {session?.speakers.map((speaker, index) => ( + + + + + {speaker.twitter ? getTwitterHandle(speaker.twitter) : 'N/A'} + + + ))} + + + ) : ( + + Twitter Handle + + + + + + {session?.speakers[0]?.twitter ? getTwitterHandle(session?.speakers[0]?.twitter) : 'N/A'} + + + + )} + + + + + + {/** Floating action button. TODO: Add share functionality */} + + + + ); }; @@ -43,6 +186,52 @@ export default Session; const styles = StyleSheet.create({ main: { flex: 1, + width: '100%', + marginBottom: 60, + }, + centered: { + paddingHorizontal: 16, + paddingVertical: 20, + borderBottomWidth: 0.5, + }, + row: { + flexDirection: 'row', + alignItems: 'center', + }, + image: { + height: 190, + width: '100%', + borderRadius: 10, + }, + chip: { paddingHorizontal: 10, + paddingVertical: 5, + borderRadius: 5, + alignSelf: 'flex-start', + }, + chipText: { + textTransform: 'uppercase', + }, + withPadding: { + padding: 20, + }, + button: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-evenly', + paddingHorizontal: 12, + paddingVertical: 8, + borderRadius: 8, + borderWidth: 1, + }, + fab: { + position: 'absolute', + bottom: 20, + right: 20, + width: 60, + height: 60, + borderRadius: 30, + justifyContent: 'center', + alignItems: 'center', }, }); diff --git a/components/lists/SessionsList.tsx b/components/lists/SessionsList.tsx index fc2ea55..3a7edba 100644 --- a/components/lists/SessionsList.tsx +++ b/components/lists/SessionsList.tsx @@ -4,9 +4,9 @@ import { useRouter } from 'expo-router'; import React from 'react'; import type { ListRenderItemInfo } from 'react-native'; import { Dimensions, FlatList, StyleSheet, TouchableWithoutFeedback, View } from 'react-native'; -import type { Session, SessionForSchedule } from '../../global'; -import { Schedule } from '../../mock/schedule'; +import type { Session } from '../../global'; import { Sessions } from '../../mock/sessions'; +import { getSessionTimeAndLocation, truncate } from '../../util/helpers'; import ViewAllButton from '../buttons/ViewAllButton'; import Row from '../common/Row'; import Space from '../common/Space'; @@ -20,29 +20,6 @@ const SessionsList = () => { const sessions = Sessions.data.slice(0, 5); // filter sessions to 5 const sessionCount = (Sessions.data.length - 5).toString(); - // truncate title to 2 lines, and add ellipsis at the end - const truncateTitle = (title: string) => { - const titleLength = title.length; - const maxTitleLength = 50; - if (titleLength > maxTitleLength) { - return `${title.substring(0, maxTitleLength)}...`; - } - return title; - }; - - // a function that gets the start time and room.title of a session from the schedule.data array - const getSessionTimeAndLocation = (slug: string) => { - for (const key in Schedule.data) { - const sessionData = Schedule.data[key]; - const session = sessionData?.find((item: SessionForSchedule) => item.slug === slug); - if (session) { - const startTime = session.start_time.split(':').slice(0, 2).join(':'); - return `@ ${startTime} | Room ${session?.rooms[0]?.title}`; - } - } - return ''; - }; - return ( @@ -62,12 +39,17 @@ const SessionsList = () => { onPress={() => router.replace({ pathname: `/session/${item.slug}`, params: { slug: item.slug } })} > - + - {truncateTitle(item.title)} + {truncate(50, item.title)} diff --git a/components/player/VideoPlayer.tsx b/components/player/VideoPlayer.tsx index 2094b69..271b143 100644 --- a/components/player/VideoPlayer.tsx +++ b/components/player/VideoPlayer.tsx @@ -1,4 +1,3 @@ -/* eslint-disable react/no-unstable-nested-components */ import { Ionicons } from '@expo/vector-icons'; import { useTheme } from '@react-navigation/native'; import type { VideoProps } from 'expo-av'; diff --git a/util/helpers.ts b/util/helpers.ts new file mode 100644 index 0000000..c9ecc69 --- /dev/null +++ b/util/helpers.ts @@ -0,0 +1,75 @@ +import type { SessionForSchedule } from '../global'; +import { Schedule } from '../mock/schedule'; + +/** + * a function to truncate text and add ellipsis + * @param limit number of characters to truncate + * @param text text to truncate + * @returns truncated text + */ +export const truncate = (limit: number, text?: string) => { + if (text && text.length > limit) { + return `${text.substring(0, limit)}...`; + } else { + return text; + } +}; + +/** + * a function that gets the start time and room.title of a session from the schedule.data array + * @param slug slug of session + * @returns start time and room.title + * @example getSessionTimeAndLocation('session-1') // returns @ 9:00 | Room 1 + */ +export const getSessionTimeAndLocation = (slug: string) => { + for (const key in Schedule.data) { + const sessionData = Schedule.data[key]; + const session = sessionData?.find((item: SessionForSchedule) => item.slug === slug); + if (session) { + const startTime = session.start_time.split(':').slice(0, 2).join(':'); + return `@ ${startTime} | Room ${session?.rooms[0]?.title}`; + } + } + return ''; +}; + +/** + * a function that gets the start time, end time, and room.title of a session from the schedule.data array + * @param _slug slug of session + * @returns start time, end time, and room.title + * @example getSessionTimesAndLocation('session-1') // returns 9:00 AM - 10:00 AM | Room 1 + */ +export const getSessionTimesAndLocation = (_slug: string) => { + for (const key in Schedule.data) { + const sessionData = Schedule.data[key]; + const sessionItem = sessionData?.find((item: SessionForSchedule) => item.slug === _slug); + + if (sessionItem) { + // convert time to 12 hour format and hh:mm aa + const startTime = new Date(sessionItem.start_date_time).toLocaleTimeString('en-US', { + hour: 'numeric', + minute: 'numeric', + hour12: true, + }); + const endTime = new Date(sessionItem.end_date_time).toLocaleTimeString('en-US', { + hour: 'numeric', + minute: 'numeric', + hour12: true, + }); + + return `${startTime} - ${endTime} | Room ${sessionItem?.rooms[0]?.title}`; + } + } + return ''; +}; + +/** + * function that returns twitter_handle of speaker given the twitter url + * @param url twitter url + * @returns twitter_handle + * @example getTwitterHandle('https://twitter.com/kharioki') // returns kharioki + */ +export const getTwitterHandle = (url?: string) => { + const twitterHandle = url?.split('/').pop(); + return twitterHandle; +};