Skip to content

Commit

Permalink
Merge pull request #8 from rendercon/ft/speakerpage
Browse files Browse the repository at this point in the history
Ft/speakerpage
  • Loading branch information
kharioki authored Sep 23, 2024
2 parents 063d8e7 + 31d778d commit 09a2ce2
Show file tree
Hide file tree
Showing 11 changed files with 398 additions and 14 deletions.
6 changes: 6 additions & 0 deletions app/(tabs)/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ export default function TabLayout() {
headerShown: false,
}}
/>
<Stack.Screen
name="speakers"
options={{
headerShown: false,
}}
/>
</Stack>
);
}
2 changes: 1 addition & 1 deletion app/(tabs)/home/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export default function TabLayout() {
<Tabs
screenOptions={{
tabBarStyle: {
backgroundColor: Colors.palette.primary + '80',
backgroundColor: Colors.palette.primary + 'cc',
borderTopColor: 'transparent',
position: 'absolute',
bottom: 0,
Expand Down
61 changes: 49 additions & 12 deletions app/(tabs)/home/speakers.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,65 @@
import { StyleSheet, View } from 'react-native';
import MainContainer from '@/components/containers/MainContainer';
import { FlatList, View, ActivityIndicator, StyleSheet } from 'react-native';
import Colors from '@/constants/Colors';
import SpeakerCard from '@/components/cards/SpeakerCard';
import { useFetchSpeakers } from '@/hooks/useFetchSpeakers';
import StyledText from '@/components/common/StyledText';
import { spacing } from '@/constants/Styles';
// import Colors from '@/constants/Colors';
import MainContainer from '@/components/containers/MainContainer';
import { sizes } from '@/constants/Styles';
import { useRouter } from 'expo-router';

const Speakers = () => {
const router = useRouter();
const { speakerList, loading, error } = useFetchSpeakers();

const home = () => {
return (
<MainContainer backgroundImage={require('@/assets/images/bg.png')} ImageBackgroundProps={{ resizeMode: 'cover' }}>
<MainContainer
backgroundImage={require('@/assets/images/bg.png')}
ImageBackgroundProps={{ resizeMode: 'cover' }}
preset="fixed"
safeAreaEdges={['top']}
>
<View style={styles.container}>
<StyledText size="lg" font="semiBold">
<StyledText size="xl" font="semiBold" style={styles.header}>
Speakers
</StyledText>
{loading ? (
<ActivityIndicator size="large" color={Colors.palette.secondary} />
) : error ? (
<StyledText variant="error" style={styles.error}>
Failed to load speakers. Please try again later.
</StyledText>
) : (
<FlatList
data={speakerList}
renderItem={({ item }) => (
<SpeakerCard speaker={item} onPress={() => router.push(`/speakers/${item.id}`)} />
)}
keyExtractor={(item) => item.id}
initialNumToRender={10}
maxToRenderPerBatch={10}
showsVerticalScrollIndicator={false}
/>
)}
</View>
</MainContainer>
);
};

export default home;

const styles = StyleSheet.create({
container: {
flex: 1,
paddingHorizontal: spacing.lg,
width: '100%',
paddingBottom: spacing.xl,
paddingTop: sizes.header,
paddingHorizontal: sizes.md,
paddingBottom: sizes.md,
},
header: {
color: Colors.palette.secondary,
marginVertical: sizes.md,
},
error: {
textAlign: 'center',
marginTop: 16,
},
});

export default Speakers;
184 changes: 184 additions & 0 deletions app/(tabs)/speakers/[id].tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
import { Image } from 'expo-image';
import { useLocalSearchParams, useRouter } from 'expo-router';
import { AntDesign, FontAwesome6 } from '@expo/vector-icons';
import { useMemo } from 'react';
import { View, StyleSheet, ActivityIndicator, Linking, Pressable } from 'react-native';
import MainContainer from '@/components/containers/MainContainer';
import Colors from '@/constants/Colors';
import { sizes, spacing } from '@/constants/Styles';
import StyledText from '@/components/common/StyledText';
import { useFetchSpeakers } from '@/hooks/useFetchSpeakers';

const SpeakerPage = () => {
const { id } = useLocalSearchParams();

const { speakerList, loading, error } = useFetchSpeakers();

const router = useRouter();

const speaker = useMemo(() => speakerList.find((s) => s.id === id), [speakerList, id]);

if (loading) {
return (
<MainContainer backgroundImage={require('@/assets/images/bg.png')} ImageBackgroundProps={{ resizeMode: 'cover' }}>
<ActivityIndicator size="large" color={Colors.palette.secondary} style={styles.loader} />
</MainContainer>
);
}

if (error) {
return (
<MainContainer backgroundImage={require('@/assets/images/bg.png')} ImageBackgroundProps={{ resizeMode: 'cover' }}>
<View style={styles.container}>
<StyledText style={styles.error}>Speaker not found</StyledText>
</View>
</MainContainer>
);
}

const openURL = (url: string) => {
Linking.openURL(url);
};

const renderLinkIcon = (linkType: string) => {
switch (linkType.toLowerCase()) {
case 'twitter':
return <FontAwesome6 name="x-twitter" size={36} color={Colors.palette.secondary} />;
case 'linkedin':
return <FontAwesome6 name="linkedin" size={36} color={Colors.palette.secondary} />;
default:
return <FontAwesome6 name="link" size={36} color={Colors.palette.secondary} />;
}
};

return (
<MainContainer
backgroundImage={require('@/assets/images/bg.png')}
ImageBackgroundProps={{ resizeMode: 'cover' }}
preset="scroll"
safeAreaEdges={['top']}
>
<View style={styles.container}>
<View style={styles.header}>
<Pressable
onPress={() => router.back()}
style={({ pressed }) => [
styles.backBtn,
{ backgroundColor: pressed ? Colors.palette.cardBg : 'transparent' },
]}
>
<View>
<AntDesign name="arrowleft" size={24} color={Colors.palette.secondary} />
</View>
</Pressable>
<View style={styles.name}>
<StyledText size="xl" font="bold" style={{ color: Colors.palette.secondary }}>
{speaker?.fullName}
</StyledText>
</View>
</View>

<View style={styles.top}>
<Image source={{ uri: speaker?.profilePicture }} style={styles.image} contentFit="cover" />
<StyledText size="md" style={styles.tagline}>
{speaker?.tagLine}
</StyledText>
</View>

<View style={styles.row}>
{speaker?.links.map((link, index) => (
<Pressable key={index} onPress={() => openURL(link.url)} style={styles.iconBtn}>
<View>{renderLinkIcon(link.linkType)}</View>
</Pressable>
))}
</View>

<StyledText style={styles.bio}>{speaker?.bio}</StyledText>

{speaker?.sessions.map((session, index) => (
<View key={index} style={styles.sessionCard}>
<StyledText size="lg" font="medium" style={{ color: Colors.palette.secondary }}>
{session?.name}
</StyledText>
</View>
))}
</View>
</MainContainer>
);
};

const styles = StyleSheet.create({
container: {
flex: 1,
padding: spacing.lg,
},
loader: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
error: {
color: Colors.palette.error,
textAlign: 'center',
},
header: {
width: '100%',
flexDirection: 'row',
alignItems: 'center',
marginBottom: spacing.lg,
},
backBtn: {
paddingVertical: spacing.sm,
paddingHorizontal: spacing.lg,
borderRadius: spacing.sm,
},
name: {
flex: 1,
alignItems: 'center',
},
top: {
alignItems: 'center',
marginBottom: sizes.lg,
},
image: {
width: sizes.cardImage,
height: sizes.cardImage,
borderRadius: spacing.sm,
marginBottom: spacing.lg,
},
tagline: {
color: Colors.palette.secondary,
},
row: {
flexDirection: 'row',
justifyContent: 'center',
flexWrap: 'wrap',
gap: sizes.md,
},
iconBtn: {
backgroundColor: Colors.palette.iconBg,
padding: sizes.sm,
borderColor: Colors.palette.border,
borderWidth: StyleSheet.hairlineWidth,
borderRadius: sizes.sm,
},
socialIcon: {
marginHorizontal: 15,
},
bio: {
fontSize: 14,
color: Colors.palette.text,
marginVertical: 20,
lineHeight: 22,
},
sessionCard: {
backgroundColor: Colors.palette.cardBg,
padding: spacing.lg,
borderRadius: sizes.sm,
marginBottom: spacing.lg,
borderWidth: StyleSheet.hairlineWidth,
borderColor: Colors.palette.border,
},
});

export default SpeakerPage;
15 changes: 15 additions & 0 deletions app/(tabs)/speakers/_layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from 'react';
import { Stack } from 'expo-router';

export default function TabLayout() {
return (
<Stack>
<Stack.Screen
name="[id]"
options={{
headerShown: false,
}}
/>
</Stack>
);
}
85 changes: 85 additions & 0 deletions components/cards/SpeakerCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { View, StyleSheet, Pressable } from 'react-native';
import { Speaker } from '../types';
import Colors from '@/constants/Colors';
import { sizes, spacing, blurhash } from '@/constants/Styles';
import StyledText from '../common/StyledText';
import { Image } from 'expo-image';

interface SpeakerCardProps {
speaker: Speaker;
onPress?: () => void;
}

const SpeakerCard: React.FC<SpeakerCardProps> = ({ speaker, onPress }) => {
return (
<Pressable
style={({ pressed }) => [
styles.card,
{ backgroundColor: pressed ? Colors.palette.primary : Colors.palette.cardBg },
]}
onPress={onPress}
>
{speaker.profilePicture ? (
<Image
source={{ uri: speaker.profilePicture }}
style={styles.image}
contentFit="cover"
placeholder={{ blurhash }}
transition={200}
/>
) : (
<View style={styles.imageFallback}>
<StyledText size="md" style={styles.imageFallbackText}>
No Image
</StyledText>
</View>
)}
<View style={styles.textContainer}>
<StyledText size="lg" font="bold" style={styles.name}>
{speaker.fullName}
</StyledText>
<StyledText size="sm">{speaker?.tagLine}</StyledText>
</View>
</Pressable>
);
};

const styles = StyleSheet.create({
card: {
borderColor: Colors.palette.border,
borderWidth: StyleSheet.hairlineWidth,
borderRadius: sizes.sm,
flexDirection: 'row',
alignItems: 'center',
padding: spacing.sm,
marginBottom: spacing.lg,
},
image: {
width: 80,
height: 80,
borderRadius: spacing.sm,
marginRight: spacing.lg,
backgroundColor: Colors.palette.primary,
},
imageFallback: {
width: 80,
height: 80,
borderRadius: spacing.sm,
marginRight: spacing.lg,
backgroundColor: Colors.palette.primary,
justifyContent: 'center',
alignItems: 'center',
},
imageFallbackText: {
color: Colors.palette.text,
},
textContainer: {
flex: 1,
gap: spacing.sm,
},
name: {
color: Colors.palette.secondary,
},
});

export default SpeakerCard;
2 changes: 1 addition & 1 deletion components/containers/MainContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ const MainContainer = (props: ScreenProps) => {
const styles = StyleSheet.create({
container: {
flex: 1,
paddingTop: Platform.OS === 'android' ? 60 : 40,
// paddingTop: Platform.OS === 'android' ? 60 : 40,
},
containerStyle: {
width: '100%',
Expand Down
Loading

0 comments on commit 09a2ce2

Please sign in to comment.