@expo/vector-icons#MaterialIcons TypeScript Examples
The following examples show how to use
@expo/vector-icons#MaterialIcons.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: index.tsx From selftrace with MIT License | 6 votes |
Icon = {
Question: (props: Props) => <FontAwesome name="question" size={25} {...props} />,
MapMarker: (props: Props) => <MaterialCommunityIcons name="map-marker" size={25} {...props} />,
Earth: (props: Props) => <MaterialCommunityIcons name="earth" size={25} {...props} />,
Lock: (props: Props) => <Foundation name="lock" size={25} {...props} />,
Form: (props: Props) => <AntDesign name="form" size={23} {...props} />,
Person: (props: Props) => <MaterialIcons name="person" size={25} {...props} />,
MapMarkerMultiple: (props: Props) => (
<MaterialCommunityIcons name="map-marker-multiple" size={23} {...props} />
),
}
Example #2
Source File: index.tsx From tiktok-clone with MIT License | 6 votes |
Inbox: React.FC = () => {
return (
<Container>
<Header>
<Title>All activity</Title>
<MaterialIcons name="arrow-drop-down" size={24} color="black" />
<Feather
style={{ position: 'absolute', right: 10, top: 10 }}
name="send"
size={24}
color="black"
/>
</Header>
</Container>
);
}
Example #3
Source File: index.tsx From lets-fork-native with MIT License | 6 votes |
export default function Card(props: Props): React.ReactElement {
const {
loading, restaurant, textOpacity, leftOpacity, rightOpacity,
} = props
return (
<View style={styles.container}>
{ !loading
? <Image style={styles.image} source={{ uri: restaurant.image_url, cache: 'force-cache' }} />
: null}
<View style={styles.overlay}>
<View style={styles.header}>
<Animated.View style={{ opacity: leftOpacity }}>
<MaterialIcons name="favorite" color={colors.red} size={64} />
</Animated.View>
<Animated.View style={{ opacity: rightOpacity }}>
<MaterialIcons name="close" color={colors.red} size={64} />
</Animated.View>
</View>
<View>
<Animated.View style={{ opacity: textOpacity }}>
<Text style={styles.name}>{restaurant.name}</Text>
</Animated.View>
<View style={styles.yelp}>
<Rating rating={restaurant.rating} size="lg" />
<FontAwesome name="yelp" size={32} color={colors.white} />
</View>
</View>
</View>
</View>
)
}
Example #4
Source File: HomeScreen.tsx From lets-fork-native with MIT License | 6 votes |
HomeScreen = React.memo((props: Props): React.ReactElement => {
const { navigation } = props
return (
<View style={styles.container}>
<View style={styles.headerContainer}>
<MaterialIcons style={styles.icon} name="restaurant" color={colors.white} size={40} />
<Text style={styles.header}>Let's Fork</Text>
</View>
<View>
<Button
color="white"
size="lg"
onPress={(): void => navigation.navigate('Create')}
>
CREATE A PARTY
</Button>
<Button
color="white"
size="lg"
onPress={(): void => navigation.navigate('Join')}
>
JOIN A PARTY
</Button>
</View>
</View>
)
})
Example #5
Source File: TutorialScreen.tsx From lets-fork-native with MIT License | 6 votes |
TutorialScreen = React.memo((props: Props): React.ReactElement => {
const { setShowApp } = props
const renderItem = (item: Item): React.ReactElement => (
<View style={styles.slide}>
<Text style={styles.text}>{item.item.text}</Text>
</View>
)
const onDone = (): void => {
setShowApp(true)
AsyncStorage.setItem('showApp', 'true')
}
return (
<View style={styles.container}>
<View style={styles.headerContainer}>
<MaterialIcons style={styles.icon} name="restaurant" color={colors.white} size={26} />
<RNText style={styles.header}>Let's Fork</RNText>
</View>
<AppIntroSlider
renderItem={renderItem}
data={slides}
onDone={onDone}
/>
</View>
)
})
Example #6
Source File: contact-row.tsx From beancount-mobile with MIT License | 6 votes |
export function ContactRow({
onPress,
name,
emailOrNumber,
selected,
}: ContactRowProps): JSX.Element {
const theme = useTheme().colorTheme;
const styles = getStyles(theme);
return (
<TouchableOpacity activeOpacity={0.9} onPress={onPress}>
<View style={styles.rowContainer}>
<MaterialIcons
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
name={`radio-button-${selected ? "" : "un"}checked`}
size={24}
color={theme.primary}
/>
<CommonMargin />
<View style={{ flex: 1 }}>
<Text style={styles.name}>{name || emailOrNumber}</Text>
{name.length > 0 && (
<Text style={styles.emailOrNum}> {emailOrNumber} </Text>
)}
</View>
</View>
</TouchableOpacity>
);
}
Example #7
Source File: ReportUnMatchButton.tsx From vsinder with Apache License 2.0 | 5 votes |
ReportUnMatchButton: React.FC<ReportUnMatchButtonProps> = ({}) => {
const { buttonBackground } = useTheme();
const { showActionSheetWithOptions } = useActionSheet();
const cache = useQueryCache();
const navigation = useNavigation();
const [mutate] = useMutation(defaultMutationFn, {
onSuccess: () => {
navigation.goBack();
cache.setQueryData<MatchesResponse>("/matches/0", (m) => {
return {
matches: m?.matches.filter((x) => x.userId !== params.id) || [],
};
});
},
});
const { params } = useRoute<MatchesStackNav<"messages">["route"]>();
return (
<ReportDialog
onReportMessage={(message) => {
mutate([
"/report",
{ message, unmatchOrReject: "unmatch", userId: params.id },
"POST",
]);
}}
>
{(setOpen) => (
<TouchableOpacity
style={{
flex: 1,
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
paddingRight: 15,
}}
onPress={() => {
const options = [
"Report user",
"Unmatch",
"Report a bug",
"Cancel",
];
const destructiveButtonIndex = 0;
const cancelButtonIndex = 3;
showActionSheetWithOptions(
{
options,
cancelButtonIndex,
destructiveButtonIndex,
},
(buttonIndex) => {
if (buttonIndex === 0) {
setOpen(true);
} else if (buttonIndex === 1) {
mutate([`/unmatch`, { userId: params.id }, "POST"]);
} else if (buttonIndex === 2) {
Linking.openURL("https://github.com/benawad/vsinder/issues");
}
}
);
}}
>
<MaterialIcons name="bug-report" size={27} color={buttonBackground} />
</TouchableOpacity>
)}
</ReportDialog>
);
}
Example #8
Source File: ReportUnMatchButton.tsx From vsinder-app with Apache License 2.0 | 5 votes |
ReportUnMatchButton: React.FC<ReportUnMatchButtonProps> = ({}) => {
const { buttonBackground } = useTheme();
const { showActionSheetWithOptions } = useActionSheet();
const cache = useQueryCache();
const navigation = useNavigation();
const [mutate] = useMutation(defaultMutationFn, {
onSuccess: () => {
navigation.goBack();
cache.setQueryData<MatchesResponse>("/matches/0", (m) => {
return {
matches: m?.matches.filter((x) => x.userId !== params.id) || [],
};
});
},
});
const { params } = useRoute<MatchesStackNav<"messages">["route"]>();
return (
<ReportDialog
onReportMessage={(message) => {
mutate([
"/report",
{ message, unmatchOrReject: "unmatch", userId: params.id },
"POST",
]);
}}
>
{(setOpen) => (
<TouchableOpacity
style={{
flex: 1,
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
paddingRight: 15,
}}
onPress={() => {
const options = ["Report", "Unmatch", "Cancel"];
const destructiveButtonIndex = 0;
const cancelButtonIndex = 2;
showActionSheetWithOptions(
{
options,
cancelButtonIndex,
destructiveButtonIndex,
},
(buttonIndex) => {
if (buttonIndex === 0) {
setOpen(true);
} else if (buttonIndex === 1) {
mutate([`/unmatch`, { userId: params.id }, "POST"]);
}
}
);
}}
>
<MaterialIcons name="bug-report" size={27} color={buttonBackground} />
</TouchableOpacity>
)}
</ReportDialog>
);
}
Example #9
Source File: index.tsx From tiktok-clone with MIT License | 5 votes |
Me: React.FC = () => {
return (
<Container>
<Header>
<AntDesign
style={{ position: 'absolute', left: 10, top: 10 }}
name="adduser"
size={24}
color="black"
/>
<Title>Matheus Castro</Title>
<MaterialIcons name="arrow-drop-down" size={24} color="black" />
<FontAwesome
style={{ position: 'absolute', right: 13, top: 12 }}
name="ellipsis-v"
size={24}
color="black"
/>
</Header>
<ScrollView>
<Content>
<Avatar source={avatar} />
<Username>@matheuscastroweb</Username>
<Stats>
<StatsColumn>
<StatsNumber>1950</StatsNumber>
<StatsText>Following</StatsText>
</StatsColumn>
<Separator>|</Separator>
<StatsColumn>
<StatsNumber>650</StatsNumber>
<StatsText>Followers</StatsText>
</StatsColumn>
<Separator>|</Separator>
<StatsColumn>
<StatsNumber>950</StatsNumber>
<StatsText>Likes</StatsText>
</StatsColumn>
</Stats>
<ProfileColumn>
<ProfileEdit>
<ProfileText>Edit profile</ProfileText>
</ProfileEdit>
<Bookmark name="bookmark" size={24} color="black" />
</ProfileColumn>
<StatsText>Tap to add bio</StatsText>
</Content>
</ScrollView>
</Container>
);
}
Example #10
Source File: index.tsx From lets-fork-native with MIT License | 5 votes |
export default function Menu(props: Props): React.ReactElement {
const { navigation } = props
const [open, setOpen] = React.useState(false)
return (
<TouchableOpacity
accessibilityRole="button"
style={styles.button}
onPress={(): void => setOpen(true)}
>
<MaterialIcons name="more-vert" color="black" size={24} />
<Modal
isVisible={open}
onBackdropPress={(): void => setOpen(false)}
animationIn="fadeIn"
animationOut="fadeOut"
backdropColor="transparent"
testID="modal"
>
<View style={styles.modal}>
<TouchableOpacity
accessibilityRole="button"
style={styles.item}
onPress={(): void => {
setOpen(false)
navigation.navigate('Share')
}}
>
{Platform.OS === 'ios' ? (
<Ionicons name="ios-share-outline" color="black" size={24} />
) : (
<MaterialIcons name="share" color="black" size={24} />
)}
<Text style={styles.text}>Share</Text>
</TouchableOpacity>
<TouchableOpacity
accessibilityRole="button"
style={styles.item}
onPress={(): void => {
setOpen(false)
navigation.navigate('Match')
}}
>
<MaterialIcons name="menu" color="black" size={24} />
<Text style={styles.text}>Matches</Text>
</TouchableOpacity>
</View>
</Modal>
</TouchableOpacity>
)
}
Example #11
Source File: index.tsx From lets-fork-native with MIT License | 5 votes |
export default function MultiSelect(props: Props): React.ReactElement {
const { handleSelect, items } = props
const [open, setOpen] = React.useState(false)
const [selected, setSelected] = React.useState<string[]>([])
const handlePress = (id: string): void => {
let sel = [...selected]
if (sel.includes(id)) {
sel = selected.filter((i) => i !== id)
} else {
sel = [...selected, id]
}
setSelected(sel)
handleSelect(sel)
}
return (
<View>
<Modal isVisible={open} onBackdropPress={(): void => setOpen(false)} testID="modal">
<ScrollView style={styles.scroll}>
{
items.map((c) => (
<View key={c.id} style={styles.item}>
<TouchableOpacity
accessibilityRole="button"
onPress={(): void => handlePress(c.id)}
>
<Text
style={{
...styles.itemText,
color: selected.includes(c.id) ? 'black' : '#808080',
}}
>
{c.name}
</Text>
</TouchableOpacity>
{
selected.includes(c.id)
&& <Feather name="check" size={20} color="black" />
}
</View>
))
}
</ScrollView>
</Modal>
<TouchableOpacity
accessibilityRole="button"
style={styles.openButton}
onPress={(): void => setOpen(true)}
>
<Text style={styles.openText}>
{ selected.length
? `${selected.length} ${selected.length === 1 ? 'category' : 'categories'} selected`
: 'Filter by Categories (Optional)'}
</Text>
<MaterialIcons name="arrow-right" size={26} />
</TouchableOpacity>
</View>
)
}
Example #12
Source File: index.tsx From lets-fork-native with MIT License | 5 votes |
// Initially shown while waiting for users to join party
// But can also be accessed through Menu for additional
// users to join after party has started
export default function Share(props: Props): React.ReactElement {
const [loading, setLoading] = React.useState(false)
const { party, ws } = props
const headerHeight = useHeaderHeight()
const viewHeight = env.ADS ? height - headerHeight - 50 : height - headerHeight
const handlePress = (): void => {
Alert.alert(
'',
'No matches will be shown until another user joins your party',
[
{
text: 'OK',
onPress: (): void => {
if (ws) {
ws.send(JSON.stringify({ type: 'start-swiping' }))
setLoading(true)
}
},
},
],
{ cancelable: true },
)
}
if (loading) {
return (
<View
style={{
...styles.container,
height: viewHeight,
}}
>
<ActivityIndicator size="large" />
</View>
)
}
return (
<View
style={{
...styles.container,
height: viewHeight,
}}
>
<Text style={styles.text}>Share this code with friends to have them join your party.</Text>
<Text style={styles.code}>{party.id}</Text>
<QRCode
size={200}
// value={`http://192.168.178.76:8003/party/${party.id}`}
value={`https://letsfork.app/party/${party.id}`}
/>
{
party.status === 'waiting'
&& <Button color="purple" size="lg" onPress={(): void => handlePress()}>START SWIPING</Button>
}
<TouchableOpacity
accessibilityRole="button"
onPress={(): Promise<ShareAction> => RNShare.share(
{ message: `Join my party on Let's Fork by clicking this link:\nhttps://letsfork.app/party/${party.id}\n\nor by opening the app and entering the code ${party.id}` },
)}
>
{Platform.OS === 'ios' ? (
<Ionicons name="ios-share-outline" size={32} />
) : (
<MaterialIcons name="share" size={32} />
)}
</TouchableOpacity>
</View>
)
}
Example #13
Source File: MessagesScreen.tsx From vsinder with Apache License 2.0 | 4 votes |
MessagesScreen: React.FC<MatchesStackNav<"messages">> = ({
route: { params },
navigation,
}) => {
const qKey = `/messages/${params.id}`;
const {
data,
isLoading,
isFetchingMore,
fetchMore,
} = useInfiniteQuery<IMessageResponse>(
qKey,
(key, cursor = "") =>
defaultQueryFn(`${key}/${cursor}`).then((x) => ({
hasMore: x.hasMore,
messages: x.messages.map((m: Message) =>
messageToGiftedMessage(m, { me: meData!.user!, them: params })
),
})),
{
staleTime: 0,
getFetchMore: ({ messages, hasMore }) =>
hasMore && messages.length
? messages[messages.length - 1].createdAt
: "",
}
);
const { data: meData } = useQuery<MeResponse>("/me", defaultQueryFn);
const cache = useQueryCache();
const [mutate] = useMutation(defaultMutationFn);
const {
inputForeground,
inputBackground,
buttonBackground,
buttonForeground,
buttonSecondaryBackground,
buttonSecondaryForeground,
buttonForegroundDarker,
buttonSecondaryForegroundDarker,
} = useTheme();
useOnWebSocket((e) => {
if (e.type === "new-message" && e.message.senderId === params.id) {
cache.setQueryData<IMessageResponse[]>(qKey, (d) => {
return produce(d!, (x) => {
x[0].messages = GiftedChat.append(x[0].messages, [
messageToGiftedMessage(e.message, {
me: meData!.user!,
them: params,
}),
]);
});
});
} else if (e.type === "unmatch") {
if (e.userId === params.id) {
navigation.goBack();
}
}
});
useEffect(() => {
getSocket().send(
JSON.stringify({ type: "message-open", userId: params.id })
);
const d = cache.getQueryData<MatchesResponse>("/matches/0");
if (d && d.matches.find((x) => x.userId === params.id && !x.read)) {
cache.setQueryData<MatchesResponse>("/matches/0", {
matches: d.matches.map((m) =>
m.userId === params.id ? { ...m, read: true } : m
),
});
}
return () => {
getSocket().send(JSON.stringify({ type: "message-open", userId: null }));
};
}, []);
if (isLoading) {
return <FullscreenLoading />;
}
if (!meData?.user) {
return null;
}
const messages = data ? data.map((x) => x.messages).flat() : [];
return (
<ScreenWrapper noPadding>
<GiftedChat
alignTop
loadEarlier={data?.[data?.length - 1]?.hasMore}
onPressAvatar={(u) =>
navigation.navigate("viewCard", { id: u._id.toString() })
}
isLoadingEarlier={!!isFetchingMore}
renderLoadEarlier={({ isLoadingEarlier }) =>
isLoadingEarlier ? (
<Loading />
) : (
<MyButton onPress={() => fetchMore()}>load more</MyButton>
)
}
listViewProps={{
showsVerticalScrollIndicator: false,
}}
timeTextStyle={{
left: { color: buttonSecondaryForegroundDarker },
right: { color: buttonForegroundDarker },
}}
renderBubble={(props) => {
return (
<Bubble
{...props}
textStyle={{
right: {
color: buttonForeground,
},
left: {
color: buttonSecondaryForeground,
},
}}
wrapperStyle={{
left: {
backgroundColor: buttonSecondaryBackground,
},
right: {
backgroundColor: buttonBackground,
},
}}
/>
);
}}
renderSend={(props) => (
<Send
{...props}
containerStyle={{
justifyContent: "center",
alignItems: "center",
alignSelf: "center",
marginRight: 15,
}}
>
<MaterialIcons name="send" size={24} color={buttonBackground} />
</Send>
)}
// @ts-ignore
containerStyle={{
backgroundColor: inputBackground,
}}
textInputStyle={{
color: inputForeground,
backgroundColor: inputBackground,
}}
// @ts-ignore
renderTicks={() => null}
messages={messages}
onSend={(messages) => {
messages.forEach((m) => {
mutate([
"/message",
{ recipientId: params.id, text: m.text, matchId: params.matchId },
"POST",
]).then(({ message: newMessage }) => {
cache.setQueryData<IMessageResponse[]>(qKey, (d) => {
return produce(d!, (x) => {
x[0].messages = GiftedChat.append(x[0].messages, [
messageToGiftedMessage(newMessage, {
me: meData.user!,
them: params,
}),
]);
});
});
const d = cache.getQueryData<MatchesResponse>("/matches/0");
if (d) {
cache.setQueryData<MatchesResponse>("/matches/0", {
matches: d.matches.map((m) =>
m.userId === params.id
? {
...m,
message: {
text: newMessage.text,
createdAt: newMessage.createdAt,
},
}
: m
),
});
}
});
});
}}
user={{
_id: meData.user.id,
}}
/>
</ScreenWrapper>
);
}
Example #14
Source File: App.tsx From lets-fork-native with MIT License | 4 votes |
export default function App(): React.ReactElement {
const [showApp, setShowApp] = React.useState(false)
const [fontsLoaded] = useFonts({ VarelaRound_400Regular })
const [loading, setLoading] = React.useState<boolean>(true)
const [location, setLocation] = React.useState<Location.LocationObject>()
const [party, setParty] = React.useState<Party>({} as Party)
const linking = {
prefixes: ['https://letsfork.app', 'letsfork://', 'exp://192.168.178.76:19000/+'],
config: {
screens: {
Party: 'party/:id',
},
},
}
React.useEffect(() => {
// Keep track of current matches
let matches: Restaurant[] = []
ws.onopen = (): void => {
console.log('opened')
}
ws.onmessage = (msg): void => {
console.log(msg.data)
const data: Party = JSON.parse(msg.data)
const newMatches = JSON.stringify(data.matches?.map((r) => r.id).sort())
const oldMatches = JSON.stringify(matches?.map((r) => r.id).sort())
// Alert when there are new matches
if (data.matches?.length
&& oldMatches !== newMatches) {
matches = data.matches
Alert.alert(
'You have a new match!',
'Click the icon in the top right to view your matches',
)
}
setParty(data)
}
ws.onclose = (msg): void => {
console.log('closed', msg.reason)
}
ws.onerror = (err): void => {
console.log('websocket error:', err)
}
}, [])
const loadApplicationAsync = async (): Promise<void> => {
const { status } = await Location.requestPermissionsAsync()
if (status !== 'granted') {
console.log('Permission to access location was denied')
}
const [loc, val] = await Promise.all([
Location.getCurrentPositionAsync({}),
AsyncStorage.getItem('showApp'),
])
if (loc) setLocation(loc)
// User has seen intro
if (val) setShowApp(true)
}
if (loading || !fontsLoaded) {
return (
<AppLoading
startAsync={loadApplicationAsync}
onFinish={(): void => setLoading(false)}
onError={console.warn}
/>
)
}
if (!showApp) {
return <TutorialScreen setShowApp={setShowApp} />
}
return (
<NavigationContainer linking={linking} fallback={<Text>Loading...</Text>}>
<Stack.Navigator initialRouteName="Home">
<Stack.Screen
name="Create"
options={(): object => ({
headerTitle: (): null => null,
})}
>
{(props): React.ReactElement => (
<CreateScreen
{...props}
ws={ws}
location={location}
/>
)}
</Stack.Screen>
<Stack.Screen
name="Home"
component={HomeScreen}
options={(): object => ({
headerShown: false,
})}
/>
<Stack.Screen
name="Join"
options={(): object => ({
headerTitle: (): null => null,
})}
>
{(props): React.ReactElement => <JoinScreen {...props} ws={ws} />}
</Stack.Screen>
<Stack.Screen
name="Match"
options={(): object => ({
headerTitle: (): null => null,
})}
>
{(props): React.ReactElement => <MatchScreen {...props} party={party} />}
</Stack.Screen>
<Stack.Screen
name="Party"
options={({ navigation }): object => ({
gestureEnabled: false,
headerTitle: (): null => null,
headerLeft: (): React.ReactElement => (
<TouchableOpacity
style={styles.backButton}
onPress={(): void => Alert.alert(
'Are you sure you want to exit?',
'Exiting will make you lose all data in this party',
[
{ text: 'Cancel' },
{
text: 'OK',
onPress: (): void => {
ws.send(JSON.stringify({ type: 'quit' }))
navigation.navigate('Home')
setParty({} as Party)
},
},
],
{ cancelable: true },
)}
>
<MaterialIcons name="close" color="black" size={24} />
</TouchableOpacity>
),
headerRight: (): React.ReactElement | null => (
party.status === 'active'
? <Menu navigation={navigation} />
: null
),
})}
>
{(props): React.ReactElement => (
<PartyScreen
{...props}
ws={ws}
party={party}
setParty={setParty}
/>
)}
</Stack.Screen>
<Stack.Screen
name="Restaurant"
component={RestaurantScreen}
options={(): object => ({
headerTitle: (): null => null,
})}
/>
<Stack.Screen
name="Share"
options={(): object => ({
headerTitle: (): null => null,
})}
>
{(props): React.ReactElement => (
<ShareScreen {...props} party={party} />
)}
</Stack.Screen>
</Stack.Navigator>
</NavigationContainer>
)
}
Example #15
Source File: index.tsx From lets-fork-native with MIT License | 4 votes |
export default function Details(props: Props): React.ReactElement {
const headerHeight = useHeaderHeight()
const { restaurant: defaultRestaurant, photos } = props
const [restaurant, setRestaurant] = React.useState(defaultRestaurant)
React.useEffect(() => {
const fetchData = async (): Promise<void> => {
// More details about the restaurant can be fetched from
// the server. This can be triggered off a feature flag in the future.
// For the time being this saves on api requests to yelp.
if (false) { // eslint-disable-line
try {
const rest = await getRestaurant(defaultRestaurant.id)
setRestaurant({
...rest,
...defaultRestaurant,
})
} catch (err) {
console.log(err)
}
} else {
setRestaurant(defaultRestaurant)
}
}
fetchData()
}, [defaultRestaurant])
const imageHeight = env.ADS
? height - headerHeight - 50
: height - headerHeight
const images = [(
<Image
key={restaurant.image_url}
style={{
...styles.image,
height: imageHeight,
}}
source={{ uri: restaurant.image_url, cache: 'force-cache' }}
/>
)]
if (restaurant.photos?.length) {
restaurant.photos.forEach((url) => {
if (url !== restaurant.image_url) {
images.push(
<Image
key={url}
style={{
...styles.image,
height: imageHeight,
}}
source={{ uri: url, cache: 'force-cache' }}
/>,
)
}
})
}
return (
<View
style={{
...styles.container,
minHeight: (height - headerHeight) * 0.8,
}}
>
{
photos ? (
<ScrollView
horizontal
alwaysBounceHorizontal={false}
showsHorizontalScrollIndicator
scrollEventThrottle={10}
pagingEnabled
onScroll={
Animated.event(
[{ nativeEvent: { contentOffset: { x: new Animated.Value(0) } } }],
{ useNativeDriver: true },
)
}
>
{images}
</ScrollView>
) : null
}
<View>
<Text style={[styles.text, styles.name]}>{restaurant.name}</Text>
<View style={styles.rating}>
<Rating rating={restaurant.rating} size="sm" />
<Text style={styles.text}>{`• ${restaurant.review_count} reviews`}</Text>
</View>
<Text
style={styles.text}
>
{
restaurant.price
? `${restaurant.price} • ${restaurant?.categories?.map((c) => c.title).join(', ')}`
: restaurant?.categories?.map((c) => c.title).join(', ')
}
</Text>
{ restaurant?.transactions?.length
? (
<Text style={styles.text}>
{restaurant.transactions.map((tran) => `${tran[0].toUpperCase()}${tran.replace('_', ' ').substring(1)}`).join(' • ')}
</Text>
) : null}
</View>
<View style={styles.section}>
<TouchableOpacity onPress={(): void => call(restaurant.display_phone)}>
<MaterialIcons name="phone" size={32} />
</TouchableOpacity>
<TouchableOpacity onPress={(): Promise<any> => Linking.openURL(restaurant.url)}>
<FontAwesome name="yelp" size={32} color={colors.yelpRed} />
</TouchableOpacity>
</View>
{
restaurant?.coordinates?.latitude && restaurant?.coordinates?.longitude
? (
<View style={styles.mapContainer}>
<MapView
region={{
latitude: restaurant.coordinates.latitude,
longitude: restaurant.coordinates.longitude,
latitudeDelta: 0.005,
longitudeDelta: 0.05,
}}
style={styles.map}
rotateEnabled={false}
scrollEnabled={false}
zoomEnabled={false}
>
<Marker
coordinate={{
latitude: restaurant.coordinates.latitude,
longitude: restaurant.coordinates.longitude,
}}
title={restaurant.name}
/>
</MapView>
</View>
) : null
}
<TouchableOpacity
style={styles.section}
onPress={(): void => {
const url = Platform.select({
ios: `maps:0,0?q=${restaurant.coordinates.latitude},${restaurant.coordinates.longitude}`,
android: `geo:0,0?q=${restaurant.coordinates.latitude},${restaurant.coordinates.longitude}`,
})
if (url) {
Linking.openURL(url)
}
}}
>
<Text style={styles.directionText}>Get Directions</Text>
<MaterialIcons name="directions" size={32} />
</TouchableOpacity>
{
restaurant.hours
? <Hours hours={restaurant.hours} />
: null
}
</View>
)
}
Example #16
Source File: invite-screen.tsx From beancount-mobile with MIT License | 4 votes |
export function InviteScreen(props: Props) {
React.useEffect(() => {
async function init() {
await analytics.track("page_view_invite", {});
}
init();
}, []);
const theme = useTheme().colorTheme;
const styles = getStyles(theme);
const contacts = useContacts();
const [selectedContacts, setSelectedContacts] = React.useState<RowItem[]>([]);
const [keyword, setKeyword] = React.useState("");
const sections = React.useMemo(() => {
// @ts-ignore
return Object.entries(
groupBy(
// Create one contact per phone number and email.
contacts.data.reduce((res, cur) => {
if (cur.phoneNumbers != null) {
for (const p of cur.phoneNumbers) {
res.push({
id: cur.id + p.number,
name: cur.name || "",
phoneNumber: p.number,
});
}
}
if (cur.emails != null) {
for (const e of cur.emails) {
res.push({
id: cur.id + e.email,
name: cur.name || "",
email: e.email,
});
}
}
return res;
}, [] as Array<RowItem>),
(c: RowItem) => {
const firstChar = (c.name.charAt(0) || "#").toLowerCase();
return firstChar.match(/[a-z]/) ? firstChar : "#";
}
)
)
.map(([key, value]: [string, RowItem[]]) => ({
key,
data: value.sort((a, b) =>
(a.name || a.name || "") < (b.name || b.name || "") ? -1 : 1
),
}))
.sort((a: { key: string }, b: { key: string }) =>
a.key < b.key ? -1 : 1
);
}, [contacts.data]);
const filteredSection = React.useMemo(() => {
if (keyword.length > 0) {
const filteredSections = new Array<SectionItem>();
for (const s of sections) {
const filteredData = s.data.filter(
(d) =>
d.name.indexOf(keyword) >= 0 ||
(d.email && d.email.indexOf(keyword) >= 0) ||
(d.phoneNumber && d.phoneNumber.indexOf(keyword) >= 0)
);
if (filteredData.length > 0) {
filteredSections.push({ key: s.key, data: filteredData });
}
}
return filteredSections;
}
return sections;
}, [sections, keyword]);
const onInvitePress = async () => {
const { shareLink } = props.route.params;
let didShare = false;
const message = `${i18n.t("recommend")} ${shareLink}`;
const emails = selectedContacts
.filter((c) => c.email != null)
.map((c) => c.email) as string[];
const phoneNumbers = selectedContacts
.filter((c) => c.phoneNumber != null)
.map((c) => c.phoneNumber) as string[];
if (emails.length > 0) {
try {
const result = await composeAsync({
recipients: emails,
subject: "beancount.io",
body: message,
isHtml: false,
});
didShare = didShare || result.status === "sent";
} catch (ex) {
Toast.fail(ex.message);
}
}
if (phoneNumbers.length > 0 && (await isAvailableAsync())) {
try {
const result = await sendSMSAsync(phoneNumbers, message);
didShare = didShare || result.result === "sent";
} catch (ex) {
Toast.fail(ex.message);
}
}
if (didShare) {
Toast.show(i18n.t("thanksShare"));
await analytics.track("tap_invite", { shareLink, selectedContacts });
}
};
const renderBody = () => {
if (contacts.loading) {
return (
<View style={styles.bodyContainer}>
<ActivityIndicator size="large" color={theme.primary} />
<CommonMargin />
<Text style={styles.loadingOrErrText}>{i18n.t("loading")}</Text>
</View>
);
}
if (contacts.error != null) {
const errMsg =
String(contacts.error.message).indexOf("permission") >= 0
? i18n.t("noContactPermission")
: String(contacts.error.message);
return (
<View style={styles.bodyContainer}>
<MaterialIcons name="error" size={48} color={theme.primary} />
<CommonMargin />
<Text style={styles.loadingOrErrText}>{errMsg}</Text>
</View>
);
}
return (
<View style={styles.flex1}>
<SearchBar
styles={{
wrapper: {
backgroundColor: theme.white,
},
}}
style={styles.searchInput}
placeholder={i18n.t("inputKeyword")}
value={keyword}
onCancel={() => {
setKeyword("");
}}
onChange={(val) => {
setKeyword(val);
}}
/>
<SectionList
showsVerticalScrollIndicator={false}
bounces={false}
sections={filteredSection}
renderSectionHeader={({ section }) => (
<View style={styles.sectionHeaderContainer}>
<Text style={styles.sectionHeaderText}>
{section.key!.toUpperCase()}
</Text>
</View>
)}
renderItem={({ item }: { item: RowItem }) => {
const selectedIndex = selectedContacts.findIndex(
(i) => i.id === item.id
);
const onPress = () => {
const newContacts = [...selectedContacts];
if (selectedIndex >= 0) {
newContacts.splice(selectedIndex, 1);
} else {
newContacts.push(item);
}
setSelectedContacts(newContacts);
};
return (
<ContactRow
name={item.name}
emailOrNumber={(item.email || item.phoneNumber)!}
selected={selectedIndex >= 0}
onPress={onPress}
/>
);
}}
extraData={selectedContacts}
contentContainerStyle={styles.contentContainerStyle}
/>
<SafeAreaView style={styles.bottomButtonContainer}>
<Button
style={styles.button}
onPress={onInvitePress}
disabled={selectedContacts.length === 0}
>
<Text style={styles.buttonText}>{`${i18n.t("invite")} (${
selectedContacts.length
})`}</Text>
</Button>
</SafeAreaView>
</View>
);
};
return (
<View style={styles.container}>
<NavigationBar
title={i18n.t("inviteFriends")}
showBack
navigation={props.navigation}
/>
{renderBody()}
</View>
);
}