From 301772e7358295a445a207831319c6e8e0e107f8 Mon Sep 17 00:00:00 2001 From: Zhiyuan Zheng Date: Fri, 30 Oct 2020 00:10:25 +0100 Subject: [PATCH] Add centrally located prop-types --- src/components/ParseContent.jsx | 13 +- src/components/Toot/Card.jsx | 19 +-- src/components/Toot/Emojis.jsx | 11 +- src/components/Toot/Media.jsx | 3 +- src/components/Toot/Poll.jsx | 20 +--- src/components/Toot/Reblog.jsx | 3 +- .../{Toot.jsx => TootNotification.jsx} | 19 +-- src/components/TootTimeline.jsx | 112 ++++++++++++++++++ src/prop-types/account.js | 37 ++++++ src/prop-types/application.js | 10 ++ src/prop-types/attachment.js | 19 +++ src/prop-types/card.js | 23 ++++ src/prop-types/emoji.js | 12 ++ src/prop-types/mention.js | 11 ++ src/prop-types/notification.js | 17 +++ src/prop-types/poll.js | 23 ++++ src/prop-types/status.js | 51 ++++++++ src/prop-types/tag.js | 10 ++ src/stacks/Notifications.jsx | 2 +- src/stacks/common/Timeline.jsx | 13 +- 20 files changed, 353 insertions(+), 75 deletions(-) rename src/components/{Toot.jsx => TootNotification.jsx} (87%) create mode 100644 src/components/TootTimeline.jsx create mode 100644 src/prop-types/account.js create mode 100644 src/prop-types/application.js create mode 100644 src/prop-types/attachment.js create mode 100644 src/prop-types/card.js create mode 100644 src/prop-types/emoji.js create mode 100644 src/prop-types/mention.js create mode 100644 src/prop-types/notification.js create mode 100644 src/prop-types/poll.js create mode 100644 src/prop-types/status.js create mode 100644 src/prop-types/tag.js diff --git a/src/components/ParseContent.jsx b/src/components/ParseContent.jsx index e0ec9486..71e1db91 100644 --- a/src/components/ParseContent.jsx +++ b/src/components/ParseContent.jsx @@ -1,5 +1,7 @@ import React from 'react' import PropTypes from 'prop-types' +import propTypesEmoji from 'src/prop-types/emoji' +import propTypesMention from 'src/prop-types/mention' import { StyleSheet, Text } from 'react-native' import HTMLView from 'react-native-htmlview' import { useNavigation } from '@react-navigation/native' @@ -109,16 +111,9 @@ const HTMLstyles = StyleSheet.create({ ParseContent.propTypes = { content: PropTypes.string.isRequired, - emojis: Emojis.propTypes.emojis, + emojis: PropTypes.arrayOf(propTypesEmoji), emojiSize: PropTypes.number, - mentions: PropTypes.arrayOf( - PropTypes.exact({ - id: PropTypes.string.isRequired, - username: PropTypes.string.isRequired, - url: PropTypes.string.isRequired, - acct: PropTypes.string.isRequired - }) - ), + mentions: PropTypes.arrayOf(propTypesMention), showFullLink: PropTypes.bool, linesTruncated: PropTypes.number } diff --git a/src/components/Toot/Card.jsx b/src/components/Toot/Card.jsx index 8d6505e9..a9d84eb5 100644 --- a/src/components/Toot/Card.jsx +++ b/src/components/Toot/Card.jsx @@ -1,5 +1,5 @@ import React from 'react' -import PropTypes from 'prop-types' +import propTypesCard from 'src/prop-types/card' import { Image, Pressable, StyleSheet, Text, View } from 'react-native' import { useNavigation } from '@react-navigation/native' @@ -54,20 +54,5 @@ const styles = StyleSheet.create({ }) Card.propTypes = { - card: PropTypes.exact({ - url: PropTypes.string.isRequired, - title: PropTypes.string.isRequired, - description: PropTypes.string, - type: PropTypes.oneOf(['link', 'photo', 'video']), - author_name: PropTypes.string, - author_url: PropTypes.string, - provider_name: PropTypes.string, - provider_url: PropTypes.string, - html: PropTypes.string, - width: PropTypes.number, - height: PropTypes.number, - image: PropTypes.string, - embed_url: PropTypes.string, - blurhash: PropTypes.string - }).isRequired + card: propTypesCard } diff --git a/src/components/Toot/Emojis.jsx b/src/components/Toot/Emojis.jsx index 12a20e95..7c2220d4 100644 --- a/src/components/Toot/Emojis.jsx +++ b/src/components/Toot/Emojis.jsx @@ -1,5 +1,6 @@ import React from 'react' import PropTypes from 'prop-types' +import propTypesEmoji from 'src/prop-types/emoji' import { Image, Text } from 'react-native' const regexEmoji = new RegExp(/(:[a-z0-9_]+:)/) @@ -44,13 +45,5 @@ export default function Emojis ({ content, emojis, dimension }) { Emojis.propTypes = { content: PropTypes.string.isRequired, - emojis: PropTypes.arrayOf( - PropTypes.exact({ - shortcode: PropTypes.string.isRequired, - url: PropTypes.string.isRequired, - static_url: PropTypes.string.isRequired, - visible_in_picker: PropTypes.bool.isRequired, - category: PropTypes.string - }) - ) + emojis: PropTypes.arrayOf(propTypesEmoji) } diff --git a/src/components/Toot/Media.jsx b/src/components/Toot/Media.jsx index dfb60d56..555d0756 100644 --- a/src/components/Toot/Media.jsx +++ b/src/components/Toot/Media.jsx @@ -1,5 +1,6 @@ import React, { useEffect, useState } from 'react' import PropTypes from 'prop-types' +import propTypesAttachment from 'src/prop-types/attachment' import { Button, Image, @@ -131,7 +132,7 @@ const styles = StyleSheet.create({ }) Media.propTypes = { - // media_attachments + media_attachments: PropTypes.arrayOf(propTypesAttachment), sensitive: PropTypes.bool.isRequired, width: PropTypes.number.isRequired } diff --git a/src/components/Toot/Poll.jsx b/src/components/Toot/Poll.jsx index d426d3a4..877f6786 100644 --- a/src/components/Toot/Poll.jsx +++ b/src/components/Toot/Poll.jsx @@ -1,5 +1,5 @@ import React from 'react' -import PropTypes from 'prop-types' +import propTypesPoll from 'src/prop-types/poll' import { StyleSheet, Text, View } from 'react-native' import Emojis from './Emojis' @@ -43,21 +43,5 @@ const styles = StyleSheet.create({ }) Poll.propTypes = { - poll: PropTypes.exact({ - id: PropTypes.string.isRequired, - expires_at: PropTypes.string.isRequired, - expired: PropTypes.bool.isRequired, - multiple: PropTypes.bool.isRequired, - votes_count: PropTypes.number, - voters_count: PropTypes.number, - voted: PropTypes.bool.isRequired, - own_votes: PropTypes.array, - options: PropTypes.arrayOf( - PropTypes.exact({ - title: PropTypes.string.isRequired, - votes_count: PropTypes.number.isRequired - }) - ), - emojis: Emojis.propTypes.emojis - }).isRequired + poll: propTypesPoll } diff --git a/src/components/Toot/Reblog.jsx b/src/components/Toot/Reblog.jsx index 7f28cc0f..2b4f3cf4 100644 --- a/src/components/Toot/Reblog.jsx +++ b/src/components/Toot/Reblog.jsx @@ -1,5 +1,6 @@ import React from 'react' import PropTypes from 'prop-types' +import propTypesEmoji from 'src/prop-types/emoji' import { StyleSheet, Text, View } from 'react-native' import { Feather } from '@expo/vector-icons' @@ -32,5 +33,5 @@ const styles = StyleSheet.create({ Reblog.propTypes = { name: PropTypes.string.isRequired, - emojis: Emojis.propTypes.emojis + emojis: PropTypes.arrayOf(propTypesEmoji) } diff --git a/src/components/Toot.jsx b/src/components/TootNotification.jsx similarity index 87% rename from src/components/Toot.jsx rename to src/components/TootNotification.jsx index 5987b554..68806c57 100644 --- a/src/components/Toot.jsx +++ b/src/components/TootNotification.jsx @@ -1,5 +1,6 @@ import React, { useMemo } from 'react' import PropTypes from 'prop-types' +import propTypesNotification from 'src/prop-types/notification' import { Dimensions, Pressable, StyleSheet, View } from 'react-native' import { useNavigation } from '@react-navigation/native' @@ -12,7 +13,7 @@ import Media from './Toot/Media' import Card from './Toot/Card' import Actions from './Toot/Actions' -export default function Toot ({ toot }) { +export default function TootNotification ({ toot }) { const navigation = useNavigation() let actualContent @@ -107,18 +108,6 @@ const styles = StyleSheet.create({ } }) -Toot.propTypes = { - toot: PropTypes.shape({ - account: PropTypes.shape({ - avatar: PropTypes.string.isRequired, - display_name: PropTypes.string.isRequired, - acct: PropTypes.string.isRequired - }).isRequired, - created_at: PropTypes.string.isRequired, - application: PropTypes.exact({ - name: PropTypes.string.isRequired, - website: PropTypes.string - }), - content: PropTypes.string - }).isRequired +TootNotification.propTypes = { + toot: propTypesNotification } diff --git a/src/components/TootTimeline.jsx b/src/components/TootTimeline.jsx new file mode 100644 index 00000000..23a1bd8c --- /dev/null +++ b/src/components/TootTimeline.jsx @@ -0,0 +1,112 @@ +import React, { useMemo } from 'react' +import propTypesStatus from 'src/prop-types/status' +import { Dimensions, Pressable, StyleSheet, View } from 'react-native' +import { useNavigation } from '@react-navigation/native' + +import Reblog from './Toot/Reblog' +import Avatar from './Toot/Avatar' +import Header from './Toot/Header' +import Content from './Toot/Content' +import Poll from './Toot/Poll' +import Media from './Toot/Media' +import Card from './Toot/Card' +import Actions from './Toot/Actions' + +export default function TootTimeline ({ toot }) { + const navigation = useNavigation() + + let actualContent + if (toot.reblog) { + actualContent = toot.reblog + } else { + actualContent = toot + } + + const tootView = useMemo(() => { + return ( + + {toot.reblog && ( + + )} + + + +
+ {/* Can pass toot info to next page to speed up performance */} + + navigation.navigate('Toot', { toot: actualContent.id }) + } + > + {actualContent.content ? ( + + ) : ( + <> + )} + {actualContent.poll && } + {actualContent.media_attachments && ( + + )} + {actualContent.card && } + + + + + + ) + }) + + return tootView +} + +const styles = StyleSheet.create({ + tootTimeline: { + flex: 1, + flexDirection: 'column', + padding: 12 + }, + toot: { + flex: 1, + flexDirection: 'row' + }, + details: { + flex: 1, + flexGrow: 1 + } +}) + +TootTimeline.propTypes = { + toot: propTypesStatus +} diff --git a/src/prop-types/account.js b/src/prop-types/account.js new file mode 100644 index 00000000..e3ca2d99 --- /dev/null +++ b/src/prop-types/account.js @@ -0,0 +1,37 @@ +import PropTypes from 'prop-types' +import propTypesEmoji from './emoji' +import propTypesStatus from './status' + +const propTypesAccount = PropTypes.shape({ + // Base + id: PropTypes.string.isRequired, + username: PropTypes.string.isRequired, + acct: PropTypes.string.isRequired, + url: PropTypes.string.isRequired, + + // Attributes + display_name: PropTypes.string.isRequired, + note: PropTypes.string, + avatar: PropTypes.string.isRequired, + avatar_static: PropTypes.string.isRequired, + header: PropTypes.string.isRequired, + header_static: PropTypes.string.isRequired, + locked: PropTypes.bool.isRequired, + emojis: PropTypes.arrayOf(propTypesEmoji), + discoverable: PropTypes.bool.isRequired, + + // Statistics + created_at: PropTypes.string.isRequired, + last_status_at: PropTypes.string.isRequired, + statuses_count: PropTypes.number.isRequired, + followers_count: PropTypes.number.isRequired, + following_count: PropTypes.number.isRequired, + + // Others + moved: propTypesStatus, + // fields prop-types + bot: PropTypes.bool.isRequired + // source prop-types +}) + +export default propTypesAccount diff --git a/src/prop-types/application.js b/src/prop-types/application.js new file mode 100644 index 00000000..ccffd298 --- /dev/null +++ b/src/prop-types/application.js @@ -0,0 +1,10 @@ +import PropTypes from 'prop-types' + +const propTypesApplication = PropTypes.shape({ + // Base + name: PropTypes.string.isRequired, + website: PropTypes.string, + vapid_key: PropTypes.string +}) + +export default propTypesApplication diff --git a/src/prop-types/attachment.js b/src/prop-types/attachment.js new file mode 100644 index 00000000..c800a0ac --- /dev/null +++ b/src/prop-types/attachment.js @@ -0,0 +1,19 @@ +import PropTypes from 'prop-types' + +const propTypesAttachment = PropTypes.shape({ + // Base + id: PropTypes.string.isRequired, + type: PropTypes.oneOf(['unknown', 'image', 'gifv', 'video', 'audio']) + .isRequired, + url: PropTypes.string.isRequired, + preview_url: PropTypes.string.isRequired, + + // Others + remote_url: PropTypes.string, + text_url: PropTypes.string, + meta: PropTypes.object, + description: PropTypes.string, + blurhash: PropTypes.string +}) + +export default propTypesAttachment diff --git a/src/prop-types/card.js b/src/prop-types/card.js new file mode 100644 index 00000000..8fb152ca --- /dev/null +++ b/src/prop-types/card.js @@ -0,0 +1,23 @@ +import PropTypes from 'prop-types' + +const propTypesCard = PropTypes.shape({ + // Base + url: PropTypes.string.isRequired, + title: PropTypes.string.isRequired, + description: PropTypes.string.isRequired, + type: PropTypes.oneOf(['link', 'photo', 'video', 'rich']).isRequired, + + // Attributes + author_name: PropTypes.string, + author_url: PropTypes.string, + provider_name: PropTypes.string, + provider_url: PropTypes.string, + html: PropTypes.string, + width: PropTypes.number, + height: PropTypes.number, + image: PropTypes.string, + embed_url: PropTypes.string, + blurhash: PropTypes.string +}) + +export default propTypesCard diff --git a/src/prop-types/emoji.js b/src/prop-types/emoji.js new file mode 100644 index 00000000..a6d98fe3 --- /dev/null +++ b/src/prop-types/emoji.js @@ -0,0 +1,12 @@ +import PropTypes from 'prop-types' + +const propTypesEmoji = PropTypes.shape({ + // Base + shortcode: PropTypes.string.isRequired, + url: PropTypes.string.isRequired, + static_url: PropTypes.string.isRequired, + visible_in_picker: PropTypes.bool.isRequired, + category: PropTypes.string +}) + +export default propTypesEmoji diff --git a/src/prop-types/mention.js b/src/prop-types/mention.js new file mode 100644 index 00000000..408897b5 --- /dev/null +++ b/src/prop-types/mention.js @@ -0,0 +1,11 @@ +import PropTypes from 'prop-types' + +const propTypesMention = PropTypes.shape({ + // Base + id: PropTypes.string.isRequired, + username: PropTypes.string.isRequired, + acct: PropTypes.string.isRequired, + url: PropTypes.string.isRequired +}) + +export default propTypesMention diff --git a/src/prop-types/notification.js b/src/prop-types/notification.js new file mode 100644 index 00000000..ff699535 --- /dev/null +++ b/src/prop-types/notification.js @@ -0,0 +1,17 @@ +import PropTypes from 'prop-types' +import propTypesAccount from './account' +import propTypesStatus from './status' + +const propTypesNotification = PropTypes.shape({ + // Base + id: PropTypes.string.isRequired, + type: PropTypes.oneOf(['follow', 'mention', 'reblog', 'favourite', 'poll']) + .isRequired, + created_at: PropTypes.string.isRequired, + account: propTypesAccount, + + // Others + status: propTypesStatus +}) + +export default propTypesNotification diff --git a/src/prop-types/poll.js b/src/prop-types/poll.js new file mode 100644 index 00000000..9ff7c3c7 --- /dev/null +++ b/src/prop-types/poll.js @@ -0,0 +1,23 @@ +import PropTypes from 'prop-types' +import propTypesEmoji from './emoji' + +const propTypesPoll = PropTypes.shape({ + // Base + id: PropTypes.string.isRequired, + expires_at: PropTypes.string.isRequired, + expired: PropTypes.bool.isRequired, + multiple: PropTypes.bool.isRequired, + votes_count: PropTypes.number.isRequired, + voters_count: PropTypes.number.isRequired, + voted: PropTypes.bool, + own_votes: PropTypes.arrayOf(PropTypes.number), + options: PropTypes.arrayOf( + PropTypes.exact({ + title: PropTypes.string.isRequired, + votes_count: PropTypes.number.isRequired + }) + ).isRequired, + emojis: PropTypes.arrayOf(propTypesEmoji) +}) + +export default propTypesPoll diff --git a/src/prop-types/status.js b/src/prop-types/status.js new file mode 100644 index 00000000..f90db938 --- /dev/null +++ b/src/prop-types/status.js @@ -0,0 +1,51 @@ +import PropTypes from 'prop-types' +import propTypesAccount from './account' +import propTypesAttachment from './attachment' +import propTypesApplication from './application' +import propTypesMention from './mention' +import propTypesTag from './tag' +import propTypesEmoji from './emoji' +import propTypesPoll from './poll' +import propTypesCard from './card' + +const propTypesStatus = PropTypes.shape({ + // Base + id: PropTypes.string.isRequired, + uri: PropTypes.string.isRequired, + created_at: PropTypes.string.isRequired, + account: propTypesAccount, + content: PropTypes.string.isRequired, // Might not be required + visibility: PropTypes.oneOf(['public', 'unlisted', 'private', 'direct']) + .isRequired, + sensitive: PropTypes.bool.isRequired, + spoiler_text: PropTypes.string, + media_attachments: PropTypes.arrayOf(propTypesAttachment), + application: propTypesApplication, + + // Attributes + mentions: PropTypes.arrayOf(propTypesMention), + tags: PropTypes.arrayOf(propTypesTag), + emojis: PropTypes.arrayOf(propTypesEmoji), + + // Interaction + reblogs_count: PropTypes.number.isRequired, + favourites_count: PropTypes.number.isRequired, + replies_count: PropTypes.number.isRequired, + favourited: PropTypes.bool, + reblogged: PropTypes.bool, + muted: PropTypes.bool, + bookmarked: PropTypes.bool, + pinned: PropTypes.bool, + + // Others + url: PropTypes.string, + in_reply_to_id: PropTypes.string, + in_reply_to_account_id: PropTypes.string, + reblog: propTypesStatus, + poll: propTypesPoll, + card: propTypesCard, + language: PropTypes.string, + text: PropTypes.string +}) + +export default propTypesStatus diff --git a/src/prop-types/tag.js b/src/prop-types/tag.js new file mode 100644 index 00000000..913359ad --- /dev/null +++ b/src/prop-types/tag.js @@ -0,0 +1,10 @@ +import PropTypes from 'prop-types' + +const propTypesTag = PropTypes.shape({ + // Base + name: PropTypes.string.isRequired, + url: PropTypes.string.isRequired + // history prop-types +}) + +export default propTypesTag diff --git a/src/stacks/Notifications.jsx b/src/stacks/Notifications.jsx index 7a5adef5..88cdc48d 100644 --- a/src/stacks/Notifications.jsx +++ b/src/stacks/Notifications.jsx @@ -26,7 +26,7 @@ export default function Notifications () { }} > - {props => } + {() => } ) diff --git a/src/stacks/common/Timeline.jsx b/src/stacks/common/Timeline.jsx index 9ebbbfdb..8916c8dc 100644 --- a/src/stacks/common/Timeline.jsx +++ b/src/stacks/common/Timeline.jsx @@ -3,7 +3,8 @@ import PropTypes from 'prop-types' import { ActivityIndicator, FlatList, Text, View } from 'react-native' import { useSelector, useDispatch } from 'react-redux' -import Toot from 'src/components/Toot' +import TootNotification from 'src/components/TootNotification' +import TootTimeline from 'src/components/TootTimeline' import { fetch } from './timelineSlice' // Opening nesting hashtag pages @@ -37,9 +38,13 @@ export default function Timeline ({ style={{ minHeight: '100%' }} data={state.toots} keyExtractor={({ id }) => id} - renderItem={({ item, index, separators }) => ( - - )} + renderItem={({ item, index, separators }) => + page === 'Notifications' ? ( + + ) : ( + + ) + } {...(state.pointer && { initialScrollIndex: state.pointer })} {...(!disableRefresh && { onRefresh: () =>