mirror of
https://github.com/tooot-app/app
synced 2025-06-05 22:19:13 +02:00
Remove most React memorization
Though added memo for timeline components making them (almost) pure
This commit is contained in:
@ -1,7 +1,7 @@
|
||||
import { RootStackScreenProps } from '@utils/navigation/navigators'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useCallback, useEffect } from 'react'
|
||||
import React, { useEffect } from 'react'
|
||||
import { Dimensions, StyleSheet, View } from 'react-native'
|
||||
import { PanGestureHandler, State, TapGestureHandler } from 'react-native-gesture-handler'
|
||||
import Animated, {
|
||||
@ -34,9 +34,8 @@ const ScreenActions = ({
|
||||
bottom: interpolate(panY.value, [0, screenHeight], [0, -screenHeight], Extrapolate.CLAMP)
|
||||
}
|
||||
})
|
||||
const dismiss = useCallback(() => {
|
||||
navigation.goBack()
|
||||
}, [])
|
||||
const dismiss = () => navigation.goBack()
|
||||
|
||||
const onGestureEvent = useAnimatedGestureHandler({
|
||||
onActive: ({ translationY }) => {
|
||||
panY.value = translationY
|
||||
|
@ -9,7 +9,7 @@ import { RootStackScreenProps } from '@utils/navigation/navigators'
|
||||
import { useAnnouncementMutation, useAnnouncementQuery } from '@utils/queryHooks/announcement'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import {
|
||||
Dimensions,
|
||||
@ -56,148 +56,140 @@ const ScreenAnnouncements: React.FC<RootStackScreenProps<'Screen-Announcements'>
|
||||
}
|
||||
}, [query.data])
|
||||
|
||||
const renderItem = useCallback(
|
||||
({ item, index }: { item: Mastodon.Announcement; index: number }) => (
|
||||
const renderItem = ({ item, index }: { item: Mastodon.Announcement; index: number }) => (
|
||||
<View
|
||||
key={index}
|
||||
style={{
|
||||
width: Dimensions.get('window').width,
|
||||
padding: StyleConstants.Spacing.Global.PagePadding,
|
||||
marginVertical: StyleConstants.Spacing.Global.PagePadding,
|
||||
justifyContent: 'center'
|
||||
}}
|
||||
>
|
||||
<Pressable style={StyleSheet.absoluteFillObject} onPress={() => navigation.goBack()} />
|
||||
<View
|
||||
key={index}
|
||||
style={{
|
||||
width: Dimensions.get('window').width,
|
||||
flexShrink: 1,
|
||||
padding: StyleConstants.Spacing.Global.PagePadding,
|
||||
marginVertical: StyleConstants.Spacing.Global.PagePadding,
|
||||
justifyContent: 'center'
|
||||
marginTop: StyleConstants.Spacing.Global.PagePadding,
|
||||
borderWidth: 1,
|
||||
borderRadius: 6,
|
||||
borderColor: colors.primaryDefault,
|
||||
backgroundColor: colors.backgroundDefault
|
||||
}}
|
||||
>
|
||||
<Pressable style={StyleSheet.absoluteFillObject} onPress={() => navigation.goBack()} />
|
||||
<View
|
||||
<CustomText
|
||||
fontStyle='S'
|
||||
style={{
|
||||
flexShrink: 1,
|
||||
padding: StyleConstants.Spacing.Global.PagePadding,
|
||||
marginTop: StyleConstants.Spacing.Global.PagePadding,
|
||||
borderWidth: 1,
|
||||
borderRadius: 6,
|
||||
borderColor: colors.primaryDefault,
|
||||
backgroundColor: colors.backgroundDefault
|
||||
marginBottom: StyleConstants.Spacing.S,
|
||||
color: colors.secondary
|
||||
}}
|
||||
>
|
||||
<CustomText
|
||||
fontStyle='S'
|
||||
style={{
|
||||
marginBottom: StyleConstants.Spacing.S,
|
||||
color: colors.secondary
|
||||
}}
|
||||
>
|
||||
<Trans
|
||||
ns='screenAnnouncements'
|
||||
i18nKey='content.published'
|
||||
components={[<RelativeTime time={item.published_at} />]}
|
||||
/>
|
||||
</CustomText>
|
||||
<ScrollView
|
||||
<Trans
|
||||
ns='screenAnnouncements'
|
||||
i18nKey='content.published'
|
||||
components={[<RelativeTime time={item.published_at} />]}
|
||||
/>
|
||||
</CustomText>
|
||||
<ScrollView
|
||||
style={{
|
||||
marginBottom: StyleConstants.Spacing.Global.PagePadding / 2
|
||||
}}
|
||||
showsVerticalScrollIndicator
|
||||
>
|
||||
<ParseHTML
|
||||
content={item.content}
|
||||
size='M'
|
||||
emojis={item.emojis}
|
||||
mentions={item.mentions}
|
||||
numberOfLines={999}
|
||||
selectable
|
||||
/>
|
||||
</ScrollView>
|
||||
{item.reactions?.length ? (
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
marginBottom: StyleConstants.Spacing.Global.PagePadding / 2
|
||||
}}
|
||||
showsVerticalScrollIndicator
|
||||
>
|
||||
<ParseHTML
|
||||
content={item.content}
|
||||
size='M'
|
||||
emojis={item.emojis}
|
||||
mentions={item.mentions}
|
||||
numberOfLines={999}
|
||||
selectable
|
||||
/>
|
||||
</ScrollView>
|
||||
{item.reactions?.length ? (
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
marginBottom: StyleConstants.Spacing.Global.PagePadding / 2
|
||||
}}
|
||||
>
|
||||
{item.reactions?.map(reaction => (
|
||||
<Pressable
|
||||
key={reaction.name}
|
||||
style={{
|
||||
borderWidth: 1,
|
||||
padding: StyleConstants.Spacing.Global.PagePadding / 2,
|
||||
marginTop: StyleConstants.Spacing.Global.PagePadding / 2,
|
||||
marginBottom: StyleConstants.Spacing.Global.PagePadding / 2,
|
||||
marginRight: StyleConstants.Spacing.M,
|
||||
borderRadius: 6,
|
||||
flexDirection: 'row',
|
||||
borderColor: reaction.me ? colors.disabled : colors.primaryDefault,
|
||||
backgroundColor: reaction.me ? colors.disabled : colors.backgroundDefault
|
||||
}}
|
||||
onPress={() =>
|
||||
mutation.mutate({
|
||||
id: item.id,
|
||||
type: 'reaction',
|
||||
name: reaction.name,
|
||||
me: reaction.me
|
||||
})
|
||||
}
|
||||
>
|
||||
{reaction.url ? (
|
||||
<FastImage
|
||||
source={{
|
||||
uri: reduceMotionEnabled ? reaction.static_url : reaction.url
|
||||
}}
|
||||
style={{
|
||||
width: StyleConstants.Font.LineHeight.M + 3,
|
||||
height: StyleConstants.Font.LineHeight.M
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<CustomText fontStyle='M'>{reaction.name}</CustomText>
|
||||
)}
|
||||
{reaction.count ? (
|
||||
<CustomText
|
||||
fontStyle='S'
|
||||
style={{
|
||||
marginLeft: StyleConstants.Spacing.S,
|
||||
color: colors.primaryDefault
|
||||
}}
|
||||
>
|
||||
{reaction.count}
|
||||
</CustomText>
|
||||
) : null}
|
||||
</Pressable>
|
||||
))}
|
||||
</View>
|
||||
) : null}
|
||||
<Button
|
||||
type='text'
|
||||
content={item.read ? t('content.button.read') : t('content.button.unread')}
|
||||
loading={mutation.isLoading}
|
||||
disabled={item.read}
|
||||
onPress={() => {
|
||||
!item.read &&
|
||||
mutation.mutate({
|
||||
id: item.id,
|
||||
type: 'dismiss'
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
{item.reactions?.map(reaction => (
|
||||
<Pressable
|
||||
key={reaction.name}
|
||||
style={{
|
||||
borderWidth: 1,
|
||||
padding: StyleConstants.Spacing.Global.PagePadding / 2,
|
||||
marginTop: StyleConstants.Spacing.Global.PagePadding / 2,
|
||||
marginBottom: StyleConstants.Spacing.Global.PagePadding / 2,
|
||||
marginRight: StyleConstants.Spacing.M,
|
||||
borderRadius: 6,
|
||||
flexDirection: 'row',
|
||||
borderColor: reaction.me ? colors.disabled : colors.primaryDefault,
|
||||
backgroundColor: reaction.me ? colors.disabled : colors.backgroundDefault
|
||||
}}
|
||||
onPress={() =>
|
||||
mutation.mutate({
|
||||
id: item.id,
|
||||
type: 'reaction',
|
||||
name: reaction.name,
|
||||
me: reaction.me
|
||||
})
|
||||
}
|
||||
>
|
||||
{reaction.url ? (
|
||||
<FastImage
|
||||
source={{
|
||||
uri: reduceMotionEnabled ? reaction.static_url : reaction.url
|
||||
}}
|
||||
style={{
|
||||
width: StyleConstants.Font.LineHeight.M + 3,
|
||||
height: StyleConstants.Font.LineHeight.M
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<CustomText fontStyle='M'>{reaction.name}</CustomText>
|
||||
)}
|
||||
{reaction.count ? (
|
||||
<CustomText
|
||||
fontStyle='S'
|
||||
style={{
|
||||
marginLeft: StyleConstants.Spacing.S,
|
||||
color: colors.primaryDefault
|
||||
}}
|
||||
>
|
||||
{reaction.count}
|
||||
</CustomText>
|
||||
) : null}
|
||||
</Pressable>
|
||||
))}
|
||||
</View>
|
||||
) : null}
|
||||
<Button
|
||||
type='text'
|
||||
content={item.read ? t('content.button.read') : t('content.button.unread')}
|
||||
loading={mutation.isLoading}
|
||||
disabled={item.read}
|
||||
onPress={() => {
|
||||
!item.read &&
|
||||
mutation.mutate({
|
||||
id: item.id,
|
||||
type: 'dismiss'
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
),
|
||||
[mode]
|
||||
</View>
|
||||
)
|
||||
|
||||
const onMomentumScrollEnd = useCallback(
|
||||
({
|
||||
nativeEvent: {
|
||||
contentOffset: { x },
|
||||
layoutMeasurement: { width }
|
||||
}
|
||||
}: NativeSyntheticEvent<NativeScrollEvent>) => {
|
||||
setIndex(Math.floor(x / width))
|
||||
},
|
||||
[]
|
||||
)
|
||||
const onMomentumScrollEnd = ({
|
||||
nativeEvent: {
|
||||
contentOffset: { x },
|
||||
layoutMeasurement: { width }
|
||||
}
|
||||
}: NativeSyntheticEvent<NativeScrollEvent>) => setIndex(Math.floor(x / width))
|
||||
|
||||
const ListEmptyComponent = useCallback(() => {
|
||||
const ListEmptyComponent = () => {
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
@ -209,7 +201,7 @@ const ScreenAnnouncements: React.FC<RootStackScreenProps<'Screen-Announcements'>
|
||||
<Circle size={StyleConstants.Font.Size.L} color={colors.secondary} />
|
||||
</View>
|
||||
)
|
||||
}, [])
|
||||
}
|
||||
|
||||
return Platform.OS === 'ios' ? (
|
||||
<BlurView
|
||||
|
@ -6,7 +6,7 @@ import { useActionSheet } from '@expo/react-native-action-sheet'
|
||||
import { androidActionSheetStyles } from '@utils/helpers/androidActionSheetStyles'
|
||||
import layoutAnimation from '@utils/styles/layoutAnimation'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useContext, useMemo } from 'react'
|
||||
import React, { useContext } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Keyboard, Pressable, StyleSheet, View } from 'react-native'
|
||||
import ComposeContext from '../utils/createContext'
|
||||
@ -18,7 +18,7 @@ const ComposeActions: React.FC = () => {
|
||||
const { t } = useTranslation(['common', 'screenCompose'])
|
||||
const { colors } = useTheme()
|
||||
|
||||
const attachmentColor = useMemo(() => {
|
||||
const attachmentColor = () => {
|
||||
if (composeState.poll.active) return colors.disabled
|
||||
|
||||
if (composeState.attachments.uploads.length) {
|
||||
@ -26,7 +26,7 @@ const ComposeActions: React.FC = () => {
|
||||
} else {
|
||||
return colors.secondary
|
||||
}
|
||||
}, [composeState.poll.active, composeState.attachments.uploads])
|
||||
}
|
||||
const attachmentOnPress = () => {
|
||||
if (composeState.poll.active) return
|
||||
|
||||
@ -35,7 +35,7 @@ const ComposeActions: React.FC = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const pollColor = useMemo(() => {
|
||||
const pollColor = () => {
|
||||
if (composeState.attachments.uploads.length) return colors.disabled
|
||||
|
||||
if (composeState.poll.active) {
|
||||
@ -43,7 +43,7 @@ const ComposeActions: React.FC = () => {
|
||||
} else {
|
||||
return colors.secondary
|
||||
}
|
||||
}, [composeState.poll.active, composeState.attachments.uploads])
|
||||
}
|
||||
const pollOnPress = () => {
|
||||
if (!composeState.attachments.uploads.length) {
|
||||
layoutAnimation()
|
||||
@ -57,7 +57,7 @@ const ComposeActions: React.FC = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const visibilityIcon = useMemo(() => {
|
||||
const visibilityIcon = () => {
|
||||
switch (composeState.visibility) {
|
||||
case 'public':
|
||||
return 'Globe'
|
||||
@ -68,7 +68,7 @@ const ComposeActions: React.FC = () => {
|
||||
case 'direct':
|
||||
return 'Mail'
|
||||
}
|
||||
}, [composeState.visibility])
|
||||
}
|
||||
const visibilityOnPress = () => {
|
||||
if (!composeState.visibilityLock) {
|
||||
showActionSheetWithOptions(
|
||||
@ -116,7 +116,7 @@ const ComposeActions: React.FC = () => {
|
||||
}
|
||||
|
||||
const { emojisState, emojisDispatch } = useContext(EmojisContext)
|
||||
const emojiColor = useMemo(() => {
|
||||
const emojiColor = () => {
|
||||
if (!emojis.current?.length) return colors.disabled
|
||||
|
||||
if (emojisState.targetIndex !== -1) {
|
||||
@ -124,7 +124,7 @@ const ComposeActions: React.FC = () => {
|
||||
} else {
|
||||
return colors.secondary
|
||||
}
|
||||
}, [emojis.current?.length, emojisState.targetIndex])
|
||||
}
|
||||
const emojiOnPress = () => {
|
||||
if (emojisState.targetIndex === -1) {
|
||||
Keyboard.dismiss()
|
||||
@ -159,7 +159,7 @@ const ComposeActions: React.FC = () => {
|
||||
}}
|
||||
style={styles.button}
|
||||
onPress={attachmentOnPress}
|
||||
children={<Icon name='Camera' size={24} color={attachmentColor} />}
|
||||
children={<Icon name='Camera' size={24} color={attachmentColor()} />}
|
||||
/>
|
||||
<Pressable
|
||||
accessibilityRole='button'
|
||||
@ -171,7 +171,7 @@ const ComposeActions: React.FC = () => {
|
||||
}}
|
||||
style={styles.button}
|
||||
onPress={pollOnPress}
|
||||
children={<Icon name='BarChart2' size={24} color={pollColor} />}
|
||||
children={<Icon name='BarChart2' size={24} color={pollColor()} />}
|
||||
/>
|
||||
<Pressable
|
||||
accessibilityRole='button'
|
||||
@ -183,7 +183,7 @@ const ComposeActions: React.FC = () => {
|
||||
onPress={visibilityOnPress}
|
||||
children={
|
||||
<Icon
|
||||
name={visibilityIcon}
|
||||
name={visibilityIcon()}
|
||||
size={24}
|
||||
color={composeState.visibilityLock ? colors.disabled : colors.secondary}
|
||||
/>
|
||||
@ -213,7 +213,7 @@ const ComposeActions: React.FC = () => {
|
||||
}}
|
||||
style={styles.button}
|
||||
onPress={emojiOnPress}
|
||||
children={<Icon name='Smile' size={24} color={emojiColor} />}
|
||||
children={<Icon name='Smile' size={24} color={emojiColor()} />}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
|
@ -8,7 +8,7 @@ import { useNavigation } from '@react-navigation/native'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import layoutAnimation from '@utils/styles/layoutAnimation'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { RefObject, useCallback, useContext, useEffect, useMemo, useRef } from 'react'
|
||||
import React, { RefObject, useContext, useEffect, useRef } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { FlatList, Pressable, StyleSheet, View } from 'react-native'
|
||||
import { Circle } from 'react-native-animated-spinkit'
|
||||
@ -32,16 +32,13 @@ const ComposeAttachments: React.FC<Props> = ({ accessibleRefAttachments }) => {
|
||||
|
||||
const flatListRef = useRef<FlatList>(null)
|
||||
|
||||
const sensitiveOnPress = useCallback(
|
||||
() =>
|
||||
composeDispatch({
|
||||
type: 'attachments/sensitive',
|
||||
payload: { sensitive: !composeState.attachments.sensitive }
|
||||
}),
|
||||
[composeState.attachments.sensitive]
|
||||
)
|
||||
const sensitiveOnPress = () =>
|
||||
composeDispatch({
|
||||
type: 'attachments/sensitive',
|
||||
payload: { sensitive: !composeState.attachments.sensitive }
|
||||
})
|
||||
|
||||
const calculateWidth = useCallback((item: ExtendedAttachment) => {
|
||||
const calculateWidth = (item: ExtendedAttachment) => {
|
||||
if (item.local) {
|
||||
return ((item.local.width || 100) / (item.local.height || 100)) * DEFAULT_HEIGHT
|
||||
} else {
|
||||
@ -59,9 +56,9 @@ const ComposeAttachments: React.FC<Props> = ({ accessibleRefAttachments }) => {
|
||||
return DEFAULT_HEIGHT
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
}
|
||||
|
||||
const snapToOffsets = useMemo(() => {
|
||||
const snapToOffsets = () => {
|
||||
const attachmentsOffsets = composeState.attachments.uploads.map((_, index) => {
|
||||
let currentOffset = 0
|
||||
Array.from(Array(index).keys()).map(
|
||||
@ -81,160 +78,116 @@ const ComposeAttachments: React.FC<Props> = ({ accessibleRefAttachments }) => {
|
||||
StyleConstants.Spacing.Global.PagePadding
|
||||
]
|
||||
: attachmentsOffsets
|
||||
}, [composeState.attachments.uploads.length])
|
||||
}
|
||||
let prevOffsets = useRef<number[]>()
|
||||
useEffect(() => {
|
||||
if (snapToOffsets.length > (prevOffsets.current ? prevOffsets.current.length : 0)) {
|
||||
const snap = snapToOffsets()
|
||||
if (snap.length > (prevOffsets.current ? prevOffsets.current.length : 0)) {
|
||||
flatListRef.current?.scrollToOffset({
|
||||
offset: snapToOffsets[snapToOffsets.length - 2] + snapToOffsets[snapToOffsets.length - 1]
|
||||
offset: snap[snapToOffsets.length - 2] + snap[snapToOffsets.length - 1]
|
||||
})
|
||||
}
|
||||
prevOffsets.current = snapToOffsets
|
||||
prevOffsets.current = snap
|
||||
}, [snapToOffsets, prevOffsets.current])
|
||||
|
||||
const renderAttachment = useCallback(
|
||||
({ item, index }: { item: ExtendedAttachment; index: number }) => {
|
||||
return (
|
||||
<View
|
||||
key={index}
|
||||
style={{
|
||||
height: DEFAULT_HEIGHT,
|
||||
marginLeft: StyleConstants.Spacing.Global.PagePadding,
|
||||
marginTop: StyleConstants.Spacing.Global.PagePadding,
|
||||
marginBottom: StyleConstants.Spacing.Global.PagePadding,
|
||||
width: calculateWidth(item)
|
||||
}}
|
||||
>
|
||||
<FastImage
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
source={{
|
||||
uri: item.local?.thumbnail || item.remote?.preview_url
|
||||
}}
|
||||
/>
|
||||
{item.remote?.meta?.original?.duration ? (
|
||||
<CustomText
|
||||
fontStyle='S'
|
||||
style={{
|
||||
position: 'absolute',
|
||||
bottom: StyleConstants.Spacing.S,
|
||||
left: StyleConstants.Spacing.S,
|
||||
paddingLeft: StyleConstants.Spacing.S,
|
||||
paddingRight: StyleConstants.Spacing.S,
|
||||
paddingTop: StyleConstants.Spacing.XS,
|
||||
paddingBottom: StyleConstants.Spacing.XS,
|
||||
color: colors.backgroundDefault,
|
||||
backgroundColor: colors.backgroundOverlayInvert
|
||||
}}
|
||||
>
|
||||
{item.remote.meta.original.duration}
|
||||
</CustomText>
|
||||
) : null}
|
||||
{item.uploading ? (
|
||||
<View
|
||||
style={{
|
||||
...StyleSheet.absoluteFillObject,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
backgroundColor: colors.backgroundOverlayInvert
|
||||
}}
|
||||
>
|
||||
<Circle size={StyleConstants.Font.Size.L} color={colors.primaryOverlay} />
|
||||
</View>
|
||||
) : (
|
||||
<View
|
||||
style={{
|
||||
...StyleSheet.absoluteFillObject,
|
||||
justifyContent: 'space-between',
|
||||
alignContent: 'flex-end',
|
||||
alignItems: 'flex-end',
|
||||
padding: StyleConstants.Spacing.S
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
accessibilityLabel={t('content.root.footer.attachments.remove.accessibilityLabel', {
|
||||
attachment: index + 1
|
||||
})}
|
||||
type='icon'
|
||||
content='X'
|
||||
spacing='M'
|
||||
round
|
||||
overlay
|
||||
onPress={() => {
|
||||
layoutAnimation()
|
||||
composeDispatch({
|
||||
type: 'attachment/delete',
|
||||
payload: item.remote!.id
|
||||
})
|
||||
haptics('Success')
|
||||
}}
|
||||
/>
|
||||
{!composeState.attachments.disallowEditing ? (
|
||||
<Button
|
||||
accessibilityLabel={t('content.root.footer.attachments.edit.accessibilityLabel', {
|
||||
attachment: index + 1
|
||||
})}
|
||||
type='icon'
|
||||
content='Edit'
|
||||
spacing='M'
|
||||
round
|
||||
overlay
|
||||
onPress={() => {
|
||||
navigation.navigate('Screen-Compose-EditAttachment', {
|
||||
index
|
||||
})
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
)
|
||||
},
|
||||
[]
|
||||
)
|
||||
|
||||
const listFooter = useMemo(
|
||||
() => (
|
||||
<Pressable
|
||||
accessible
|
||||
accessibilityLabel={t('content.root.footer.attachments.upload.accessibilityLabel')}
|
||||
const renderAttachment = ({ item, index }: { item: ExtendedAttachment; index: number }) => {
|
||||
return (
|
||||
<View
|
||||
key={index}
|
||||
style={{
|
||||
height: DEFAULT_HEIGHT,
|
||||
marginLeft: StyleConstants.Spacing.Global.PagePadding,
|
||||
marginTop: StyleConstants.Spacing.Global.PagePadding,
|
||||
marginBottom: StyleConstants.Spacing.Global.PagePadding,
|
||||
width: DEFAULT_HEIGHT,
|
||||
backgroundColor: colors.backgroundOverlayInvert
|
||||
}}
|
||||
onPress={async () => {
|
||||
await chooseAndUploadAttachment({
|
||||
composeDispatch,
|
||||
showActionSheetWithOptions
|
||||
})
|
||||
width: calculateWidth(item)
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
type='icon'
|
||||
content='UploadCloud'
|
||||
spacing='M'
|
||||
round
|
||||
overlay
|
||||
onPress={async () => {
|
||||
await chooseAndUploadAttachment({
|
||||
composeDispatch,
|
||||
showActionSheetWithOptions
|
||||
})
|
||||
}}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: (DEFAULT_HEIGHT - StyleConstants.Spacing.M * 2 - StyleConstants.Font.Size.M) / 2,
|
||||
left: (DEFAULT_HEIGHT - StyleConstants.Spacing.M * 2 - StyleConstants.Font.Size.M) / 2
|
||||
<FastImage
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
source={{
|
||||
uri: item.local?.thumbnail || item.remote?.preview_url
|
||||
}}
|
||||
/>
|
||||
</Pressable>
|
||||
),
|
||||
[]
|
||||
)
|
||||
{item.remote?.meta?.original?.duration ? (
|
||||
<CustomText
|
||||
fontStyle='S'
|
||||
style={{
|
||||
position: 'absolute',
|
||||
bottom: StyleConstants.Spacing.S,
|
||||
left: StyleConstants.Spacing.S,
|
||||
paddingLeft: StyleConstants.Spacing.S,
|
||||
paddingRight: StyleConstants.Spacing.S,
|
||||
paddingTop: StyleConstants.Spacing.XS,
|
||||
paddingBottom: StyleConstants.Spacing.XS,
|
||||
color: colors.backgroundDefault,
|
||||
backgroundColor: colors.backgroundOverlayInvert
|
||||
}}
|
||||
>
|
||||
{item.remote.meta.original.duration}
|
||||
</CustomText>
|
||||
) : null}
|
||||
{item.uploading ? (
|
||||
<View
|
||||
style={{
|
||||
...StyleSheet.absoluteFillObject,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
backgroundColor: colors.backgroundOverlayInvert
|
||||
}}
|
||||
>
|
||||
<Circle size={StyleConstants.Font.Size.L} color={colors.primaryOverlay} />
|
||||
</View>
|
||||
) : (
|
||||
<View
|
||||
style={{
|
||||
...StyleSheet.absoluteFillObject,
|
||||
justifyContent: 'space-between',
|
||||
alignContent: 'flex-end',
|
||||
alignItems: 'flex-end',
|
||||
padding: StyleConstants.Spacing.S
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
accessibilityLabel={t('content.root.footer.attachments.remove.accessibilityLabel', {
|
||||
attachment: index + 1
|
||||
})}
|
||||
type='icon'
|
||||
content='X'
|
||||
spacing='M'
|
||||
round
|
||||
overlay
|
||||
onPress={() => {
|
||||
layoutAnimation()
|
||||
composeDispatch({
|
||||
type: 'attachment/delete',
|
||||
payload: item.remote!.id
|
||||
})
|
||||
haptics('Success')
|
||||
}}
|
||||
/>
|
||||
{!composeState.attachments.disallowEditing ? (
|
||||
<Button
|
||||
accessibilityLabel={t('content.root.footer.attachments.edit.accessibilityLabel', {
|
||||
attachment: index + 1
|
||||
})}
|
||||
type='icon'
|
||||
content='Edit'
|
||||
spacing='M'
|
||||
round
|
||||
overlay
|
||||
onPress={() => {
|
||||
navigation.navigate('Screen-Compose-EditAttachment', {
|
||||
index
|
||||
})
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
@ -276,13 +229,54 @@ const ComposeAttachments: React.FC<Props> = ({ accessibleRefAttachments }) => {
|
||||
pagingEnabled={false}
|
||||
snapToAlignment='center'
|
||||
renderItem={renderAttachment}
|
||||
snapToOffsets={snapToOffsets}
|
||||
snapToOffsets={snapToOffsets()}
|
||||
keyboardShouldPersistTaps='always'
|
||||
showsHorizontalScrollIndicator={false}
|
||||
data={composeState.attachments.uploads}
|
||||
keyExtractor={item => item.local?.uri || item.remote?.url || Math.random().toString()}
|
||||
ListFooterComponent={
|
||||
composeState.attachments.uploads.length < MAX_MEDIA_ATTACHMENTS ? listFooter : null
|
||||
composeState.attachments.uploads.length < MAX_MEDIA_ATTACHMENTS ? (
|
||||
<Pressable
|
||||
accessible
|
||||
accessibilityLabel={t('content.root.footer.attachments.upload.accessibilityLabel')}
|
||||
style={{
|
||||
height: DEFAULT_HEIGHT,
|
||||
marginLeft: StyleConstants.Spacing.Global.PagePadding,
|
||||
marginTop: StyleConstants.Spacing.Global.PagePadding,
|
||||
marginBottom: StyleConstants.Spacing.Global.PagePadding,
|
||||
width: DEFAULT_HEIGHT,
|
||||
backgroundColor: colors.backgroundOverlayInvert
|
||||
}}
|
||||
onPress={async () => {
|
||||
await chooseAndUploadAttachment({
|
||||
composeDispatch,
|
||||
showActionSheetWithOptions
|
||||
})
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
type='icon'
|
||||
content='UploadCloud'
|
||||
spacing='M'
|
||||
round
|
||||
overlay
|
||||
onPress={async () => {
|
||||
await chooseAndUploadAttachment({
|
||||
composeDispatch,
|
||||
showActionSheetWithOptions
|
||||
})
|
||||
}}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top:
|
||||
(DEFAULT_HEIGHT - StyleConstants.Spacing.M * 2 - StyleConstants.Font.Size.M) /
|
||||
2,
|
||||
left:
|
||||
(DEFAULT_HEIGHT - StyleConstants.Spacing.M * 2 - StyleConstants.Font.Size.M) / 2
|
||||
}}
|
||||
/>
|
||||
</Pressable>
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
|
@ -2,7 +2,7 @@ import ComponentSeparator from '@components/Separator'
|
||||
import { useSearchQuery } from '@utils/queryHooks/search'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useContext, useEffect, useMemo, useRef } from 'react'
|
||||
import React, { useContext, useEffect, useRef } from 'react'
|
||||
import { AccessibilityInfo, findNodeHandle, FlatList, View } from 'react-native'
|
||||
import { Circle } from 'react-native-animated-spinkit'
|
||||
import ComposePosting from '../Posting'
|
||||
@ -53,29 +53,22 @@ const ComposeRoot = () => {
|
||||
}
|
||||
}, [composeState.tag])
|
||||
|
||||
const listEmpty = useMemo(() => {
|
||||
if (isFetching) {
|
||||
return (
|
||||
<View key='listEmpty' style={{ flex: 1, alignItems: 'center' }}>
|
||||
<Circle size={StyleConstants.Font.Size.M * 1.25} color={colors.secondary} />
|
||||
</View>
|
||||
)
|
||||
}
|
||||
}, [isFetching])
|
||||
|
||||
const Footer = useMemo(
|
||||
() => <ComposeRootFooter accessibleRefAttachments={accessibleRefAttachments} />,
|
||||
[accessibleRefAttachments.current]
|
||||
)
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1 }}>
|
||||
<FlatList
|
||||
renderItem={({ item }) => <ComposeRootSuggestion item={item} />}
|
||||
ListEmptyComponent={listEmpty}
|
||||
ListEmptyComponent={
|
||||
isFetching ? (
|
||||
<View key='listEmpty' style={{ flex: 1, alignItems: 'center' }}>
|
||||
<Circle size={StyleConstants.Font.Size.M * 1.25} color={colors.secondary} />
|
||||
</View>
|
||||
) : null
|
||||
}
|
||||
keyboardShouldPersistTaps='always'
|
||||
ListHeaderComponent={ComposeRootHeader}
|
||||
ListFooterComponent={Footer}
|
||||
ListFooterComponent={
|
||||
<ComposeRootFooter accessibleRefAttachments={accessibleRefAttachments} />
|
||||
}
|
||||
ItemSeparatorComponent={ComponentSeparator}
|
||||
// @ts-ignore
|
||||
data={data ? data[mapSchemaToType()] : undefined}
|
||||
|
@ -21,7 +21,7 @@ import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import * as StoreReview from 'expo-store-review'
|
||||
import { filter } from 'lodash'
|
||||
import React, { useCallback, useEffect, useMemo, useReducer, useState } from 'react'
|
||||
import React, { useEffect, useMemo, useReducer, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Alert, Keyboard, Platform } from 'react-native'
|
||||
import ComposeDraftsList, { removeDraft } from './DraftsList'
|
||||
@ -202,44 +202,7 @@ const ScreenCompose: React.FC<RootStackScreenProps<'Screen-Compose'>> = ({
|
||||
return () => (autoSave ? clearInterval(autoSave) : undefined)
|
||||
}, [composeState])
|
||||
|
||||
const headerLeft = useCallback(
|
||||
() => (
|
||||
<HeaderLeft
|
||||
type='text'
|
||||
content={t('common:buttons.cancel')}
|
||||
onPress={() => {
|
||||
if (!composeState.dirty) {
|
||||
navigation.goBack()
|
||||
return
|
||||
} else {
|
||||
Alert.alert(t('screenCompose:heading.left.alert.title'), undefined, [
|
||||
{
|
||||
text: t('screenCompose:heading.left.alert.buttons.delete'),
|
||||
style: 'destructive',
|
||||
onPress: () => {
|
||||
removeDraft(composeState.timestamp)
|
||||
navigation.goBack()
|
||||
}
|
||||
},
|
||||
{
|
||||
text: t('screenCompose:heading.left.alert.buttons.save'),
|
||||
onPress: () => {
|
||||
saveDraft()
|
||||
navigation.goBack()
|
||||
}
|
||||
},
|
||||
{
|
||||
text: t('common:buttons.cancel'),
|
||||
style: 'cancel'
|
||||
}
|
||||
])
|
||||
}
|
||||
}}
|
||||
/>
|
||||
),
|
||||
[composeState]
|
||||
)
|
||||
const headerRightDisabled = useMemo(() => {
|
||||
const headerRightDisabled = () => {
|
||||
if (totalTextCount > maxTootChars) {
|
||||
return true
|
||||
}
|
||||
@ -250,104 +213,8 @@ const ScreenCompose: React.FC<RootStackScreenProps<'Screen-Compose'>> = ({
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}, [totalTextCount, composeState.attachments.uploads, composeState.text.raw])
|
||||
}
|
||||
const mutateTimeline = useTimelineMutation({ onMutate: true })
|
||||
const headerRight = useCallback(
|
||||
() => (
|
||||
<HeaderRight
|
||||
type='text'
|
||||
content={t(
|
||||
`screenCompose:heading.right.button.${
|
||||
(params?.type &&
|
||||
(params.type === 'conversation'
|
||||
? params.visibility === 'direct'
|
||||
? params.type
|
||||
: 'default'
|
||||
: params.type)) ||
|
||||
'default'
|
||||
}`
|
||||
)}
|
||||
onPress={() => {
|
||||
composeDispatch({ type: 'posting', payload: true })
|
||||
|
||||
composePost(params, composeState)
|
||||
.then(res => {
|
||||
haptics('Success')
|
||||
if (Platform.OS === 'ios' && Platform.constants.osVersion === '13.3') {
|
||||
// https://github.com/tooot-app/app/issues/59
|
||||
} else {
|
||||
const currentCount = getGlobalStorage.number('app.count_till_store_review')
|
||||
if (currentCount === 10) {
|
||||
StoreReview?.isAvailableAsync()
|
||||
.then(() => StoreReview.requestReview())
|
||||
.catch(() => {})
|
||||
} else {
|
||||
setGlobalStorage('app.count_till_store_review', (currentCount || 0) + 1)
|
||||
}
|
||||
}
|
||||
|
||||
switch (params?.type) {
|
||||
case 'edit':
|
||||
mutateTimeline.mutate({
|
||||
type: 'editItem',
|
||||
queryKey: params.queryKey,
|
||||
rootQueryKey: params.rootQueryKey,
|
||||
status: res
|
||||
})
|
||||
break
|
||||
case 'deleteEdit':
|
||||
case 'reply':
|
||||
if (params?.queryKey && params.queryKey[1].page === 'Toot') {
|
||||
queryClient.invalidateQueries(params.queryKey)
|
||||
}
|
||||
break
|
||||
}
|
||||
removeDraft(composeState.timestamp)
|
||||
navigation.goBack()
|
||||
})
|
||||
.catch(error => {
|
||||
if (error?.removeReply) {
|
||||
Alert.alert(
|
||||
t('screenCompose:heading.right.alert.removeReply.title'),
|
||||
t('screenCompose:heading.right.alert.removeReply.description'),
|
||||
[
|
||||
{
|
||||
text: t('common:buttons.cancel'),
|
||||
onPress: () => {
|
||||
composeDispatch({ type: 'posting', payload: false })
|
||||
},
|
||||
style: 'destructive'
|
||||
},
|
||||
{
|
||||
text: t('screenCompose:heading.right.alert.removeReply.confirm'),
|
||||
onPress: () => {
|
||||
composeDispatch({ type: 'removeReply' })
|
||||
composeDispatch({ type: 'posting', payload: false })
|
||||
},
|
||||
style: 'default'
|
||||
}
|
||||
]
|
||||
)
|
||||
} else {
|
||||
haptics('Error')
|
||||
handleError({ message: 'Posting error', captureResponse: true })
|
||||
composeDispatch({ type: 'posting', payload: false })
|
||||
Alert.alert(t('screenCompose:heading.right.alert.default.title'), undefined, [
|
||||
{ text: t('screenCompose:heading.right.alert.default.button') }
|
||||
])
|
||||
}
|
||||
})
|
||||
}}
|
||||
loading={composeState.posting}
|
||||
disabled={headerRightDisabled}
|
||||
/>
|
||||
),
|
||||
[totalTextCount, composeState]
|
||||
)
|
||||
|
||||
const headerContent = useMemo(() => {
|
||||
return `${totalTextCount} / ${maxTootChars}`
|
||||
}, [totalTextCount, maxTootChars, composeState.dirty])
|
||||
|
||||
const inputProps: EmojisState['inputProps'] = [
|
||||
{
|
||||
@ -393,7 +260,7 @@ const ScreenCompose: React.FC<RootStackScreenProps<'Screen-Compose'>> = ({
|
||||
name='Screen-Compose-Root'
|
||||
component={ComposeRoot}
|
||||
options={{
|
||||
title: headerContent,
|
||||
title: `${totalTextCount} / ${maxTootChars}`,
|
||||
headerTitleStyle: {
|
||||
fontWeight:
|
||||
totalTextCount > maxTootChars
|
||||
@ -402,8 +269,133 @@ const ScreenCompose: React.FC<RootStackScreenProps<'Screen-Compose'>> = ({
|
||||
fontSize: StyleConstants.Font.Size.M
|
||||
},
|
||||
headerTintColor: totalTextCount > maxTootChars ? colors.red : colors.secondary,
|
||||
headerLeft,
|
||||
headerRight
|
||||
headerLeft: () => (
|
||||
<HeaderLeft
|
||||
type='text'
|
||||
content={t('common:buttons.cancel')}
|
||||
onPress={() => {
|
||||
if (!composeState.dirty) {
|
||||
navigation.goBack()
|
||||
return
|
||||
} else {
|
||||
Alert.alert(t('screenCompose:heading.left.alert.title'), undefined, [
|
||||
{
|
||||
text: t('screenCompose:heading.left.alert.buttons.delete'),
|
||||
style: 'destructive',
|
||||
onPress: () => {
|
||||
removeDraft(composeState.timestamp)
|
||||
navigation.goBack()
|
||||
}
|
||||
},
|
||||
{
|
||||
text: t('screenCompose:heading.left.alert.buttons.save'),
|
||||
onPress: () => {
|
||||
saveDraft()
|
||||
navigation.goBack()
|
||||
}
|
||||
},
|
||||
{
|
||||
text: t('common:buttons.cancel'),
|
||||
style: 'cancel'
|
||||
}
|
||||
])
|
||||
}
|
||||
}}
|
||||
/>
|
||||
),
|
||||
headerRight: () => (
|
||||
<HeaderRight
|
||||
type='text'
|
||||
content={t(
|
||||
`screenCompose:heading.right.button.${
|
||||
(params?.type &&
|
||||
(params.type === 'conversation'
|
||||
? params.visibility === 'direct'
|
||||
? params.type
|
||||
: 'default'
|
||||
: params.type)) ||
|
||||
'default'
|
||||
}`
|
||||
)}
|
||||
onPress={() => {
|
||||
composeDispatch({ type: 'posting', payload: true })
|
||||
|
||||
composePost(params, composeState)
|
||||
.then(res => {
|
||||
haptics('Success')
|
||||
if (Platform.OS === 'ios' && Platform.constants.osVersion === '13.3') {
|
||||
// https://github.com/tooot-app/app/issues/59
|
||||
} else {
|
||||
const currentCount = getGlobalStorage.number(
|
||||
'app.count_till_store_review'
|
||||
)
|
||||
if (currentCount === 10) {
|
||||
StoreReview?.isAvailableAsync()
|
||||
.then(() => StoreReview.requestReview())
|
||||
.catch(() => {})
|
||||
} else {
|
||||
setGlobalStorage('app.count_till_store_review', (currentCount || 0) + 1)
|
||||
}
|
||||
}
|
||||
|
||||
switch (params?.type) {
|
||||
case 'edit':
|
||||
mutateTimeline.mutate({
|
||||
type: 'editItem',
|
||||
queryKey: params.queryKey,
|
||||
rootQueryKey: params.rootQueryKey,
|
||||
status: res
|
||||
})
|
||||
break
|
||||
case 'deleteEdit':
|
||||
case 'reply':
|
||||
if (params?.queryKey && params.queryKey[1].page === 'Toot') {
|
||||
queryClient.invalidateQueries(params.queryKey)
|
||||
}
|
||||
break
|
||||
}
|
||||
removeDraft(composeState.timestamp)
|
||||
navigation.goBack()
|
||||
})
|
||||
.catch(error => {
|
||||
if (error?.removeReply) {
|
||||
Alert.alert(
|
||||
t('screenCompose:heading.right.alert.removeReply.title'),
|
||||
t('screenCompose:heading.right.alert.removeReply.description'),
|
||||
[
|
||||
{
|
||||
text: t('common:buttons.cancel'),
|
||||
onPress: () => {
|
||||
composeDispatch({ type: 'posting', payload: false })
|
||||
},
|
||||
style: 'destructive'
|
||||
},
|
||||
{
|
||||
text: t('screenCompose:heading.right.alert.removeReply.confirm'),
|
||||
onPress: () => {
|
||||
composeDispatch({ type: 'removeReply' })
|
||||
composeDispatch({ type: 'posting', payload: false })
|
||||
},
|
||||
style: 'default'
|
||||
}
|
||||
]
|
||||
)
|
||||
} else {
|
||||
haptics('Error')
|
||||
handleError({ message: 'Posting error', captureResponse: true })
|
||||
composeDispatch({ type: 'posting', payload: false })
|
||||
Alert.alert(
|
||||
t('screenCompose:heading.right.alert.default.title'),
|
||||
undefined,
|
||||
[{ text: t('screenCompose:heading.right.alert.default.button') }]
|
||||
)
|
||||
}
|
||||
})
|
||||
}}
|
||||
loading={composeState.posting}
|
||||
disabled={headerRightDisabled()}
|
||||
/>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
|
@ -4,7 +4,7 @@ import { useActionSheet } from '@expo/react-native-action-sheet'
|
||||
import { androidActionSheetStyles } from '@utils/helpers/androidActionSheetStyles'
|
||||
import { RootStackScreenProps } from '@utils/navigation/navigators'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import React, { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
Dimensions,
|
||||
@ -40,111 +40,16 @@ const ScreenImagesViewer = ({
|
||||
|
||||
const insets = useSafeAreaInsets()
|
||||
|
||||
const { mode, colors } = useTheme()
|
||||
const { colors } = useTheme()
|
||||
const { t } = useTranslation(['common', 'screenImageViewer'])
|
||||
|
||||
const initialIndex = imageUrls.findIndex(image => image.id === id)
|
||||
const [currentIndex, setCurrentIndex] = useState(initialIndex)
|
||||
|
||||
const { showActionSheetWithOptions } = useActionSheet()
|
||||
const onPress = useCallback(() => {
|
||||
showActionSheetWithOptions(
|
||||
{
|
||||
options: [
|
||||
t('screenImageViewer:content.options.save'),
|
||||
t('screenImageViewer:content.options.share'),
|
||||
t('common:buttons.cancel')
|
||||
],
|
||||
cancelButtonIndex: 2,
|
||||
...androidActionSheetStyles(colors)
|
||||
},
|
||||
async buttonIndex => {
|
||||
switch (buttonIndex) {
|
||||
case 0:
|
||||
saveImage({ image: imageUrls[currentIndex] })
|
||||
break
|
||||
case 1:
|
||||
switch (Platform.OS) {
|
||||
case 'ios':
|
||||
await Share.share({ url: imageUrls[currentIndex].url })
|
||||
break
|
||||
case 'android':
|
||||
await Share.share({ message: imageUrls[currentIndex].url })
|
||||
break
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
)
|
||||
}, [currentIndex])
|
||||
|
||||
const isZoomed = useSharedValue(false)
|
||||
|
||||
const renderItem = React.useCallback(
|
||||
({
|
||||
item
|
||||
}: {
|
||||
item: RootStackScreenProps<'Screen-ImagesViewer'>['route']['params']['imageUrls'][0]
|
||||
}) => {
|
||||
const screenRatio = WINDOW_WIDTH / WINDOW_HEIGHT
|
||||
const imageRatio = item.width && item.height ? item.width / item.height : 1
|
||||
const imageWidth = item.width || 100
|
||||
const imageHeight = item.height || 100
|
||||
|
||||
const maxWidthScale = item.width ? (item.width / WINDOW_WIDTH / PixelRatio.get()) * 4 : 0
|
||||
const maxHeightScale = item.height ? (item.height / WINDOW_WIDTH / PixelRatio.get()) * 4 : 0
|
||||
const max = Math.max.apply(Math, [maxWidthScale, maxHeightScale, 4])
|
||||
|
||||
return (
|
||||
<Zoom
|
||||
onZoomBegin={() => (isZoomed.value = true)}
|
||||
onZoomEnd={() => (isZoomed.value = false)}
|
||||
maximumZoomScale={max > 8 ? 8 : max}
|
||||
simultaneousGesture={Gesture.Fling()
|
||||
.direction(Directions.DOWN)
|
||||
.onStart(() => {
|
||||
if (isZoomed.value === false) {
|
||||
runOnJS(navigation.goBack)()
|
||||
}
|
||||
})}
|
||||
children={
|
||||
<View
|
||||
style={{
|
||||
width: WINDOW_WIDTH,
|
||||
height: WINDOW_HEIGHT,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
}}
|
||||
>
|
||||
<GracefullyImage
|
||||
uri={{ preview: item.preview_url, remote: item.remote_url, original: item.url }}
|
||||
dimension={{
|
||||
width:
|
||||
screenRatio > imageRatio
|
||||
? (WINDOW_HEIGHT / imageHeight) * imageWidth
|
||||
: WINDOW_WIDTH,
|
||||
height:
|
||||
screenRatio > imageRatio
|
||||
? WINDOW_HEIGHT
|
||||
: (WINDOW_WIDTH / imageWidth) * imageHeight
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
}
|
||||
/>
|
||||
)
|
||||
},
|
||||
[isZoomed.value]
|
||||
)
|
||||
|
||||
const onViewableItemsChanged = useCallback(
|
||||
({ viewableItems }: { viewableItems: ViewToken[] }) => {
|
||||
setCurrentIndex(viewableItems[0]?.index || 0)
|
||||
},
|
||||
[]
|
||||
)
|
||||
|
||||
return (
|
||||
<View style={{ backgroundColor: 'black' }}>
|
||||
<StatusBar hidden />
|
||||
@ -169,7 +74,36 @@ const ScreenImagesViewer = ({
|
||||
content='MoreHorizontal'
|
||||
native={false}
|
||||
background
|
||||
onPress={onPress}
|
||||
onPress={() =>
|
||||
showActionSheetWithOptions(
|
||||
{
|
||||
options: [
|
||||
t('screenImageViewer:content.options.save'),
|
||||
t('screenImageViewer:content.options.share'),
|
||||
t('common:buttons.cancel')
|
||||
],
|
||||
cancelButtonIndex: 2,
|
||||
...androidActionSheetStyles(colors)
|
||||
},
|
||||
async buttonIndex => {
|
||||
switch (buttonIndex) {
|
||||
case 0:
|
||||
saveImage({ image: imageUrls[currentIndex] })
|
||||
break
|
||||
case 1:
|
||||
switch (Platform.OS) {
|
||||
case 'ios':
|
||||
await Share.share({ url: imageUrls[currentIndex].url })
|
||||
break
|
||||
case 'android':
|
||||
await Share.share({ message: imageUrls[currentIndex].url })
|
||||
break
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
<LongPressGestureHandler
|
||||
@ -211,8 +145,71 @@ const ScreenImagesViewer = ({
|
||||
pagingEnabled
|
||||
horizontal
|
||||
keyExtractor={item => item.id}
|
||||
renderItem={renderItem}
|
||||
onViewableItemsChanged={onViewableItemsChanged}
|
||||
renderItem={({
|
||||
item
|
||||
}: {
|
||||
item: RootStackScreenProps<'Screen-ImagesViewer'>['route']['params']['imageUrls'][0]
|
||||
}) => {
|
||||
const screenRatio = WINDOW_WIDTH / WINDOW_HEIGHT
|
||||
const imageRatio = item.width && item.height ? item.width / item.height : 1
|
||||
const imageWidth = item.width || 100
|
||||
const imageHeight = item.height || 100
|
||||
|
||||
const maxWidthScale = item.width
|
||||
? (item.width / WINDOW_WIDTH / PixelRatio.get()) * 4
|
||||
: 0
|
||||
const maxHeightScale = item.height
|
||||
? (item.height / WINDOW_WIDTH / PixelRatio.get()) * 4
|
||||
: 0
|
||||
const max = Math.max.apply(Math, [maxWidthScale, maxHeightScale, 4])
|
||||
|
||||
return (
|
||||
<Zoom
|
||||
onZoomBegin={() => (isZoomed.value = true)}
|
||||
onZoomEnd={() => (isZoomed.value = false)}
|
||||
maximumZoomScale={max > 8 ? 8 : max}
|
||||
simultaneousGesture={Gesture.Fling()
|
||||
.direction(Directions.DOWN)
|
||||
.onStart(() => {
|
||||
if (isZoomed.value === false) {
|
||||
runOnJS(navigation.goBack)()
|
||||
}
|
||||
})}
|
||||
children={
|
||||
<View
|
||||
style={{
|
||||
width: WINDOW_WIDTH,
|
||||
height: WINDOW_HEIGHT,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
}}
|
||||
>
|
||||
<GracefullyImage
|
||||
uri={{
|
||||
preview: item.preview_url,
|
||||
remote: item.remote_url,
|
||||
original: item.url
|
||||
}}
|
||||
dimension={{
|
||||
width:
|
||||
screenRatio > imageRatio
|
||||
? (WINDOW_HEIGHT / imageHeight) * imageWidth
|
||||
: WINDOW_WIDTH,
|
||||
height:
|
||||
screenRatio > imageRatio
|
||||
? WINDOW_HEIGHT
|
||||
: (WINDOW_WIDTH / imageWidth) * imageHeight
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}}
|
||||
onViewableItemsChanged={({ viewableItems }: { viewableItems: ViewToken[] }) => {
|
||||
setCurrentIndex(viewableItems[0]?.index || 0)
|
||||
}}
|
||||
viewabilityConfig={{
|
||||
itemVisiblePercentThreshold: 50
|
||||
}}
|
||||
|
@ -6,8 +6,8 @@ import { TabLocalStackParamList } from '@utils/navigation/navigators'
|
||||
import { useTimelineQuery } from '@utils/queryHooks/timeline'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useCallback } from 'react'
|
||||
import { Dimensions, ListRenderItem, Pressable, View } from 'react-native'
|
||||
import React from 'react'
|
||||
import { Dimensions, Pressable, View } from 'react-native'
|
||||
import { FlatList } from 'react-native-gesture-handler'
|
||||
import Animated, { useAnimatedStyle, withTiming } from 'react-native-reanimated'
|
||||
|
||||
@ -39,55 +39,6 @@ const AccountAttachments: React.FC<Props> = ({ account }) => {
|
||||
.splice(0, DISPLAY_AMOUNT)
|
||||
: []
|
||||
|
||||
const renderItem = useCallback<ListRenderItem<Mastodon.Status>>(
|
||||
({ item, index }) => {
|
||||
if (index === DISPLAY_AMOUNT - 1) {
|
||||
return (
|
||||
<Pressable
|
||||
onPress={() => {
|
||||
account && navigation.push('Tab-Shared-Attachments', { account })
|
||||
}}
|
||||
children={
|
||||
<View
|
||||
style={{
|
||||
marginHorizontal: StyleConstants.Spacing.Global.PagePadding,
|
||||
backgroundColor: colors.backgroundOverlayInvert,
|
||||
width: width,
|
||||
height: width,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center'
|
||||
}}
|
||||
children={
|
||||
<Icon
|
||||
name='MoreHorizontal'
|
||||
color={colors.primaryOverlay}
|
||||
size={StyleConstants.Font.Size.L * 1.5}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<GracefullyImage
|
||||
uri={{
|
||||
original: item.media_attachments[0]?.preview_url || item.media_attachments[0]?.url,
|
||||
remote: item.media_attachments[0]?.remote_url
|
||||
}}
|
||||
blurhash={
|
||||
item.media_attachments[0] && (item.media_attachments[0].blurhash || undefined)
|
||||
}
|
||||
dimension={{ width: width, height: width }}
|
||||
style={{ marginLeft: StyleConstants.Spacing.Global.PagePadding }}
|
||||
onPress={() => navigation.push('Tab-Shared-Toot', { toot: item })}
|
||||
/>
|
||||
)
|
||||
}
|
||||
},
|
||||
[account]
|
||||
)
|
||||
|
||||
const styleContainer = useAnimatedStyle(() => {
|
||||
if (flattenData.length) {
|
||||
return {
|
||||
@ -106,7 +57,52 @@ const AccountAttachments: React.FC<Props> = ({ account }) => {
|
||||
<FlatList
|
||||
horizontal
|
||||
data={flattenData}
|
||||
renderItem={renderItem}
|
||||
renderItem={({ item, index }) => {
|
||||
if (index === DISPLAY_AMOUNT - 1) {
|
||||
return (
|
||||
<Pressable
|
||||
onPress={() => {
|
||||
account && navigation.push('Tab-Shared-Attachments', { account })
|
||||
}}
|
||||
children={
|
||||
<View
|
||||
style={{
|
||||
marginHorizontal: StyleConstants.Spacing.Global.PagePadding,
|
||||
backgroundColor: colors.backgroundOverlayInvert,
|
||||
width: width,
|
||||
height: width,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center'
|
||||
}}
|
||||
children={
|
||||
<Icon
|
||||
name='MoreHorizontal'
|
||||
color={colors.primaryOverlay}
|
||||
size={StyleConstants.Font.Size.L * 1.5}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<GracefullyImage
|
||||
uri={{
|
||||
original:
|
||||
item.media_attachments[0]?.preview_url || item.media_attachments[0]?.url,
|
||||
remote: item.media_attachments[0]?.remote_url
|
||||
}}
|
||||
blurhash={
|
||||
item.media_attachments[0] && (item.media_attachments[0].blurhash || undefined)
|
||||
}
|
||||
dimension={{ width: width, height: width }}
|
||||
style={{ marginLeft: StyleConstants.Spacing.Global.PagePadding }}
|
||||
onPress={() => navigation.push('Tab-Shared-Toot', { toot: item })}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}}
|
||||
showsHorizontalScrollIndicator={false}
|
||||
/>
|
||||
</Animated.View>
|
||||
|
@ -2,7 +2,7 @@ import { ParseEmojis } from '@components/Parse'
|
||||
import CustomText from '@components/Text'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useMemo } from 'react'
|
||||
import React from 'react'
|
||||
import { View } from 'react-native'
|
||||
import { PlaceholderLine } from 'rn-placeholder'
|
||||
|
||||
@ -13,21 +13,6 @@ export interface Props {
|
||||
const AccountInformationName: React.FC<Props> = ({ account }) => {
|
||||
const { colors } = useTheme()
|
||||
|
||||
const movedContent = useMemo(() => {
|
||||
if (account?.moved) {
|
||||
return (
|
||||
<View style={{ marginLeft: StyleConstants.Spacing.S }}>
|
||||
<ParseEmojis
|
||||
content={account.moved.display_name || account.moved.username}
|
||||
emojis={account.moved.emojis}
|
||||
size='L'
|
||||
fontBold
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
}, [account?.moved])
|
||||
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
@ -51,7 +36,16 @@ const AccountInformationName: React.FC<Props> = ({ account }) => {
|
||||
fontBold
|
||||
/>
|
||||
</CustomText>
|
||||
{movedContent}
|
||||
{account.moved ? (
|
||||
<View style={{ marginLeft: StyleConstants.Spacing.S }}>
|
||||
<ParseEmojis
|
||||
content={account.moved.display_name || account.moved.username}
|
||||
emojis={account.moved.emojis}
|
||||
size='L'
|
||||
fontBold
|
||||
/>
|
||||
</View>
|
||||
) : null}
|
||||
</>
|
||||
) : (
|
||||
<PlaceholderLine
|
||||
|
@ -9,7 +9,7 @@ import { SearchResult } from '@utils/queryHooks/search'
|
||||
import { QueryKeyUsers, useUsersQuery } from '@utils/queryHooks/users'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { View } from 'react-native'
|
||||
import { Circle, Flow } from 'react-native-animated-spinkit'
|
||||
@ -41,11 +41,6 @@ const TabSharedUsers: React.FC<TabSharedStackScreenProps<'Tab-Shared-Users'>> =
|
||||
})
|
||||
const flattenData = data?.pages ? data.pages.flatMap(page => [...page.body]) : []
|
||||
|
||||
const onEndReached = useCallback(
|
||||
() => hasNextPage && !isFetchingNextPage && fetchNextPage(),
|
||||
[hasNextPage, isFetchingNextPage]
|
||||
)
|
||||
|
||||
const [isSearching, setIsSearching] = useState(false)
|
||||
|
||||
return (
|
||||
@ -90,7 +85,7 @@ const TabSharedUsers: React.FC<TabSharedStackScreenProps<'Tab-Shared-Users'>> =
|
||||
children={<Flow size={StyleConstants.Font.Size.L} color={colors.secondary} />}
|
||||
/>
|
||||
)}
|
||||
onEndReached={onEndReached}
|
||||
onEndReached={() => hasNextPage && !isFetchingNextPage && fetchNextPage()}
|
||||
onEndReachedThreshold={0.75}
|
||||
ItemSeparatorComponent={ComponentSeparator}
|
||||
ListEmptyComponent={
|
||||
|
@ -5,7 +5,7 @@ import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'
|
||||
import { RootStackScreenProps, ScreenTabsStackParamList } from '@utils/navigation/navigators'
|
||||
import { getGlobalStorage, useAccountStorage, useGlobalStorage } from '@utils/storage/actions'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useCallback, useMemo } from 'react'
|
||||
import React from 'react'
|
||||
import { Platform } from 'react-native'
|
||||
import TabLocal from './Local'
|
||||
import TabMe from './Me'
|
||||
@ -20,31 +20,6 @@ const ScreenTabs = ({ navigation }: RootStackScreenProps<'Screen-Tabs'>) => {
|
||||
const [accountActive] = useGlobalStorage.string('account.active')
|
||||
const [avatarStatic] = useAccountStorage.string('auth.account.avatar_static')
|
||||
|
||||
const composeListeners = useMemo(
|
||||
() => ({
|
||||
tabPress: (e: any) => {
|
||||
e.preventDefault()
|
||||
haptics('Light')
|
||||
navigation.navigate('Screen-Compose')
|
||||
}
|
||||
}),
|
||||
[]
|
||||
)
|
||||
const composeComponent = useCallback(() => null, [])
|
||||
|
||||
const meListeners = useMemo(
|
||||
() => ({
|
||||
tabLongPress: () => {
|
||||
haptics('Light')
|
||||
//@ts-ignore
|
||||
navigation.navigate('Tab-Me', { screen: 'Tab-Me-Root' })
|
||||
//@ts-ignore
|
||||
navigation.navigate('Tab-Me', { screen: 'Tab-Me-Switch' })
|
||||
}
|
||||
}),
|
||||
[]
|
||||
)
|
||||
|
||||
return (
|
||||
<Tab.Navigator
|
||||
initialRouteName={accountActive ? getGlobalStorage.string('app.prev_tab') : 'Tab-Me'}
|
||||
@ -97,9 +72,32 @@ const ScreenTabs = ({ navigation }: RootStackScreenProps<'Screen-Tabs'>) => {
|
||||
>
|
||||
<Tab.Screen name='Tab-Local' component={TabLocal} />
|
||||
<Tab.Screen name='Tab-Public' component={TabPublic} />
|
||||
<Tab.Screen name='Tab-Compose' component={composeComponent} listeners={composeListeners} />
|
||||
<Tab.Screen
|
||||
name='Tab-Compose'
|
||||
listeners={{
|
||||
tabPress: e => {
|
||||
e.preventDefault()
|
||||
haptics('Light')
|
||||
navigation.navigate('Screen-Compose')
|
||||
}
|
||||
}}
|
||||
>
|
||||
{() => null}
|
||||
</Tab.Screen>
|
||||
<Tab.Screen name='Tab-Notifications' component={TabNotifications} />
|
||||
<Tab.Screen name='Tab-Me' component={TabMe} listeners={meListeners} />
|
||||
<Tab.Screen
|
||||
name='Tab-Me'
|
||||
component={TabMe}
|
||||
listeners={{
|
||||
tabLongPress: () => {
|
||||
haptics('Light')
|
||||
//@ts-ignore
|
||||
navigation.navigate('Tab-Me', { screen: 'Tab-Me-Root' })
|
||||
//@ts-ignore
|
||||
navigation.navigate('Tab-Me', { screen: 'Tab-Me-Switch' })
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Tab.Navigator>
|
||||
)
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import { themes } from '@utils/styles/themes'
|
||||
import * as Linking from 'expo-linking'
|
||||
import { addScreenshotListener } from 'expo-screen-capture'
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { IntlProvider } from 'react-intl'
|
||||
import { Alert, Platform, StatusBar } from 'react-native'
|
||||
@ -90,7 +90,7 @@ const Screens: React.FC<Props> = ({ localCorrupt }) => {
|
||||
useEmojisQuery({ options: { enabled: !!accountActive } })
|
||||
|
||||
// Callbacks
|
||||
const navigationContainerOnStateChange = useCallback(() => {
|
||||
const navigationContainerOnStateChange = () => {
|
||||
const currentRoute = navigationRef.getCurrentRoute()
|
||||
|
||||
const matchTabName = currentRoute?.name?.match(/(Tab-.*)-Root/)
|
||||
@ -98,7 +98,7 @@ const Screens: React.FC<Props> = ({ localCorrupt }) => {
|
||||
// @ts-ignore
|
||||
setGlobalStorage('app.prev_tab', matchTabName[1])
|
||||
}
|
||||
}, [])
|
||||
}
|
||||
|
||||
// Deep linking for compose
|
||||
const [deeplinked, setDeeplinked] = useState(false)
|
||||
@ -128,109 +128,106 @@ const Screens: React.FC<Props> = ({ localCorrupt }) => {
|
||||
}, [accounts, accountActive, deeplinked])
|
||||
|
||||
// Share Extension
|
||||
const handleShare = useCallback(
|
||||
(
|
||||
item?:
|
||||
| {
|
||||
data: { mimeType: string; data: string }[]
|
||||
mimeType: undefined
|
||||
}
|
||||
| { data: string | string[]; mimeType: string }
|
||||
) => {
|
||||
if (!accountActive) {
|
||||
return
|
||||
}
|
||||
if (!item || !item.data) {
|
||||
return
|
||||
}
|
||||
|
||||
let text: string | undefined = undefined
|
||||
let media: { uri: string; mime: string }[] = []
|
||||
|
||||
const typesImage = ['png', 'jpg', 'jpeg', 'gif']
|
||||
const typesVideo = ['mp4', 'm4v', 'mov', 'webm', 'mpeg']
|
||||
const filterMedia = ({ uri, mime }: { uri: string; mime: string }) => {
|
||||
if (mime.startsWith('image/')) {
|
||||
if (!typesImage.includes(mime.split('/')[1])) {
|
||||
console.warn('Image type not supported:', mime.split('/')[1])
|
||||
displayMessage({
|
||||
message: t('screens:shareError.imageNotSupported', {
|
||||
type: mime.split('/')[1]
|
||||
}),
|
||||
type: 'danger'
|
||||
})
|
||||
return
|
||||
}
|
||||
media.push({ uri, mime })
|
||||
} else if (mime.startsWith('video/')) {
|
||||
if (!typesVideo.includes(mime.split('/')[1])) {
|
||||
console.warn('Video type not supported:', mime.split('/')[1])
|
||||
displayMessage({
|
||||
message: t('screens:shareError.videoNotSupported', {
|
||||
type: mime.split('/')[1]
|
||||
}),
|
||||
type: 'danger'
|
||||
})
|
||||
return
|
||||
}
|
||||
media.push({ uri, mime })
|
||||
} else {
|
||||
if (typesImage.includes(uri.split('.').pop() || '')) {
|
||||
media.push({ uri, mime: 'image/jpg' })
|
||||
return
|
||||
}
|
||||
if (typesVideo.includes(uri.split('.').pop() || '')) {
|
||||
media.push({ uri, mime: 'video/mp4' })
|
||||
return
|
||||
}
|
||||
text = !text ? uri : text.concat(text, `\n${uri}`)
|
||||
const handleShare = (
|
||||
item?:
|
||||
| {
|
||||
data: { mimeType: string; data: string }[]
|
||||
mimeType: undefined
|
||||
}
|
||||
}
|
||||
| { data: string | string[]; mimeType: string }
|
||||
) => {
|
||||
if (!accountActive) {
|
||||
return
|
||||
}
|
||||
if (!item || !item.data) {
|
||||
return
|
||||
}
|
||||
|
||||
switch (Platform.OS) {
|
||||
case 'ios':
|
||||
if (!Array.isArray(item.data) || !item.data) {
|
||||
return
|
||||
}
|
||||
let text: string | undefined = undefined
|
||||
let media: { uri: string; mime: string }[] = []
|
||||
|
||||
for (const d of item.data) {
|
||||
if (typeof d !== 'string') {
|
||||
filterMedia({ uri: d.data, mime: d.mimeType })
|
||||
}
|
||||
}
|
||||
break
|
||||
case 'android':
|
||||
if (!item.mimeType) {
|
||||
return
|
||||
}
|
||||
if (Array.isArray(item.data)) {
|
||||
for (const d of item.data) {
|
||||
filterMedia({ uri: d, mime: item.mimeType })
|
||||
}
|
||||
} else {
|
||||
filterMedia({ uri: item.data, mime: item.mimeType })
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if (!text && !media.length) {
|
||||
return
|
||||
const typesImage = ['png', 'jpg', 'jpeg', 'gif']
|
||||
const typesVideo = ['mp4', 'm4v', 'mov', 'webm', 'mpeg']
|
||||
const filterMedia = ({ uri, mime }: { uri: string; mime: string }) => {
|
||||
if (mime.startsWith('image/')) {
|
||||
if (!typesImage.includes(mime.split('/')[1])) {
|
||||
console.warn('Image type not supported:', mime.split('/')[1])
|
||||
displayMessage({
|
||||
message: t('screens:shareError.imageNotSupported', {
|
||||
type: mime.split('/')[1]
|
||||
}),
|
||||
type: 'danger'
|
||||
})
|
||||
return
|
||||
}
|
||||
media.push({ uri, mime })
|
||||
} else if (mime.startsWith('video/')) {
|
||||
if (!typesVideo.includes(mime.split('/')[1])) {
|
||||
console.warn('Video type not supported:', mime.split('/')[1])
|
||||
displayMessage({
|
||||
message: t('screens:shareError.videoNotSupported', {
|
||||
type: mime.split('/')[1]
|
||||
}),
|
||||
type: 'danger'
|
||||
})
|
||||
return
|
||||
}
|
||||
media.push({ uri, mime })
|
||||
} else {
|
||||
if (accounts?.length) {
|
||||
navigationRef.navigate('Screen-AccountSelection', {
|
||||
share: { text, media }
|
||||
})
|
||||
} else {
|
||||
navigationRef.navigate('Screen-Compose', {
|
||||
type: 'share',
|
||||
text,
|
||||
media
|
||||
})
|
||||
if (typesImage.includes(uri.split('.').pop() || '')) {
|
||||
media.push({ uri, mime: 'image/jpg' })
|
||||
return
|
||||
}
|
||||
if (typesVideo.includes(uri.split('.').pop() || '')) {
|
||||
media.push({ uri, mime: 'video/mp4' })
|
||||
return
|
||||
}
|
||||
text = !text ? uri : text.concat(text, `\n${uri}`)
|
||||
}
|
||||
},
|
||||
[]
|
||||
)
|
||||
}
|
||||
|
||||
switch (Platform.OS) {
|
||||
case 'ios':
|
||||
if (!Array.isArray(item.data) || !item.data) {
|
||||
return
|
||||
}
|
||||
|
||||
for (const d of item.data) {
|
||||
if (typeof d !== 'string') {
|
||||
filterMedia({ uri: d.data, mime: d.mimeType })
|
||||
}
|
||||
}
|
||||
break
|
||||
case 'android':
|
||||
if (!item.mimeType) {
|
||||
return
|
||||
}
|
||||
if (Array.isArray(item.data)) {
|
||||
for (const d of item.data) {
|
||||
filterMedia({ uri: d, mime: item.mimeType })
|
||||
}
|
||||
} else {
|
||||
filterMedia({ uri: item.data, mime: item.mimeType })
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if (!text && !media.length) {
|
||||
return
|
||||
} else {
|
||||
if (accounts?.length) {
|
||||
navigationRef.navigate('Screen-AccountSelection', {
|
||||
share: { text, media }
|
||||
})
|
||||
} else {
|
||||
navigationRef.navigate('Screen-Compose', {
|
||||
type: 'share',
|
||||
text,
|
||||
media
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
useEffect(() => {
|
||||
ShareMenu.getInitialShare(handleShare)
|
||||
}, [])
|
||||
|
Reference in New Issue
Block a user