-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #8 from rendercon/ft/speakerpage
Ft/speakerpage
- Loading branch information
Showing
11 changed files
with
398 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.