From 83048450e80ab61de807d24bffa0c87210caec9d Mon Sep 17 00:00:00 2001 From: Zhiyuan Zheng Date: Sat, 6 Mar 2021 21:01:38 +0100 Subject: [PATCH] Updates --- src/components/Instance.tsx | 35 +- src/components/Instance/EULA.tsx | 62 --- src/components/Instance/Info.tsx | 32 +- src/components/Timeline/Default.tsx | 2 +- src/components/Timeline/Refresh.tsx | 2 +- src/components/Timeline/Shared/Actions.tsx | 517 +++++++++--------- .../Timeline/Shared/HeaderConversation.tsx | 44 +- .../Timeline/Shared/HeaderShared/Account.tsx | 45 +- .../Shared/HeaderShared/Application.tsx | 39 +- .../Timeline/Shared/HeaderShared/Muted.tsx | 25 +- .../Shared/HeaderShared/Visibility.tsx | 53 +- src/screens/Compose.tsx | 2 +- src/screens/Tabs/Me/Root/Lists/List.tsx | 9 +- src/screens/Tabs/Me/Switch.tsx | 47 +- src/screens/Tabs/Me/Switch/Root.tsx | 82 ++- 15 files changed, 476 insertions(+), 520 deletions(-) delete mode 100644 src/components/Instance/EULA.tsx diff --git a/src/components/Instance.tsx b/src/components/Instance.tsx index 3c3655d1..16722729 100644 --- a/src/components/Instance.tsx +++ b/src/components/Instance.tsx @@ -9,9 +9,18 @@ import * as WebBrowser from 'expo-web-browser' import { debounce } from 'lodash' import React, { useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' -import { Alert, Image, StyleSheet, Text, TextInput, View } from 'react-native' +import { + Alert, + Image, + KeyboardAvoidingView, + Platform, + StyleSheet, + Text, + TextInput, + View +} from 'react-native' import { useSelector } from 'react-redux' -import { Placeholder, Fade } from 'rn-placeholder' +import { Placeholder } from 'rn-placeholder' import analytics from './analytics' import InstanceAuth from './Instance/Auth' import InstanceInfo from './Instance/Info' @@ -115,7 +124,10 @@ const ComponentInstance: React.FC = ({ }, [domain, instanceQuery.data, appsQuery.data]) return ( - <> + {!disableHeaderImage ? ( = ({ - ( - - ) - })} - > + = ({ = ({ /> = ({ /> = ({ {requestAuth} - + ) } diff --git a/src/components/Instance/EULA.tsx b/src/components/Instance/EULA.tsx deleted file mode 100644 index cd247f4b..00000000 --- a/src/components/Instance/EULA.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import analytics from '@components/analytics' -import Icon from '@components/Icon' -import { StyleConstants } from '@utils/styles/constants' -import { useTheme } from '@utils/styles/ThemeManager' -import * as WebBrowser from 'expo-web-browser' -import React from 'react' -import { useTranslation } from 'react-i18next' -import { Pressable, StyleSheet, Text } from 'react-native' - -export interface Props { - agreed: boolean - setAgreed: React.Dispatch> -} - -const EULA = React.memo( - ({ agreed, setAgreed }: Props) => { - const { t } = useTranslation('componentInstance') - const { theme } = useTheme() - - return ( - setAgreed(!agreed)}> - - - {t('server.EULA.base')} - { - analytics('view_EULA') - WebBrowser.openBrowserAsync( - 'https://tooot.app/end-user-license-agreement' - ) - }} - /> - - - ) - }, - (prev, next) => prev.agreed === next.agreed -) - -const styles = StyleSheet.create({ - base: { - flexDirection: 'row', - marginTop: StyleConstants.Spacing.M, - paddingHorizontal: StyleConstants.Spacing.Global.PagePadding, - alignItems: 'center' - }, - icon: { - marginRight: StyleConstants.Spacing.XS - }, - text: { - ...StyleConstants.FontStyle.S - } -}) - -export default EULA diff --git a/src/components/Instance/Info.tsx b/src/components/Instance/Info.tsx index 0352eea2..bb5dcd5d 100644 --- a/src/components/Instance/Info.tsx +++ b/src/components/Instance/Info.tsx @@ -8,15 +8,13 @@ import { PlaceholderLine } from 'rn-placeholder' export interface Props { style?: ViewStyle - visible: boolean header: string content?: string potentialWidth?: number - potentialLines?: number } const InstanceInfo = React.memo( - ({ style, header, content, potentialWidth, potentialLines = 1 }: Props) => { + ({ style, header, content, potentialWidth }: Props) => { const { t } = useTranslation('componentInstance') const { theme } = useTheme() @@ -31,24 +29,22 @@ const InstanceInfo = React.memo( expandHint={t('server.information.description.expandHint')} /> ) : ( - Array.from(Array(potentialLines)).map((_, i) => ( - - )) + )} ) - } + }, + (prev, next) => prev.content === next.content ) const styles = StyleSheet.create({ diff --git a/src/components/Timeline/Default.tsx b/src/components/Timeline/Default.tsx index fea7bb6c..399aa193 100644 --- a/src/components/Timeline/Default.tsx +++ b/src/components/Timeline/Default.tsx @@ -41,7 +41,7 @@ const TimelineDefault: React.FC = ({ pinned }) => { const { theme } = useTheme() - const instanceAccount = useSelector(getInstanceAccount, (prev, next) => true) + const instanceAccount = useSelector(getInstanceAccount, () => true) const navigation = useNavigation< StackNavigationProp >() diff --git a/src/components/Timeline/Refresh.tsx b/src/components/Timeline/Refresh.tsx index 236f11c1..a870939b 100644 --- a/src/components/Timeline/Refresh.tsx +++ b/src/components/Timeline/Refresh.tsx @@ -103,7 +103,7 @@ const TimelineRefresh: React.FC = ({ queryClient.setQueryData | undefined>( queryKey, data => { - if (data?.pages[0].body.length === 0) { + if (data?.pages[0] && data.pages[0].body.length === 0) { return { pages: data.pages.slice(1), pageParams: data.pageParams.slice(1) diff --git a/src/components/Timeline/Shared/Actions.tsx b/src/components/Timeline/Shared/Actions.tsx index 7715b727..1a312e07 100644 --- a/src/components/Timeline/Shared/Actions.tsx +++ b/src/components/Timeline/Shared/Actions.tsx @@ -22,277 +22,274 @@ export interface Props { reblog: boolean } -const TimelineActions: React.FC = ({ - queryKey, - rootQueryKey, - status, - accts, - reblog -}) => { - const navigation = useNavigation() - const { t } = useTranslation('componentTimeline') - const { mode, theme } = useTheme() - const iconColor = theme.secondary - const iconColorAction = (state: boolean) => - state ? theme.primary : theme.secondary +const TimelineActions = React.memo( + ({ queryKey, rootQueryKey, status, accts, reblog }: Props) => { + const navigation = useNavigation() + const { t } = useTranslation('componentTimeline') + const { mode, theme } = useTheme() + const iconColor = theme.secondary + const iconColorAction = (state: boolean) => + state ? theme.primary : theme.secondary - const queryClient = useQueryClient() - const mutation = useTimelineMutation({ - queryClient, - onMutate: true, - onSuccess: (_, params) => { - const theParams = params as MutationVarsTimelineUpdateStatusProperty - if ( - // Un-bookmark from bookmarks page - (queryKey[1].page === 'Bookmarks' && - theParams.payload.property === 'bookmarked') || - // Un-favourite from favourites page - (queryKey[1].page === 'Favourites' && - theParams.payload.property === 'favourited') || - // Un-reblog from following page - (queryKey[1].page === 'Following' && + const queryClient = useQueryClient() + const mutation = useTimelineMutation({ + queryClient, + onMutate: true, + onSuccess: (_, params) => { + const theParams = params as MutationVarsTimelineUpdateStatusProperty + if ( + // Un-bookmark from bookmarks page + (queryKey[1].page === 'Bookmarks' && + theParams.payload.property === 'bookmarked') || + // Un-favourite from favourites page + (queryKey[1].page === 'Favourites' && + theParams.payload.property === 'favourited') || + // Un-reblog from following page + (queryKey[1].page === 'Following' && + theParams.payload.property === 'reblogged' && + theParams.payload.currentValue === true) + ) { + queryClient.invalidateQueries(queryKey) + } else if ( theParams.payload.property === 'reblogged' && - theParams.payload.currentValue === true) - ) { + queryKey[1].page !== 'Following' + ) { + // When reblogged, update cache of following page + const tempQueryKey: QueryKeyTimeline = [ + 'Timeline', + { page: 'Following' } + ] + queryClient.invalidateQueries(tempQueryKey) + } else if (theParams.payload.property === 'favourited') { + // When favourited, update favourited page + const tempQueryKey: QueryKeyTimeline = [ + 'Timeline', + { page: 'Favourites' } + ] + queryClient.invalidateQueries(tempQueryKey) + } else if (theParams.payload.property === 'bookmarked') { + // When bookmarked, update bookmark page + const tempQueryKey: QueryKeyTimeline = [ + 'Timeline', + { page: 'Bookmarks' } + ] + queryClient.invalidateQueries(tempQueryKey) + } + }, + onError: (err: any, params, oldData) => { + const correctParam = params as MutationVarsTimelineUpdateStatusProperty + displayMessage({ + mode, + type: 'error', + message: t('common:toastMessage.error.message', { + function: t( + `shared.actions.${correctParam.payload.property}.function` + ) + }), + ...(err.status && + typeof err.status === 'number' && + err.data && + err.data.error && + typeof err.data.error === 'string' && { + description: err.data.error + }) + }) queryClient.invalidateQueries(queryKey) - } else if ( - theParams.payload.property === 'reblogged' && - queryKey[1].page !== 'Following' - ) { - // When reblogged, update cache of following page - const tempQueryKey: QueryKeyTimeline = [ - 'Timeline', - { page: 'Following' } - ] - queryClient.invalidateQueries(tempQueryKey) - } else if (theParams.payload.property === 'favourited') { - // When favourited, update favourited page - const tempQueryKey: QueryKeyTimeline = [ - 'Timeline', - { page: 'Favourites' } - ] - queryClient.invalidateQueries(tempQueryKey) - } else if (theParams.payload.property === 'bookmarked') { - // When bookmarked, update bookmark page - const tempQueryKey: QueryKeyTimeline = [ - 'Timeline', - { page: 'Bookmarks' } - ] - queryClient.invalidateQueries(tempQueryKey) } - }, - onError: (err: any, params, oldData) => { - const correctParam = params as MutationVarsTimelineUpdateStatusProperty - displayMessage({ - mode, - type: 'error', - message: t('common:toastMessage.error.message', { - function: t( - `shared.actions.${correctParam.payload.property}.function` - ) - }), - ...(err.status && - typeof err.status === 'number' && - err.data && - err.data.error && - typeof err.data.error === 'string' && { - description: err.data.error - }) + }) + + const onPressReply = useCallback(() => { + analytics('timeline_shared_actions_reply_press', { + page: queryKey[1].page, + count: status.replies_count }) - queryClient.invalidateQueries(queryKey) - } - }) + navigation.navigate('Screen-Compose', { + type: 'reply', + incomingStatus: status, + accts, + queryKey + }) + }, [status.replies_count]) + const onPressReblog = useCallback(() => { + analytics('timeline_shared_actions_reblog_press', { + page: queryKey[1].page, + count: status.reblogs_count, + current: status.reblogged + }) + mutation.mutate({ + type: 'updateStatusProperty', + queryKey, + rootQueryKey, + id: status.id, + reblog, + payload: { + property: 'reblogged', + currentValue: status.reblogged, + propertyCount: 'reblogs_count', + countValue: status.reblogs_count + } + }) + }, [status.reblogged, status.reblogs_count]) + const onPressFavourite = useCallback(() => { + analytics('timeline_shared_actions_favourite_press', { + page: queryKey[1].page, + count: status.favourites_count, + current: status.favourited + }) + mutation.mutate({ + type: 'updateStatusProperty', + queryKey, + rootQueryKey, + id: status.id, + reblog, + payload: { + property: 'favourited', + currentValue: status.favourited, + propertyCount: 'favourites_count', + countValue: status.favourites_count + } + }) + }, [status.favourited, status.favourites_count]) + const onPressBookmark = useCallback(() => { + analytics('timeline_shared_actions_bookmark_press', { + page: queryKey[1].page, + current: status.bookmarked + }) + mutation.mutate({ + type: 'updateStatusProperty', + queryKey, + rootQueryKey, + id: status.id, + reblog, + payload: { + property: 'bookmarked', + currentValue: status.bookmarked, + propertyCount: undefined, + countValue: undefined + } + }) + }, [status.bookmarked]) - const onPressReply = useCallback(() => { - analytics('timeline_shared_actions_reply_press', { - page: queryKey[1].page, - count: status.replies_count - }) - navigation.navigate('Screen-Compose', { - type: 'reply', - incomingStatus: status, - accts, - queryKey - }) - }, [status.replies_count]) - const onPressReblog = useCallback(() => { - analytics('timeline_shared_actions_reblog_press', { - page: queryKey[1].page, - count: status.reblogs_count, - current: status.reblogged - }) - mutation.mutate({ - type: 'updateStatusProperty', - queryKey, - rootQueryKey, - id: status.id, - reblog, - payload: { - property: 'reblogged', - currentValue: status.reblogged, - propertyCount: 'reblogs_count', - countValue: status.reblogs_count - } - }) - }, [status.reblogged, status.reblogs_count]) - const onPressFavourite = useCallback(() => { - analytics('timeline_shared_actions_favourite_press', { - page: queryKey[1].page, - count: status.favourites_count, - current: status.favourited - }) - mutation.mutate({ - type: 'updateStatusProperty', - queryKey, - rootQueryKey, - id: status.id, - reblog, - payload: { - property: 'favourited', - currentValue: status.favourited, - propertyCount: 'favourites_count', - countValue: status.favourites_count - } - }) - }, [status.favourited, status.favourites_count]) - const onPressBookmark = useCallback(() => { - analytics('timeline_shared_actions_bookmark_press', { - page: queryKey[1].page, - current: status.bookmarked - }) - mutation.mutate({ - type: 'updateStatusProperty', - queryKey, - rootQueryKey, - id: status.id, - reblog, - payload: { - property: 'bookmarked', - currentValue: status.bookmarked, - propertyCount: undefined, - countValue: undefined - } - }) - }, [status.bookmarked]) - - const childrenReply = useMemo( - () => ( - <> + const childrenReply = useMemo( + () => ( + <> + + {status.replies_count > 0 && ( + + {status.replies_count} + + )} + + ), + [status.replies_count] + ) + const childrenReblog = useMemo( + () => ( + <> + + {status.reblogs_count > 0 && ( + + {status.reblogs_count} + + )} + + ), + [status.reblogged, status.reblogs_count] + ) + const childrenFavourite = useMemo( + () => ( + <> + + {status.favourites_count > 0 && ( + + {status.favourites_count} + + )} + + ), + [status.favourited, status.favourites_count] + ) + const childrenBookmark = useMemo( + () => ( - {status.replies_count > 0 && ( - - {status.replies_count} - - )} - - ), - [status.replies_count] - ) - const childrenReblog = useMemo( - () => ( + ), + [status.bookmarked] + ) + + return ( <> - - {status.reblogs_count > 0 && ( - - {status.reblogs_count} - - )} + + + + + + + + + - ), - [status.reblogged, status.reblogs_count] - ) - const childrenFavourite = useMemo( - () => ( - <> - - {status.favourites_count > 0 && ( - - {status.favourites_count} - - )} - - ), - [status.favourited, status.favourites_count] - ) - const childrenBookmark = useMemo( - () => ( - - ), - [status.bookmarked] - ) - - return ( - <> - - - - - - - - - - - ) -} + ) + }, + () => true +) const styles = StyleSheet.create({ actions: { diff --git a/src/components/Timeline/Shared/HeaderConversation.tsx b/src/components/Timeline/Shared/HeaderConversation.tsx index bc5f6341..145122cd 100644 --- a/src/components/Timeline/Shared/HeaderConversation.tsx +++ b/src/components/Timeline/Shared/HeaderConversation.tsx @@ -15,28 +15,32 @@ import { useQueryClient } from 'react-query' import HeaderSharedCreated from './HeaderShared/Created' import HeaderSharedMuted from './HeaderShared/Muted' -const Names: React.FC<{ accounts: Mastodon.Account[] }> = ({ accounts }) => { - const { t } = useTranslation('componentTimeline') - const { theme } = useTheme() +const Names = React.memo( + ({ accounts }: { accounts: Mastodon.Account[] }) => { + const { t } = useTranslation('componentTimeline') + const { theme } = useTheme() - return ( - - - {t('shared.header.conversation.withAccounts')} + return ( + + {t('shared.header.conversation.withAccounts')} + {accounts.map((account, index) => ( + + {index !== 0 ? t('common:separator') : undefined} + + + ))} - {accounts.map((account, index) => ( - - {index !== 0 ? t('common:separator') : undefined} - - - ))} - - ) -} + ) + }, + () => true +) export interface Props { queryKey: QueryKeyTimeline diff --git a/src/components/Timeline/Shared/HeaderShared/Account.tsx b/src/components/Timeline/Shared/HeaderShared/Account.tsx index ea2aaea2..03e33d3c 100644 --- a/src/components/Timeline/Shared/HeaderShared/Account.tsx +++ b/src/components/Timeline/Shared/HeaderShared/Account.tsx @@ -9,29 +9,32 @@ export interface Props { withoutName?: boolean // For notification follow request etc. } -const HeaderSharedAccount: React.FC = ({ - account, - withoutName = false -}) => { - const { theme } = useTheme() +const HeaderSharedAccount = React.memo( + ({ account, withoutName = false }: Props) => { + const { theme } = useTheme() - return ( - - {withoutName ? null : ( - - + return ( + + {withoutName ? null : ( + + + + )} + + @{account.acct} - )} - - @{account.acct} - - - ) -} + + ) + }, + () => true +) const styles = StyleSheet.create({ base: { diff --git a/src/components/Timeline/Shared/HeaderShared/Application.tsx b/src/components/Timeline/Shared/HeaderShared/Application.tsx index 2b5f67d2..e9b8430f 100644 --- a/src/components/Timeline/Shared/HeaderShared/Application.tsx +++ b/src/components/Timeline/Shared/HeaderShared/Application.tsx @@ -10,24 +10,29 @@ export interface Props { application?: Mastodon.Application } -const HeaderSharedApplication: React.FC = ({ application }) => { - const { theme } = useTheme() - const { t } = useTranslation('componentTimeline') +const HeaderSharedApplication = React.memo( + ({ application }: Props) => { + const { theme } = useTheme() + const { t } = useTranslation('componentTimeline') - return application && application.name !== 'Web' ? ( - { - analytics('timeline_shared_header_application_press', { - application - }) - application.website && (await openLink(application.website)) - }} - style={[styles.application, { color: theme.secondary }]} - > - {t('shared.header.shared.application', { application: application.name })} - - ) : null -} + return application && application.name !== 'Web' ? ( + { + analytics('timeline_shared_header_application_press', { + application + }) + application.website && (await openLink(application.website)) + }} + style={[styles.application, { color: theme.secondary }]} + > + {t('shared.header.shared.application', { + application: application.name + })} + + ) : null + }, + () => true +) const styles = StyleSheet.create({ application: { diff --git a/src/components/Timeline/Shared/HeaderShared/Muted.tsx b/src/components/Timeline/Shared/HeaderShared/Muted.tsx index 63fed225..e2b0c6b4 100644 --- a/src/components/Timeline/Shared/HeaderShared/Muted.tsx +++ b/src/components/Timeline/Shared/HeaderShared/Muted.tsx @@ -8,18 +8,21 @@ export interface Props { muted?: Mastodon.Status['muted'] } -const HeaderSharedMuted: React.FC = ({ muted }) => { - const { theme } = useTheme() +const HeaderSharedMuted = React.memo( + ({ muted }: Props) => { + const { theme } = useTheme() - return muted ? ( - - ) : null -} + return muted ? ( + + ) : null + }, + () => true +) const styles = StyleSheet.create({ visibility: { diff --git a/src/components/Timeline/Shared/HeaderShared/Visibility.tsx b/src/components/Timeline/Shared/HeaderShared/Visibility.tsx index 48d662da..235e4e3d 100644 --- a/src/components/Timeline/Shared/HeaderShared/Visibility.tsx +++ b/src/components/Timeline/Shared/HeaderShared/Visibility.tsx @@ -8,32 +8,35 @@ export interface Props { visibility: Mastodon.Status['visibility'] } -const HeaderSharedVisibility: React.FC = ({ visibility }) => { - const { theme } = useTheme() +const HeaderSharedVisibility = React.memo( + ({ visibility }: Props) => { + const { theme } = useTheme() - switch (visibility) { - case 'private': - return ( - - ) - case 'direct': - return ( - - ) - default: - return null - } -} + switch (visibility) { + case 'private': + return ( + + ) + case 'direct': + return ( + + ) + default: + return null + } + }, + () => true +) const styles = StyleSheet.create({ visibility: { diff --git a/src/screens/Compose.tsx b/src/screens/Compose.tsx index fd962558..8fc5228a 100644 --- a/src/screens/Compose.tsx +++ b/src/screens/Compose.tsx @@ -353,7 +353,7 @@ const ScreenCompose: React.FC = ({ { const queryKey: QueryKeyTimeline = ['Timeline', { page: 'List', list }] + const renderItem = useCallback( + ({ item }) => , + [] + ) - return + return } export default ScreenMeListsList diff --git a/src/screens/Tabs/Me/Switch.tsx b/src/screens/Tabs/Me/Switch.tsx index b3c7b09f..085b2327 100644 --- a/src/screens/Tabs/Me/Switch.tsx +++ b/src/screens/Tabs/Me/Switch.tsx @@ -2,7 +2,7 @@ import { HeaderCenter, HeaderLeft } from '@components/Header' import { StackScreenProps } from '@react-navigation/stack' import React from 'react' import { useTranslation } from 'react-i18next' -import { Platform } from 'react-native' +import { KeyboardAvoidingView, Platform } from 'react-native' import { createNativeStackNavigator } from 'react-native-screens/native-stack' import ScreenMeSwitchRoot from './Switch/Root' @@ -14,26 +14,33 @@ const ScreenMeSwitch: React.FC> = ({ navigation }) => { const { t } = useTranslation() return ( - - - }), - headerLeft: () => ( - navigation.goBack()} - /> - ) - }} - /> - + + ( + + ) + }), + headerLeft: () => ( + navigation.goBack()} + /> + ) + }} + /> + + ) } diff --git a/src/screens/Tabs/Me/Switch/Root.tsx b/src/screens/Tabs/Me/Switch/Root.tsx index f7f80abc..44e4cb8c 100644 --- a/src/screens/Tabs/Me/Switch/Root.tsx +++ b/src/screens/Tabs/Me/Switch/Root.tsx @@ -60,56 +60,52 @@ const ScreenMeSwitchRoot: React.FC = () => { const instanceActive = useSelector(getInstanceActive) return ( - - - - - {t('content.existing')} - - - {instances.length - ? instances - .slice() - .sort((a, b) => - `${a.uri}${a.account.acct}`.localeCompare( - `${b.uri}${b.account.acct}` - ) + + + + {t('content.existing')} + + + {instances.length + ? instances + .slice() + .sort((a, b) => + `${a.uri}${a.account.acct}`.localeCompare( + `${b.uri}${b.account.acct}` ) - .map((instance, index) => { - const localAccount = instances[instanceActive!] - return ( - - ) - }) - : null} - + ) + .map((instance, index) => { + const localAccount = instances[instanceActive!] + return ( + + ) + }) + : null} + - - - {t('content.new')} - - - - - + + + {t('content.new')} + + + + ) } const styles = StyleSheet.create({ + base: { + marginBottom: StyleConstants.Spacing.L + }, header: { ...StyleConstants.FontStyle.M, textAlign: 'center',