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;
+};