Remove most React memo

Maybe would solve iOS out of memory crashes
This commit is contained in:
xmflsct 2022-12-24 01:18:20 +01:00
parent 1e0e8842db
commit b6045e5121
19 changed files with 517 additions and 600 deletions

View File

@ -331,4 +331,4 @@ const Screens: React.FC<Props> = ({ localCorrupt }) => {
) )
} }
export default React.memo(Screens, () => true) export default Screens

View File

@ -12,55 +12,44 @@ export interface Props {
potentialWidth?: number potentialWidth?: number
} }
const InstanceInfo = React.memo( const InstanceInfo: React.FC<Props> = ({ style, header, content, potentialWidth }) => {
({ style, header, content, potentialWidth }: Props) => { const { colors } = useTheme()
const { colors } = useTheme()
return ( return (
<View <View
style={[ style={[
{ {
flex: 1, flex: 1,
marginTop: StyleConstants.Spacing.M, marginTop: StyleConstants.Spacing.M,
paddingLeft: StyleConstants.Spacing.Global.PagePadding, paddingLeft: StyleConstants.Spacing.Global.PagePadding,
paddingRight: StyleConstants.Spacing.Global.PagePadding paddingRight: StyleConstants.Spacing.Global.PagePadding
}, },
style style
]} ]}
accessible accessible
> >
<CustomText <CustomText
fontStyle='S' fontStyle='S'
style={{ style={{
marginBottom: StyleConstants.Spacing.XS, marginBottom: StyleConstants.Spacing.XS,
color: colors.primaryDefault color: colors.primaryDefault
}} }}
fontWeight='Bold' fontWeight='Bold'
children={header} children={header}
/>
{content ? (
<CustomText fontStyle='M' style={{ color: colors.primaryDefault }} children={content} />
) : (
<PlaceholderLine
width={potentialWidth ? potentialWidth * StyleConstants.Font.Size.M : undefined}
height={StyleConstants.Font.LineHeight.M}
color={colors.shimmerDefault}
noMargin
style={{ borderRadius: 0 }}
/> />
{content ? ( )}
<CustomText </View>
fontStyle='M' )
style={{ color: colors.primaryDefault }} }
children={content}
/>
) : (
<PlaceholderLine
width={
potentialWidth
? potentialWidth * StyleConstants.Font.Size.M
: undefined
}
height={StyleConstants.Font.LineHeight.M}
color={colors.shimmerDefault}
noMargin
style={{ borderRadius: 0 }}
/>
)}
</View>
)
},
(prev, next) => prev.content === next.content
)
export default InstanceInfo export default InstanceInfo

View File

@ -13,74 +13,71 @@ export interface Props {
queryKey: QueryKeyTimeline queryKey: QueryKeyTimeline
} }
const TimelineEmpty = React.memo( const TimelineEmpty: React.FC<Props> = ({ queryKey }) => {
({ queryKey }: Props) => { const { status, refetch } = useTimelineQuery({
const { status, refetch } = useTimelineQuery({ ...queryKey[1],
...queryKey[1], options: { notifyOnChangeProps: ['status'] }
options: { notifyOnChangeProps: ['status'] } })
})
const { colors } = useTheme() const { colors } = useTheme()
const { t } = useTranslation('componentTimeline') const { t } = useTranslation('componentTimeline')
const children = () => { const children = () => {
switch (status) { switch (status) {
case 'loading': case 'loading':
return <Circle size={StyleConstants.Font.Size.L} color={colors.secondary} /> return <Circle size={StyleConstants.Font.Size.L} color={colors.secondary} />
case 'error': case 'error':
return ( return (
<> <>
<Icon name='Frown' size={StyleConstants.Font.Size.L} color={colors.primaryDefault} /> <Icon name='Frown' size={StyleConstants.Font.Size.L} color={colors.primaryDefault} />
<CustomText <CustomText
fontStyle='M' fontStyle='M'
style={{ style={{
marginTop: StyleConstants.Spacing.S, marginTop: StyleConstants.Spacing.S,
marginBottom: StyleConstants.Spacing.L, marginBottom: StyleConstants.Spacing.L,
color: colors.primaryDefault color: colors.primaryDefault
}} }}
> >
{t('empty.error.message')} {t('empty.error.message')}
</CustomText> </CustomText>
<Button type='text' content={t('empty.error.button')} onPress={() => refetch()} /> <Button type='text' content={t('empty.error.button')} onPress={() => refetch()} />
</> </>
) )
case 'success': case 'success':
return ( return (
<> <>
<Icon <Icon
name='Smartphone' name='Smartphone'
size={StyleConstants.Font.Size.L} size={StyleConstants.Font.Size.L}
color={colors.primaryDefault} color={colors.primaryDefault}
/> />
<CustomText <CustomText
fontStyle='M' fontStyle='M'
style={{ style={{
marginTop: StyleConstants.Spacing.S, marginTop: StyleConstants.Spacing.S,
marginBottom: StyleConstants.Spacing.L, marginBottom: StyleConstants.Spacing.L,
color: colors.secondary color: colors.secondary
}} }}
> >
{t('empty.success.message')} {t('empty.success.message')}
</CustomText> </CustomText>
</> </>
) )
}
} }
return ( }
<View return (
style={{ <View
flex: 1, style={{
minHeight: '100%', flex: 1,
justifyContent: 'center', minHeight: '100%',
alignItems: 'center', justifyContent: 'center',
backgroundColor: colors.backgroundDefault alignItems: 'center',
}} backgroundColor: colors.backgroundDefault
> }}
{children()} >
</View> {children()}
) </View>
}, )
() => true }
)
export default TimelineEmpty export default TimelineEmpty

View File

@ -13,50 +13,47 @@ export interface Props {
disableInfinity: boolean disableInfinity: boolean
} }
const TimelineFooter = React.memo( const TimelineFooter: React.FC<Props> = ({ queryKey, disableInfinity }) => {
({ queryKey, disableInfinity }: Props) => { const { hasNextPage } = useTimelineQuery({
const { hasNextPage } = useTimelineQuery({ ...queryKey[1],
...queryKey[1], options: {
options: { enabled: !disableInfinity,
enabled: !disableInfinity, notifyOnChangeProps: ['hasNextPage'],
notifyOnChangeProps: ['hasNextPage'], getNextPageParam: lastPage =>
getNextPageParam: lastPage => lastPage?.links?.next && {
lastPage?.links?.next && { ...(lastPage.links.next.isOffset
...(lastPage.links.next.isOffset ? { offset: lastPage.links.next.id }
? { offset: lastPage.links.next.id } : { max_id: lastPage.links.next.id })
: { max_id: lastPage.links.next.id }) }
} }
} })
})
const { colors } = useTheme() const { colors } = useTheme()
return ( return (
<View <View
style={{ style={{
flex: 1, flex: 1,
flexDirection: 'row', flexDirection: 'row',
justifyContent: 'center', justifyContent: 'center',
padding: StyleConstants.Spacing.M padding: StyleConstants.Spacing.M
}} }}
> >
{!disableInfinity && hasNextPage ? ( {!disableInfinity && hasNextPage ? (
<Circle size={StyleConstants.Font.Size.L} color={colors.secondary} /> <Circle size={StyleConstants.Font.Size.L} color={colors.secondary} />
) : ( ) : (
<CustomText fontStyle='S' style={{ color: colors.secondary }}> <CustomText fontStyle='S' style={{ color: colors.secondary }}>
<Trans <Trans
ns='componentTimeline' ns='componentTimeline'
i18nKey='end.message' i18nKey='end.message'
components={[ components={[
<Icon name='Coffee' size={StyleConstants.Font.Size.S} color={colors.secondary} /> <Icon name='Coffee' size={StyleConstants.Font.Size.S} color={colors.secondary} />
]} ]}
/> />
</CustomText> </CustomText>
)} )}
</View> </View>
) )
}, }
() => true
)
export default TimelineFooter export default TimelineFooter

View File

@ -211,15 +211,14 @@ const TimelineActions: React.FC = () => {
) )
const childrenReblog = useMemo(() => { const childrenReblog = useMemo(() => {
const color = (state: boolean) => (state ? colors.green : colors.secondary) const color = (state: boolean) => (state ? colors.green : colors.secondary)
const disabled =
status.visibility === 'direct' || (status.visibility === 'private' && !ownAccount)
return ( return (
<> <>
<Icon <Icon
name='Repeat' name='Repeat'
color={ color={disabled ? colors.disabled : color(status.reblogged)}
status.visibility === 'direct' || (status.visibility === 'private' && !ownAccount) crossOut={disabled}
? colors.disabled
: color(status.reblogged)
}
size={StyleConstants.Font.Size.L} size={StyleConstants.Font.Size.L}
/> />
{status.reblogs_count > 0 ? ( {status.reblogs_count > 0 ? (

View File

@ -9,31 +9,28 @@ export interface Props {
application?: Mastodon.Application application?: Mastodon.Application
} }
const HeaderSharedApplication = React.memo( const HeaderSharedApplication: React.FC<Props> = ({ application }) => {
({ application }: Props) => { const { colors } = useTheme()
const { colors } = useTheme() const { t } = useTranslation('componentTimeline')
const { t } = useTranslation('componentTimeline')
return application && application.name !== 'Web' ? ( return application && application.name !== 'Web' ? (
<CustomText <CustomText
fontStyle='S' fontStyle='S'
accessibilityRole='link' accessibilityRole='link'
onPress={async () => { onPress={async () => {
application.website && (await openLink(application.website)) application.website && (await openLink(application.website))
}} }}
style={{ style={{
marginLeft: StyleConstants.Spacing.S, marginLeft: StyleConstants.Spacing.S,
color: colors.secondary color: colors.secondary
}} }}
numberOfLines={1} numberOfLines={1}
> >
{t('shared.header.shared.application', { {t('shared.header.shared.application', {
application: application.name application: application.name
})} })}
</CustomText> </CustomText>
) : null ) : null
}, }
() => true
)
export default HeaderSharedApplication export default HeaderSharedApplication

View File

@ -13,43 +13,34 @@ export interface Props {
highlighted?: boolean highlighted?: boolean
} }
const HeaderSharedCreated = React.memo( const HeaderSharedCreated: React.FC<Props> = ({ created_at, edited_at, highlighted = false }) => {
({ created_at, edited_at, highlighted = false }: Props) => { const { t } = useTranslation('componentTimeline')
const { t } = useTranslation('componentTimeline') const { colors } = useTheme()
const { colors } = useTheme()
const actualTime = edited_at || created_at const actualTime = edited_at || created_at
return ( return (
<> <>
<CustomText fontStyle='S' style={{ color: colors.secondary }}> <CustomText fontStyle='S' style={{ color: colors.secondary }}>
{highlighted ? ( {highlighted ? (
<> <>
<FormattedDate <FormattedDate value={new Date(actualTime)} dateStyle='medium' timeStyle='short' />
value={new Date(actualTime)} </>
dateStyle='medium' ) : (
timeStyle='short' <RelativeTime time={actualTime} />
/> )}
</> </CustomText>
) : ( {edited_at ? (
<RelativeTime time={actualTime} /> <Icon
)} accessibilityLabel={t('shared.header.shared.edited.accessibilityLabel')}
</CustomText> name='Edit'
{edited_at ? ( size={StyleConstants.Font.Size.S}
<Icon color={colors.secondary}
accessibilityLabel={t( style={{ marginLeft: StyleConstants.Spacing.S }}
'shared.header.shared.edited.accessibilityLabel' />
)} ) : null}
name='Edit' </>
size={StyleConstants.Font.Size.S} )
color={colors.secondary} }
style={{ marginLeft: StyleConstants.Spacing.S }}
/>
) : null}
</>
)
},
(prev, next) => prev.edited_at === next.edited_at
)
export default HeaderSharedCreated export default HeaderSharedCreated

View File

@ -3,34 +3,24 @@ import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager' import { useTheme } from '@utils/styles/ThemeManager'
import React from 'react' import React from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { StyleSheet } from 'react-native'
export interface Props { export interface Props {
muted?: Mastodon.Status['muted'] muted?: Mastodon.Status['muted']
} }
const HeaderSharedMuted = React.memo( const HeaderSharedMuted: React.FC<Props> = ({ muted }) => {
({ muted }: Props) => { const { t } = useTranslation('componentTimeline')
const { t } = useTranslation('componentTimeline') const { colors } = useTheme()
const { colors } = useTheme()
return muted ? ( return muted ? (
<Icon <Icon
accessibilityLabel={t('shared.header.shared.muted.accessibilityLabel')} accessibilityLabel={t('shared.header.shared.muted.accessibilityLabel')}
name='VolumeX' name='VolumeX'
size={StyleConstants.Font.Size.S} size={StyleConstants.Font.Size.M}
color={colors.secondary} color={colors.secondary}
style={styles.visibility} style={{ marginLeft: StyleConstants.Spacing.S }}
/> />
) : null ) : null
}, }
() => true
)
const styles = StyleSheet.create({
visibility: {
marginLeft: StyleConstants.Spacing.S
}
})
export default HeaderSharedMuted export default HeaderSharedMuted

View File

@ -9,48 +9,45 @@ export interface Props {
visibility: Mastodon.Status['visibility'] visibility: Mastodon.Status['visibility']
} }
const HeaderSharedVisibility = React.memo( const HeaderSharedVisibility: React.FC<Props> = ({ visibility }) => {
({ visibility }: Props) => { const { t } = useTranslation('componentTimeline')
const { t } = useTranslation('componentTimeline') const { colors } = useTheme()
const { colors } = useTheme()
switch (visibility) { switch (visibility) {
case 'unlisted': case 'unlisted':
return ( return (
<Icon <Icon
accessibilityLabel={t('shared.header.shared.visibility.private.accessibilityLabel')} accessibilityLabel={t('shared.header.shared.visibility.private.accessibilityLabel')}
name='Unlock' name='Unlock'
size={StyleConstants.Font.Size.S} size={StyleConstants.Font.Size.S}
color={colors.secondary} color={colors.secondary}
style={styles.visibility} style={styles.visibility}
/> />
) )
case 'private': case 'private':
return ( return (
<Icon <Icon
accessibilityLabel={t('shared.header.shared.visibility.private.accessibilityLabel')} accessibilityLabel={t('shared.header.shared.visibility.private.accessibilityLabel')}
name='Lock' name='Lock'
size={StyleConstants.Font.Size.S} size={StyleConstants.Font.Size.S}
color={colors.secondary} color={colors.secondary}
style={styles.visibility} style={styles.visibility}
/> />
) )
case 'direct': case 'direct':
return ( return (
<Icon <Icon
accessibilityLabel={t('shared.header.shared.visibility.direct.accessibilityLabel')} accessibilityLabel={t('shared.header.shared.visibility.direct.accessibilityLabel')}
name='Mail' name='Mail'
size={StyleConstants.Font.Size.S} size={StyleConstants.Font.Size.S}
color={colors.secondary} color={colors.secondary}
style={styles.visibility} style={styles.visibility}
/> />
) )
default: default:
return null return null
} }
}, }
() => true
)
const styles = StyleSheet.create({ const styles = StyleSheet.create({
visibility: { visibility: {

View File

@ -3,25 +3,18 @@ import { Modal, View } from 'react-native'
import { useTheme } from '@utils/styles/ThemeManager' import { useTheme } from '@utils/styles/ThemeManager'
import ComposeContext from './utils/createContext' import ComposeContext from './utils/createContext'
const ComposePosting = React.memo( const ComposePosting = () => {
() => { const { composeState } = useContext(ComposeContext)
const { composeState } = useContext(ComposeContext) const { colors } = useTheme()
const { colors } = useTheme()
return ( return (
<Modal <Modal
transparent transparent
animationType='fade' animationType='fade'
visible={composeState.posting} visible={composeState.posting}
children={ children={<View style={{ flex: 1, backgroundColor: colors.backgroundOverlayInvert }} />}
<View />
style={{ flex: 1, backgroundColor: colors.backgroundOverlayInvert }} )
/> }
}
/>
)
},
() => true
)
export default ComposePosting export default ComposePosting

View File

@ -17,87 +17,84 @@ import { getInstanceConfigurationStatusCharsURL } from '@utils/slices/instancesS
export let instanceConfigurationStatusCharsURL = 23 export let instanceConfigurationStatusCharsURL = 23
const ComposeRoot = React.memo( const ComposeRoot = () => {
() => { const { colors } = useTheme()
const { colors } = useTheme()
instanceConfigurationStatusCharsURL = useSelector( instanceConfigurationStatusCharsURL = useSelector(
getInstanceConfigurationStatusCharsURL, getInstanceConfigurationStatusCharsURL,
() => true () => true
) )
const accessibleRefDrafts = useRef(null) const accessibleRefDrafts = useRef(null)
const accessibleRefAttachments = useRef(null) const accessibleRefAttachments = useRef(null)
useEffect(() => { useEffect(() => {
const tagDrafts = findNodeHandle(accessibleRefDrafts.current) const tagDrafts = findNodeHandle(accessibleRefDrafts.current)
tagDrafts && AccessibilityInfo.setAccessibilityFocus(tagDrafts) tagDrafts && AccessibilityInfo.setAccessibilityFocus(tagDrafts)
}, [accessibleRefDrafts.current]) }, [accessibleRefDrafts.current])
const { composeState } = useContext(ComposeContext) const { composeState } = useContext(ComposeContext)
const mapSchemaToType = () => { const mapSchemaToType = () => {
if (composeState.tag) { if (composeState.tag) {
switch (composeState.tag?.schema) { switch (composeState.tag?.schema) {
case '@': case '@':
return 'accounts' return 'accounts'
case '#': case '#':
return 'hashtags' return 'hashtags'
}
} else {
return undefined
} }
} else {
return undefined
} }
const { isFetching, data, refetch } = useSearchQuery({ }
type: mapSchemaToType(), const { isFetching, data, refetch } = useSearchQuery({
term: composeState.tag?.raw.substring(1), type: mapSchemaToType(),
options: { enabled: false } term: composeState.tag?.raw.substring(1),
}) options: { enabled: false }
})
useEffect(() => { useEffect(() => {
if ( if (
(composeState.tag?.schema === '@' || composeState.tag?.schema === '#') && (composeState.tag?.schema === '@' || composeState.tag?.schema === '#') &&
composeState.tag?.raw composeState.tag?.raw
) { ) {
refetch() refetch()
} }
}, [composeState.tag]) }, [composeState.tag])
const listEmpty = useMemo(() => { const listEmpty = useMemo(() => {
if (isFetching) { if (isFetching) {
return ( return (
<View key='listEmpty' style={{ flex: 1, alignItems: 'center' }}> <View key='listEmpty' style={{ flex: 1, alignItems: 'center' }}>
<Circle size={StyleConstants.Font.Size.M * 1.25} color={colors.secondary} /> <Circle size={StyleConstants.Font.Size.M * 1.25} color={colors.secondary} />
</View> </View>
) )
} }
}, [isFetching]) }, [isFetching])
const Footer = useMemo( const Footer = useMemo(
() => <ComposeRootFooter accessibleRefAttachments={accessibleRefAttachments} />, () => <ComposeRootFooter accessibleRefAttachments={accessibleRefAttachments} />,
[accessibleRefAttachments.current] [accessibleRefAttachments.current]
) )
return ( return (
<View style={{ flex: 1 }}> <View style={{ flex: 1 }}>
<FlatList <FlatList
renderItem={({ item }) => <ComposeRootSuggestion item={item} />} renderItem={({ item }) => <ComposeRootSuggestion item={item} />}
ListEmptyComponent={listEmpty} ListEmptyComponent={listEmpty}
keyboardShouldPersistTaps='always' keyboardShouldPersistTaps='always'
ListHeaderComponent={ComposeRootHeader} ListHeaderComponent={ComposeRootHeader}
ListFooterComponent={Footer} ListFooterComponent={Footer}
ItemSeparatorComponent={ComponentSeparator} ItemSeparatorComponent={ComponentSeparator}
// @ts-ignore // @ts-ignore
data={data ? data[mapSchemaToType()] : undefined} data={data ? data[mapSchemaToType()] : undefined}
keyExtractor={() => Math.random().toString()} keyExtractor={() => Math.random().toString()}
/> />
<ComposeActions /> <ComposeActions />
<ComposeDrafts accessibleRefDrafts={accessibleRefDrafts} /> <ComposeDrafts accessibleRefDrafts={accessibleRefDrafts} />
<ComposePosting /> <ComposePosting />
</View> </View>
) )
}, }
() => true
)
export default ComposeRoot export default ComposeRoot

View File

@ -292,4 +292,4 @@ const ComposeAttachments: React.FC<Props> = ({ accessibleRefAttachments }) => {
) )
} }
export default React.memo(ComposeAttachments, () => true) export default ComposeAttachments

View File

@ -31,4 +31,4 @@ const ComposeReply: React.FC = () => {
) )
} }
export default React.memo(ComposeReply, () => true) export default ComposeReply

View File

@ -1,34 +1,25 @@
import CustomText from '@components/Text' import CustomText from '@components/Text'
import { import { getInstanceAccount, getInstanceUri } from '@utils/slices/instancesSlice'
getInstanceAccount,
getInstanceUri
} from '@utils/slices/instancesSlice'
import { useTheme } from '@utils/styles/ThemeManager' import { useTheme } from '@utils/styles/ThemeManager'
import React from 'react' import React from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux'
const ComposePostingAs = React.memo( const ComposePostingAs = () => {
() => { const { t } = useTranslation('screenCompose')
const { t } = useTranslation('screenCompose') const { colors } = useTheme()
const { colors } = useTheme()
const instanceAccount = useSelector( const instanceAccount = useSelector(getInstanceAccount, (prev, next) => prev?.acct === next?.acct)
getInstanceAccount, const instanceUri = useSelector(getInstanceUri)
(prev, next) => prev?.acct === next?.acct
)
const instanceUri = useSelector(getInstanceUri)
return ( return (
<CustomText fontStyle='S' style={{ color: colors.secondary }}> <CustomText fontStyle='S' style={{ color: colors.secondary }}>
{t('content.root.header.postingAs', { {t('content.root.header.postingAs', {
acct: instanceAccount?.acct, acct: instanceAccount?.acct,
domain: instanceUri domain: instanceUri
})} })}
</CustomText> </CustomText>
) )
}, }
() => true
)
export default ComposePostingAs export default ComposePostingAs

View File

@ -16,103 +16,100 @@ import TabPublic from './Tabs/Public'
const Tab = createBottomTabNavigator<ScreenTabsStackParamList>() const Tab = createBottomTabNavigator<ScreenTabsStackParamList>()
const ScreenTabs = React.memo( const ScreenTabs = ({ navigation }: RootStackScreenProps<'Screen-Tabs'>) => {
({ navigation }: RootStackScreenProps<'Screen-Tabs'>) => { const { colors } = useTheme()
const { colors } = useTheme()
const instanceActive = useSelector(getInstanceActive) const instanceActive = useSelector(getInstanceActive)
const instanceAccount = useSelector( const instanceAccount = useSelector(
getInstanceAccount, getInstanceAccount,
(prev, next) => prev?.avatarStatic === next?.avatarStatic (prev, next) => prev?.avatarStatic === next?.avatarStatic
) )
const composeListeners = useMemo( const composeListeners = useMemo(
() => ({ () => ({
tabPress: (e: any) => { tabPress: (e: any) => {
e.preventDefault() e.preventDefault()
haptics('Light') haptics('Light')
navigation.navigate('Screen-Compose') navigation.navigate('Screen-Compose')
} }
}), }),
[] []
) )
const composeComponent = useCallback(() => null, []) const composeComponent = useCallback(() => null, [])
const meListeners = useMemo( const meListeners = useMemo(
() => ({ () => ({
tabLongPress: () => { tabLongPress: () => {
haptics('Light') haptics('Light')
//@ts-ignore //@ts-ignore
navigation.navigate('Tab-Me', { screen: 'Tab-Me-Root' }) navigation.navigate('Tab-Me', { screen: 'Tab-Me-Root' })
//@ts-ignore //@ts-ignore
navigation.navigate('Tab-Me', { screen: 'Tab-Me-Switch' }) navigation.navigate('Tab-Me', { screen: 'Tab-Me-Switch' })
} }
}), }),
[] []
) )
const previousTab = useSelector(getPreviousTab, () => true) const previousTab = useSelector(getPreviousTab, () => true)
return ( return (
<Tab.Navigator <Tab.Navigator
initialRouteName={instanceActive !== -1 ? previousTab : 'Tab-Me'} initialRouteName={instanceActive !== -1 ? previousTab : 'Tab-Me'}
screenOptions={({ route }) => ({ screenOptions={({ route }) => ({
headerShown: false, headerShown: false,
tabBarActiveTintColor: colors.primaryDefault, tabBarActiveTintColor: colors.primaryDefault,
tabBarInactiveTintColor: colors.secondary, tabBarInactiveTintColor: colors.secondary,
tabBarShowLabel: false, tabBarShowLabel: false,
...(Platform.OS === 'android' && { tabBarHideOnKeyboard: true }), ...(Platform.OS === 'android' && { tabBarHideOnKeyboard: true }),
tabBarStyle: { display: instanceActive !== -1 ? 'flex' : 'none' }, tabBarStyle: { display: instanceActive !== -1 ? 'flex' : 'none' },
tabBarIcon: ({ tabBarIcon: ({
focused, focused,
color, color,
size size
}: { }: {
focused: boolean focused: boolean
color: string color: string
size: number size: number
}) => { }) => {
switch (route.name) { switch (route.name) {
case 'Tab-Local': case 'Tab-Local':
return <Icon name='Home' size={size} color={color} /> return <Icon name='Home' size={size} color={color} />
case 'Tab-Public': case 'Tab-Public':
return <Icon name='Globe' size={size} color={color} /> return <Icon name='Globe' size={size} color={color} />
case 'Tab-Compose': case 'Tab-Compose':
return <Icon name='Plus' size={size} color={color} /> return <Icon name='Plus' size={size} color={color} />
case 'Tab-Notifications': case 'Tab-Notifications':
return <Icon name='Bell' size={size} color={color} /> return <Icon name='Bell' size={size} color={color} />
case 'Tab-Me': case 'Tab-Me':
return ( return (
<GracefullyImage <GracefullyImage
key={instanceAccount?.avatarStatic} key={instanceAccount?.avatarStatic}
uri={{ original: instanceAccount?.avatarStatic }} uri={{ original: instanceAccount?.avatarStatic }}
dimension={{ dimension={{
width: size, width: size,
height: size height: size
}} }}
style={{ style={{
borderRadius: size, borderRadius: size,
overflow: 'hidden', overflow: 'hidden',
borderWidth: focused ? 2 : 0, borderWidth: focused ? 2 : 0,
borderColor: focused ? colors.secondary : color borderColor: focused ? colors.secondary : color
}} }}
/> />
) )
default: default:
return <Icon name='AlertOctagon' size={size} color={color} /> return <Icon name='AlertOctagon' size={size} color={color} />
}
} }
})} }
> })}
<Tab.Screen name='Tab-Local' component={TabLocal} /> >
<Tab.Screen name='Tab-Public' component={TabPublic} /> <Tab.Screen name='Tab-Local' component={TabLocal} />
<Tab.Screen name='Tab-Compose' component={composeComponent} listeners={composeListeners} /> <Tab.Screen name='Tab-Public' component={TabPublic} />
<Tab.Screen name='Tab-Notifications' component={TabNotifications} /> <Tab.Screen name='Tab-Compose' component={composeComponent} listeners={composeListeners} />
<Tab.Screen name='Tab-Me' component={TabMe} listeners={meListeners} /> <Tab.Screen name='Tab-Notifications' component={TabNotifications} />
</Tab.Navigator> <Tab.Screen name='Tab-Me' component={TabMe} listeners={meListeners} />
) </Tab.Navigator>
}, )
() => true }
)
export default ScreenTabs export default ScreenTabs

View File

@ -3,22 +3,17 @@ import TimelineDefault from '@components/Timeline/Default'
import { QueryKeyTimeline } from '@utils/queryHooks/timeline' import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
import React from 'react' import React from 'react'
const TabMeBookmarks = React.memo( const TabMeBookmarks = () => {
() => { const queryKey: QueryKeyTimeline = ['Timeline', { page: 'Bookmarks' }]
const queryKey: QueryKeyTimeline = ['Timeline', { page: 'Bookmarks' }]
return ( return (
<Timeline <Timeline
queryKey={queryKey} queryKey={queryKey}
customProps={{ customProps={{
renderItem: ({ item }) => ( renderItem: ({ item }) => <TimelineDefault item={item} queryKey={queryKey} />
<TimelineDefault item={item} queryKey={queryKey} /> }}
) />
}} )
/> }
)
},
() => true
)
export default TabMeBookmarks export default TabMeBookmarks

View File

@ -3,22 +3,17 @@ import TimelineConversation from '@components/Timeline/Conversation'
import { QueryKeyTimeline } from '@utils/queryHooks/timeline' import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
import React from 'react' import React from 'react'
const TabMeConversations = React.memo( const TabMeConversations = () => {
() => { const queryKey: QueryKeyTimeline = ['Timeline', { page: 'Conversations' }]
const queryKey: QueryKeyTimeline = ['Timeline', { page: 'Conversations' }]
return ( return (
<Timeline <Timeline
queryKey={queryKey} queryKey={queryKey}
customProps={{ customProps={{
renderItem: ({ item }) => ( renderItem: ({ item }) => <TimelineConversation conversation={item} queryKey={queryKey} />
<TimelineConversation conversation={item} queryKey={queryKey} /> }}
) />
}} )
/> }
)
},
() => true
)
export default TabMeConversations export default TabMeConversations

View File

@ -3,22 +3,17 @@ import TimelineDefault from '@components/Timeline/Default'
import { QueryKeyTimeline } from '@utils/queryHooks/timeline' import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
import React from 'react' import React from 'react'
const TabMeFavourites = React.memo( const TabMeFavourites = () => {
() => { const queryKey: QueryKeyTimeline = ['Timeline', { page: 'Favourites' }]
const queryKey: QueryKeyTimeline = ['Timeline', { page: 'Favourites' }]
return ( return (
<Timeline <Timeline
queryKey={queryKey} queryKey={queryKey}
customProps={{ customProps={{
renderItem: ({ item }) => ( renderItem: ({ item }) => <TimelineDefault item={item} queryKey={queryKey} />
<TimelineDefault item={item} queryKey={queryKey} /> }}
) />
}} )
/> }
)
},
() => true
)
export default TabMeFavourites export default TabMeFavourites

View File

@ -12,65 +12,62 @@ export interface Props {
account: Mastodon.Account | undefined account: Mastodon.Account | undefined
} }
const AccountNav = React.memo( const AccountNav: React.FC<Props> = ({ scrollY, account }) => {
({ scrollY, account }: Props) => { const { colors } = useTheme()
const { colors } = useTheme() const headerHeight = useSafeAreaInsets().top + 44
const headerHeight = useSafeAreaInsets().top + 44
const nameY = const nameY =
Dimensions.get('window').width / 3 + Dimensions.get('window').width / 3 +
StyleConstants.Avatar.L - StyleConstants.Avatar.L -
StyleConstants.Spacing.Global.PagePadding * 2 + StyleConstants.Spacing.Global.PagePadding * 2 +
StyleConstants.Spacing.M - StyleConstants.Spacing.M -
headerHeight headerHeight
const styleOpacity = useAnimatedStyle(() => { const styleOpacity = useAnimatedStyle(() => {
return { return {
opacity: interpolate(scrollY.value, [0, 200], [0, 1], Extrapolate.CLAMP) opacity: interpolate(scrollY.value, [0, 200], [0, 1], Extrapolate.CLAMP)
} }
}) })
const styleMarginTop = useAnimatedStyle(() => { const styleMarginTop = useAnimatedStyle(() => {
return { return {
marginTop: interpolate(scrollY.value, [nameY, nameY + 20], [50, 0], Extrapolate.CLAMP) marginTop: interpolate(scrollY.value, [nameY, nameY + 20], [50, 0], Extrapolate.CLAMP)
} }
}) })
return ( return (
<Animated.View <Animated.View
style={[ style={[
styleOpacity, styleOpacity,
{ {
...StyleSheet.absoluteFillObject, ...StyleSheet.absoluteFillObject,
zIndex: 99, zIndex: 99,
backgroundColor: colors.backgroundDefault, backgroundColor: colors.backgroundDefault,
height: headerHeight height: headerHeight
} }
]} ]}
>
<View
style={{
flex: 1,
alignItems: 'center',
overflow: 'hidden',
marginTop: useSafeAreaInsets().top + (44 - StyleConstants.Font.Size.L) / 2
}}
> >
<View <Animated.View style={[{ flexDirection: 'row' }, styleMarginTop]}>
style={{ {account ? (
flex: 1, <CustomText numberOfLines={1}>
alignItems: 'center', <ParseEmojis
overflow: 'hidden', content={account.display_name || account.username}
marginTop: useSafeAreaInsets().top + (44 - StyleConstants.Font.Size.L) / 2 emojis={account.emojis}
}} fontBold
> />
<Animated.View style={[{ flexDirection: 'row' }, styleMarginTop]}> </CustomText>
{account ? ( ) : null}
<CustomText numberOfLines={1}> </Animated.View>
<ParseEmojis </View>
content={account.display_name || account.username} </Animated.View>
emojis={account.emojis} )
fontBold }
/>
</CustomText>
) : null}
</Animated.View>
</View>
</Animated.View>
)
},
(_, next) => next.account === undefined
)
export default AccountNav export default AccountNav