mirror of https://github.com/tooot-app/app
commit
f6f078dc1a
|
@ -5,7 +5,7 @@ import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { adaptiveScale } from '@utils/styles/scaling'
|
import { adaptiveScale } from '@utils/styles/scaling'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React, { useMemo } from 'react'
|
import React, { useMemo } from 'react'
|
||||||
import { StyleSheet } from 'react-native'
|
import { Platform, StyleSheet } from 'react-native'
|
||||||
import FastImage from 'react-native-fast-image'
|
import FastImage from 'react-native-fast-image'
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
import validUrl from 'valid-url'
|
import validUrl from 'valid-url'
|
||||||
|
@ -51,7 +51,13 @@ const ParseEmojis = React.memo(
|
||||||
image: {
|
image: {
|
||||||
width: adaptedFontsize,
|
width: adaptedFontsize,
|
||||||
height: adaptedFontsize,
|
height: adaptedFontsize,
|
||||||
transform: [{ translateY: -2 }]
|
...(Platform.OS === 'ios'
|
||||||
|
? {
|
||||||
|
transform: [{ translateY: -2 }]
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
transform: [{ translateY: 1 }]
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}, [theme, adaptiveFontsize])
|
}, [theme, adaptiveFontsize])
|
||||||
|
|
|
@ -13,7 +13,7 @@ import { adaptiveScale } from '@utils/styles/scaling'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React, { useCallback, useState } from 'react'
|
import React, { useCallback, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Pressable, View } from 'react-native'
|
import { Platform, Pressable, View } from 'react-native'
|
||||||
import HTMLView from 'react-native-htmlview'
|
import HTMLView from 'react-native-htmlview'
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
|
|
||||||
|
@ -139,7 +139,13 @@ const renderNode = ({
|
||||||
name='ExternalLink'
|
name='ExternalLink'
|
||||||
size={adaptedFontsize}
|
size={adaptedFontsize}
|
||||||
style={{
|
style={{
|
||||||
transform: [{ translateY: -2 }]
|
...(Platform.OS === 'ios'
|
||||||
|
? {
|
||||||
|
transform: [{ translateY: -2 }]
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
transform: [{ translateY: 1 }]
|
||||||
|
})
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
|
@ -1,10 +1,6 @@
|
||||||
import ComponentSeparator from '@components/Separator'
|
import ComponentSeparator from '@components/Separator'
|
||||||
import { useScrollToTop } from '@react-navigation/native'
|
import { useScrollToTop } from '@react-navigation/native'
|
||||||
import {
|
import { QueryKeyTimeline, useTimelineQuery } from '@utils/queryHooks/timeline'
|
||||||
QueryKeyTimeline,
|
|
||||||
TimelineData,
|
|
||||||
useTimelineQuery
|
|
||||||
} from '@utils/queryHooks/timeline'
|
|
||||||
import { getInstanceActive } from '@utils/slices/instancesSlice'
|
import { getInstanceActive } from '@utils/slices/instancesSlice'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
|
@ -16,10 +12,20 @@ import {
|
||||||
RefreshControl,
|
RefreshControl,
|
||||||
StyleSheet
|
StyleSheet
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
import { InfiniteData, useQueryClient } from 'react-query'
|
import Animated, {
|
||||||
|
useAnimatedScrollHandler,
|
||||||
|
useSharedValue
|
||||||
|
} from 'react-native-reanimated'
|
||||||
|
import { useQueryClient } from 'react-query'
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
import TimelineEmpty from './Timeline/Empty'
|
import TimelineEmpty from './Timeline/Empty'
|
||||||
import TimelineFooter from './Timeline/Footer'
|
import TimelineFooter from './Timeline/Footer'
|
||||||
|
import TimelineRefresh, {
|
||||||
|
SEPARATION_Y_1,
|
||||||
|
SEPARATION_Y_2
|
||||||
|
} from './Timeline/Refresh'
|
||||||
|
|
||||||
|
const AnimatedFlatList = Animated.createAnimatedComponent(FlatList)
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
flRef?: RefObject<FlatList<any>>
|
flRef?: RefObject<FlatList<any>>
|
||||||
|
@ -40,15 +46,12 @@ const Timeline: React.FC<Props> = ({
|
||||||
}) => {
|
}) => {
|
||||||
const { colors } = useTheme()
|
const { colors } = useTheme()
|
||||||
|
|
||||||
const queryClient = useQueryClient()
|
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
refetch,
|
refetch,
|
||||||
isFetching,
|
isFetching,
|
||||||
isLoading,
|
isLoading,
|
||||||
fetchPreviousPage,
|
|
||||||
fetchNextPage,
|
fetchNextPage,
|
||||||
isFetchingPreviousPage,
|
|
||||||
isFetchingNextPage
|
isFetchingNextPage
|
||||||
} = useTimelineQuery({
|
} = useTimelineQuery({
|
||||||
...queryKey[1],
|
...queryKey[1],
|
||||||
|
@ -57,12 +60,6 @@ const Timeline: React.FC<Props> = ({
|
||||||
ios: ['dataUpdatedAt', 'isFetching'],
|
ios: ['dataUpdatedAt', 'isFetching'],
|
||||||
android: ['dataUpdatedAt', 'isFetching', 'isLoading']
|
android: ['dataUpdatedAt', 'isFetching', 'isLoading']
|
||||||
}),
|
}),
|
||||||
getPreviousPageParam: firstPage =>
|
|
||||||
firstPage?.links?.prev && {
|
|
||||||
min_id: firstPage.links.prev,
|
|
||||||
// https://github.com/facebook/react-native/issues/25239
|
|
||||||
limit: '10'
|
|
||||||
},
|
|
||||||
getNextPageParam: lastPage =>
|
getNextPageParam: lastPage =>
|
||||||
lastPage?.links?.next && {
|
lastPage?.links?.next && {
|
||||||
max_id: lastPage.links.next
|
max_id: lastPage.links.next
|
||||||
|
@ -92,6 +89,27 @@ const Timeline: React.FC<Props> = ({
|
||||||
|
|
||||||
const flRef = useRef<FlatList>(null)
|
const flRef = useRef<FlatList>(null)
|
||||||
|
|
||||||
|
const scrollY = useSharedValue(0)
|
||||||
|
const fetchingType = useSharedValue<0 | 1 | 2>(0)
|
||||||
|
|
||||||
|
const onScroll = useAnimatedScrollHandler(
|
||||||
|
{
|
||||||
|
onScroll: ({ contentOffset: { y } }) => {
|
||||||
|
scrollY.value = y
|
||||||
|
},
|
||||||
|
onEndDrag: ({ contentOffset: { y } }) => {
|
||||||
|
if (!disableRefresh && !isFetching) {
|
||||||
|
if (y <= SEPARATION_Y_2) {
|
||||||
|
fetchingType.value = 2
|
||||||
|
} else if (y <= SEPARATION_Y_1) {
|
||||||
|
fetchingType.value = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[isFetching]
|
||||||
|
)
|
||||||
|
|
||||||
const androidRefreshControl = Platform.select({
|
const androidRefreshControl = Platform.select({
|
||||||
android: {
|
android: {
|
||||||
refreshControl: (
|
refreshControl: (
|
||||||
|
@ -115,46 +133,40 @@ const Timeline: React.FC<Props> = ({
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FlatList
|
<>
|
||||||
ref={customFLRef || flRef}
|
<TimelineRefresh
|
||||||
scrollEventThrottle={16}
|
flRef={flRef}
|
||||||
windowSize={7}
|
queryKey={queryKey}
|
||||||
data={flattenData}
|
scrollY={scrollY}
|
||||||
initialNumToRender={6}
|
fetchingType={fetchingType}
|
||||||
maxToRenderPerBatch={3}
|
disableRefresh={disableRefresh}
|
||||||
style={styles.flatList}
|
/>
|
||||||
onEndReached={onEndReached}
|
<AnimatedFlatList
|
||||||
onEndReachedThreshold={0.75}
|
ref={customFLRef || flRef}
|
||||||
ListFooterComponent={
|
scrollEventThrottle={16}
|
||||||
<TimelineFooter queryKey={queryKey} disableInfinity={disableInfinity} />
|
onScroll={onScroll}
|
||||||
}
|
windowSize={7}
|
||||||
ListEmptyComponent={<TimelineEmpty queryKey={queryKey} />}
|
data={flattenData}
|
||||||
ItemSeparatorComponent={ItemSeparatorComponent}
|
initialNumToRender={6}
|
||||||
{...(isFetchingPreviousPage && {
|
maxToRenderPerBatch={3}
|
||||||
maintainVisibleContentPosition: { minIndexForVisible: 0 }
|
style={styles.flatList}
|
||||||
})}
|
onEndReached={onEndReached}
|
||||||
refreshing={isFetchingPreviousPage}
|
onEndReachedThreshold={0.75}
|
||||||
onRefresh={() => {
|
ListFooterComponent={
|
||||||
if (!disableRefresh && !isFetchingPreviousPage) {
|
<TimelineFooter
|
||||||
queryClient.setQueryData<InfiniteData<TimelineData> | undefined>(
|
queryKey={queryKey}
|
||||||
queryKey,
|
disableInfinity={disableInfinity}
|
||||||
data => {
|
/>
|
||||||
if (data?.pages[0] && data.pages[0].body.length === 0) {
|
|
||||||
return {
|
|
||||||
pages: data.pages.slice(1),
|
|
||||||
pageParams: data.pageParams.slice(1)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
fetchPreviousPage()
|
|
||||||
}
|
}
|
||||||
}}
|
ListEmptyComponent={<TimelineEmpty queryKey={queryKey} />}
|
||||||
{...androidRefreshControl}
|
ItemSeparatorComponent={ItemSeparatorComponent}
|
||||||
{...customProps}
|
maintainVisibleContentPosition={{
|
||||||
/>
|
minIndexForVisible: 0
|
||||||
|
}}
|
||||||
|
{...androidRefreshControl}
|
||||||
|
{...customProps}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,323 @@
|
||||||
|
import haptics from '@components/haptics'
|
||||||
|
import Icon from '@components/Icon'
|
||||||
|
import {
|
||||||
|
QueryKeyTimeline,
|
||||||
|
TimelineData,
|
||||||
|
useTimelineQuery
|
||||||
|
} from '@utils/queryHooks/timeline'
|
||||||
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
|
import React, { RefObject, useCallback, useRef, useState } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { FlatList, Platform, StyleSheet, Text, View } from 'react-native'
|
||||||
|
import { Circle } from 'react-native-animated-spinkit'
|
||||||
|
import Animated, {
|
||||||
|
Extrapolate,
|
||||||
|
interpolate,
|
||||||
|
runOnJS,
|
||||||
|
useAnimatedReaction,
|
||||||
|
useAnimatedStyle,
|
||||||
|
useSharedValue,
|
||||||
|
withTiming
|
||||||
|
} from 'react-native-reanimated'
|
||||||
|
import { InfiniteData, useQueryClient } from 'react-query'
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
flRef: RefObject<FlatList<any>>
|
||||||
|
queryKey: QueryKeyTimeline
|
||||||
|
scrollY: Animated.SharedValue<number>
|
||||||
|
fetchingType: Animated.SharedValue<0 | 1 | 2>
|
||||||
|
disableRefresh?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const CONTAINER_HEIGHT = StyleConstants.Spacing.M * 2.5
|
||||||
|
export const SEPARATION_Y_1 = -(
|
||||||
|
CONTAINER_HEIGHT / 2 +
|
||||||
|
StyleConstants.Font.Size.S / 2
|
||||||
|
)
|
||||||
|
export const SEPARATION_Y_2 = -(
|
||||||
|
CONTAINER_HEIGHT * 1.5 +
|
||||||
|
StyleConstants.Font.Size.S / 2
|
||||||
|
)
|
||||||
|
|
||||||
|
const TimelineRefresh: React.FC<Props> = ({
|
||||||
|
flRef,
|
||||||
|
queryKey,
|
||||||
|
scrollY,
|
||||||
|
fetchingType,
|
||||||
|
disableRefresh = false
|
||||||
|
}) => {
|
||||||
|
if (Platform.OS !== 'ios') {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if (disableRefresh) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchingLatestIndex = useRef(0)
|
||||||
|
const refetchActive = useRef(false)
|
||||||
|
|
||||||
|
const {
|
||||||
|
refetch,
|
||||||
|
isFetching,
|
||||||
|
isLoading,
|
||||||
|
fetchPreviousPage,
|
||||||
|
hasPreviousPage,
|
||||||
|
isFetchingNextPage
|
||||||
|
} = useTimelineQuery({
|
||||||
|
...queryKey[1],
|
||||||
|
options: {
|
||||||
|
getPreviousPageParam: firstPage =>
|
||||||
|
firstPage?.links?.prev && {
|
||||||
|
min_id: firstPage.links.prev,
|
||||||
|
// https://github.com/facebook/react-native/issues/25239#issuecomment-731100372
|
||||||
|
limit: '3'
|
||||||
|
},
|
||||||
|
select: data => {
|
||||||
|
if (refetchActive.current) {
|
||||||
|
data.pageParams = [data.pageParams[0]]
|
||||||
|
data.pages = [data.pages[0]]
|
||||||
|
refetchActive.current = false
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
if (fetchingLatestIndex.current > 0) {
|
||||||
|
if (fetchingLatestIndex.current > 5) {
|
||||||
|
clearFirstPage()
|
||||||
|
fetchingLatestIndex.current = 0
|
||||||
|
} else {
|
||||||
|
if (hasPreviousPage) {
|
||||||
|
fetchPreviousPage()
|
||||||
|
fetchingLatestIndex.current++
|
||||||
|
} else {
|
||||||
|
clearFirstPage()
|
||||||
|
fetchingLatestIndex.current = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const { t } = useTranslation('componentTimeline')
|
||||||
|
const { colors } = useTheme()
|
||||||
|
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
const clearFirstPage = () => {
|
||||||
|
queryClient.setQueryData<InfiniteData<TimelineData> | undefined>(
|
||||||
|
queryKey,
|
||||||
|
data => {
|
||||||
|
if (data?.pages[0] && data.pages[0].body.length === 0) {
|
||||||
|
return {
|
||||||
|
pages: data.pages.slice(1),
|
||||||
|
pageParams: data.pageParams.slice(1)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
const prepareRefetch = () => {
|
||||||
|
refetchActive.current = true
|
||||||
|
queryClient.setQueryData<InfiniteData<TimelineData> | undefined>(
|
||||||
|
queryKey,
|
||||||
|
data => {
|
||||||
|
if (data) {
|
||||||
|
data.pageParams = [undefined]
|
||||||
|
const newFirstPage: TimelineData = { body: [] }
|
||||||
|
for (let page of data.pages) {
|
||||||
|
// @ts-ignore
|
||||||
|
newFirstPage.body.push(...page.body)
|
||||||
|
if (newFirstPage.body.length > 10) break
|
||||||
|
}
|
||||||
|
data.pages = [newFirstPage]
|
||||||
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
const callRefetch = async () => {
|
||||||
|
await refetch()
|
||||||
|
setTimeout(() => flRef.current?.scrollToOffset({ offset: 1 }), 50)
|
||||||
|
}
|
||||||
|
|
||||||
|
const [textRight, setTextRight] = useState(0)
|
||||||
|
const arrowY = useAnimatedStyle(() => ({
|
||||||
|
transform: [
|
||||||
|
{
|
||||||
|
translateY: interpolate(
|
||||||
|
scrollY.value,
|
||||||
|
[0, SEPARATION_Y_1],
|
||||||
|
[
|
||||||
|
-CONTAINER_HEIGHT / 2 - StyleConstants.Font.Size.M / 2,
|
||||||
|
CONTAINER_HEIGHT / 2 - StyleConstants.Font.Size.S / 2
|
||||||
|
],
|
||||||
|
Extrapolate.CLAMP
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}))
|
||||||
|
const arrowTop = useAnimatedStyle(() => ({
|
||||||
|
marginTop:
|
||||||
|
scrollY.value < SEPARATION_Y_2
|
||||||
|
? withTiming(CONTAINER_HEIGHT)
|
||||||
|
: withTiming(0)
|
||||||
|
}))
|
||||||
|
|
||||||
|
const arrowStage = useSharedValue(0)
|
||||||
|
const onLayout = useCallback(
|
||||||
|
({ nativeEvent }) => {
|
||||||
|
if (nativeEvent.layout.x + nativeEvent.layout.width > textRight) {
|
||||||
|
setTextRight(nativeEvent.layout.x + nativeEvent.layout.width)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[textRight]
|
||||||
|
)
|
||||||
|
useAnimatedReaction(
|
||||||
|
() => {
|
||||||
|
if (isFetching) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
switch (arrowStage.value) {
|
||||||
|
case 0:
|
||||||
|
if (scrollY.value < SEPARATION_Y_1) {
|
||||||
|
arrowStage.value = 1
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
case 1:
|
||||||
|
if (scrollY.value < SEPARATION_Y_2) {
|
||||||
|
arrowStage.value = 2
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (scrollY.value > SEPARATION_Y_1) {
|
||||||
|
arrowStage.value = 0
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
case 2:
|
||||||
|
if (scrollY.value > SEPARATION_Y_2) {
|
||||||
|
arrowStage.value = 1
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data => {
|
||||||
|
if (data) {
|
||||||
|
runOnJS(haptics)('Light')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[isFetching]
|
||||||
|
)
|
||||||
|
const wrapperStartLatest = () => {
|
||||||
|
fetchingLatestIndex.current = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
useAnimatedReaction(
|
||||||
|
() => {
|
||||||
|
return fetchingType.value
|
||||||
|
},
|
||||||
|
data => {
|
||||||
|
fetchingType.value = 0
|
||||||
|
switch (data) {
|
||||||
|
case 1:
|
||||||
|
runOnJS(wrapperStartLatest)()
|
||||||
|
runOnJS(clearFirstPage)()
|
||||||
|
runOnJS(fetchPreviousPage)()
|
||||||
|
break
|
||||||
|
case 2:
|
||||||
|
runOnJS(prepareRefetch)()
|
||||||
|
runOnJS(callRefetch)()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
|
||||||
|
const headerPadding = useAnimatedStyle(
|
||||||
|
() => ({
|
||||||
|
paddingTop:
|
||||||
|
fetchingLatestIndex.current !== 0 ||
|
||||||
|
(isFetching && !isLoading && !isFetchingNextPage)
|
||||||
|
? withTiming(StyleConstants.Spacing.M * 2.5)
|
||||||
|
: withTiming(0)
|
||||||
|
}),
|
||||||
|
[fetchingLatestIndex.current, isFetching, isFetchingNextPage, isLoading]
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Animated.View style={headerPadding}>
|
||||||
|
<View style={styles.base}>
|
||||||
|
{isFetching ? (
|
||||||
|
<View style={styles.container2}>
|
||||||
|
<Circle
|
||||||
|
size={StyleConstants.Font.Size.L}
|
||||||
|
color={colors.secondary}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<View style={styles.container1}>
|
||||||
|
<Text
|
||||||
|
style={[styles.explanation, { color: colors.primaryDefault }]}
|
||||||
|
onLayout={onLayout}
|
||||||
|
children={t('refresh.fetchPreviousPage')}
|
||||||
|
/>
|
||||||
|
<Animated.View
|
||||||
|
style={[
|
||||||
|
{
|
||||||
|
position: 'absolute',
|
||||||
|
left: textRight + StyleConstants.Spacing.S
|
||||||
|
},
|
||||||
|
arrowY,
|
||||||
|
arrowTop
|
||||||
|
]}
|
||||||
|
children={
|
||||||
|
<Icon
|
||||||
|
name='ArrowLeft'
|
||||||
|
size={StyleConstants.Font.Size.M}
|
||||||
|
color={colors.primaryDefault}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<View style={styles.container2}>
|
||||||
|
<Text
|
||||||
|
style={[styles.explanation, { color: colors.primaryDefault }]}
|
||||||
|
onLayout={onLayout}
|
||||||
|
children={t('refresh.refetch')}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</Animated.View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
base: {
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
height: CONTAINER_HEIGHT * 2,
|
||||||
|
alignItems: 'center'
|
||||||
|
},
|
||||||
|
container1: {
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: 'row',
|
||||||
|
height: CONTAINER_HEIGHT
|
||||||
|
},
|
||||||
|
container2: { height: CONTAINER_HEIGHT, justifyContent: 'center' },
|
||||||
|
explanation: {
|
||||||
|
fontSize: StyleConstants.Font.Size.S,
|
||||||
|
lineHeight: CONTAINER_HEIGHT
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default TimelineRefresh
|
|
@ -8,11 +8,13 @@ import AttachmentVideo from '@components/Timeline/Shared/Attachment/Video'
|
||||||
import { useNavigation } from '@react-navigation/native'
|
import { useNavigation } from '@react-navigation/native'
|
||||||
import { StackNavigationProp } from '@react-navigation/stack'
|
import { StackNavigationProp } from '@react-navigation/stack'
|
||||||
import { RootStackParamList } from '@utils/navigation/navigators'
|
import { RootStackParamList } from '@utils/navigation/navigators'
|
||||||
|
import { getInstanceAccount } from '@utils/slices/instancesSlice'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import layoutAnimation from '@utils/styles/layoutAnimation'
|
import layoutAnimation from '@utils/styles/layoutAnimation'
|
||||||
import React, { useRef, useState } from 'react'
|
import React, { useRef, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Pressable, View } from 'react-native'
|
import { Pressable, View } from 'react-native'
|
||||||
|
import { useSelector } from 'react-redux'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
status: Pick<Mastodon.Status, 'media_attachments' | 'sensitive'>
|
status: Pick<Mastodon.Status, 'media_attachments' | 'sensitive'>
|
||||||
|
@ -22,7 +24,23 @@ const TimelineAttachment = React.memo(
|
||||||
({ status }: Props) => {
|
({ status }: Props) => {
|
||||||
const { t } = useTranslation('componentTimeline')
|
const { t } = useTranslation('componentTimeline')
|
||||||
|
|
||||||
const [sensitiveShown, setSensitiveShown] = useState(status.sensitive)
|
const account = useSelector(
|
||||||
|
getInstanceAccount,
|
||||||
|
(prev, next) =>
|
||||||
|
prev.preferences['reading:expand:media'] ===
|
||||||
|
next.preferences['reading:expand:media']
|
||||||
|
)
|
||||||
|
const defaultSensitive = () => {
|
||||||
|
switch (account.preferences['reading:expand:media']) {
|
||||||
|
case 'show_all':
|
||||||
|
return false
|
||||||
|
case 'hide_all':
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return status.sensitive
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const [sensitiveShown, setSensitiveShown] = useState(defaultSensitive())
|
||||||
|
|
||||||
const imageUrls = useRef<
|
const imageUrls = useRef<
|
||||||
RootStackParamList['Screen-ImagesViewer']['imageUrls']
|
RootStackParamList['Screen-ImagesViewer']['imageUrls']
|
||||||
|
@ -151,7 +169,7 @@ const TimelineAttachment = React.memo(
|
||||||
})}
|
})}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{status.sensitive &&
|
{defaultSensitive() &&
|
||||||
(sensitiveShown ? (
|
(sensitiveShown ? (
|
||||||
<Pressable
|
<Pressable
|
||||||
style={{
|
style={{
|
||||||
|
|
|
@ -55,7 +55,7 @@ export const shouldFilter = ({
|
||||||
) // $& means the whole matched string
|
) // $& means the whole matched string
|
||||||
switch (filter.whole_word) {
|
switch (filter.whole_word) {
|
||||||
case true:
|
case true:
|
||||||
if (new RegExp('\\B' + escapedPhrase + '\\B').test(text)) {
|
if (new RegExp('\\b' + escapedPhrase + '\\b').test(text)) {
|
||||||
shouldFilter = true
|
shouldFilter = true
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
@ -100,6 +100,7 @@ export const shouldFilter = ({
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
status.spoiler_text && parser.write(status.spoiler_text)
|
||||||
parser.write(status.content)
|
parser.write(status.content)
|
||||||
parser.end()
|
parser.end()
|
||||||
}
|
}
|
||||||
|
|
|
@ -168,6 +168,7 @@
|
||||||
"header": {
|
"header": {
|
||||||
"title": "Entwurf"
|
"title": "Entwurf"
|
||||||
},
|
},
|
||||||
|
"warning": "",
|
||||||
"content": {
|
"content": {
|
||||||
"accessibilityHint": "Gespeicherter Entwurf, tippe, um diesen zu bearbeiten",
|
"accessibilityHint": "Gespeicherter Entwurf, tippe, um diesen zu bearbeiten",
|
||||||
"textEmpty": "Kein Inhalt"
|
"textEmpty": "Kein Inhalt"
|
||||||
|
|
|
@ -169,6 +169,9 @@
|
||||||
"follow": {
|
"follow": {
|
||||||
"heading": "Neue Follower"
|
"heading": "Neue Follower"
|
||||||
},
|
},
|
||||||
|
"follow_request": {
|
||||||
|
"heading": ""
|
||||||
|
},
|
||||||
"favourite": {
|
"favourite": {
|
||||||
"heading": "Favoriten"
|
"heading": "Favoriten"
|
||||||
},
|
},
|
||||||
|
@ -181,6 +184,9 @@
|
||||||
"poll": {
|
"poll": {
|
||||||
"heading": "Umfrageupdate"
|
"heading": "Umfrageupdate"
|
||||||
},
|
},
|
||||||
|
"status": {
|
||||||
|
"heading": ""
|
||||||
|
},
|
||||||
"howitworks": "Erfahre, wie das Routing funktioniert"
|
"howitworks": "Erfahre, wie das Routing funktioniert"
|
||||||
},
|
},
|
||||||
"root": {
|
"root": {
|
||||||
|
|
|
@ -168,6 +168,7 @@
|
||||||
"header": {
|
"header": {
|
||||||
"title": "Bozza"
|
"title": "Bozza"
|
||||||
},
|
},
|
||||||
|
"warning": "",
|
||||||
"content": {
|
"content": {
|
||||||
"accessibilityHint": "Bozza salvata, premi per modificarla",
|
"accessibilityHint": "Bozza salvata, premi per modificarla",
|
||||||
"textEmpty": "Testo vuoto"
|
"textEmpty": "Testo vuoto"
|
||||||
|
|
|
@ -169,6 +169,9 @@
|
||||||
"follow": {
|
"follow": {
|
||||||
"heading": "Nuovi seguaci"
|
"heading": "Nuovi seguaci"
|
||||||
},
|
},
|
||||||
|
"follow_request": {
|
||||||
|
"heading": ""
|
||||||
|
},
|
||||||
"favourite": {
|
"favourite": {
|
||||||
"heading": "Apprezzamenti"
|
"heading": "Apprezzamenti"
|
||||||
},
|
},
|
||||||
|
@ -181,6 +184,9 @@
|
||||||
"poll": {
|
"poll": {
|
||||||
"heading": "Novità sui sondaggi"
|
"heading": "Novità sui sondaggi"
|
||||||
},
|
},
|
||||||
|
"status": {
|
||||||
|
"heading": ""
|
||||||
|
},
|
||||||
"howitworks": "Scopri come funziona il traversamento dei messaggi"
|
"howitworks": "Scopri come funziona il traversamento dei messaggi"
|
||||||
},
|
},
|
||||||
"root": {
|
"root": {
|
||||||
|
|
|
@ -168,6 +168,7 @@
|
||||||
"header": {
|
"header": {
|
||||||
"title": "초안"
|
"title": "초안"
|
||||||
},
|
},
|
||||||
|
"warning": "",
|
||||||
"content": {
|
"content": {
|
||||||
"accessibilityHint": "저장된 초안, 수정하려면 탭하세요",
|
"accessibilityHint": "저장된 초안, 수정하려면 탭하세요",
|
||||||
"textEmpty": "콘텐츠 빔"
|
"textEmpty": "콘텐츠 빔"
|
||||||
|
|
|
@ -169,6 +169,9 @@
|
||||||
"follow": {
|
"follow": {
|
||||||
"heading": "새 팔로워"
|
"heading": "새 팔로워"
|
||||||
},
|
},
|
||||||
|
"follow_request": {
|
||||||
|
"heading": ""
|
||||||
|
},
|
||||||
"favourite": {
|
"favourite": {
|
||||||
"heading": "즐겨찾기됨"
|
"heading": "즐겨찾기됨"
|
||||||
},
|
},
|
||||||
|
@ -181,6 +184,9 @@
|
||||||
"poll": {
|
"poll": {
|
||||||
"heading": "투표 업데이트"
|
"heading": "투표 업데이트"
|
||||||
},
|
},
|
||||||
|
"status": {
|
||||||
|
"heading": ""
|
||||||
|
},
|
||||||
"howitworks": "라우팅 방법 알아보기"
|
"howitworks": "라우팅 방법 알아보기"
|
||||||
},
|
},
|
||||||
"root": {
|
"root": {
|
||||||
|
|
|
@ -168,6 +168,7 @@
|
||||||
"header": {
|
"header": {
|
||||||
"title": "Rascunho"
|
"title": "Rascunho"
|
||||||
},
|
},
|
||||||
|
"warning": "",
|
||||||
"content": {
|
"content": {
|
||||||
"accessibilityHint": "Toque para editar este rascunho",
|
"accessibilityHint": "Toque para editar este rascunho",
|
||||||
"textEmpty": "O conteúdo está vazio"
|
"textEmpty": "O conteúdo está vazio"
|
||||||
|
|
|
@ -169,6 +169,9 @@
|
||||||
"follow": {
|
"follow": {
|
||||||
"heading": "Novo seguidor"
|
"heading": "Novo seguidor"
|
||||||
},
|
},
|
||||||
|
"follow_request": {
|
||||||
|
"heading": ""
|
||||||
|
},
|
||||||
"favourite": {
|
"favourite": {
|
||||||
"heading": "Favoritos"
|
"heading": "Favoritos"
|
||||||
},
|
},
|
||||||
|
@ -181,6 +184,9 @@
|
||||||
"poll": {
|
"poll": {
|
||||||
"heading": "Pesquisa atualizada"
|
"heading": "Pesquisa atualizada"
|
||||||
},
|
},
|
||||||
|
"status": {
|
||||||
|
"heading": ""
|
||||||
|
},
|
||||||
"howitworks": "Saiba como funciona o roteamento"
|
"howitworks": "Saiba como funciona o roteamento"
|
||||||
},
|
},
|
||||||
"root": {
|
"root": {
|
||||||
|
|
|
@ -30,7 +30,7 @@
|
||||||
"default": "{{name}} đăng lại",
|
"default": "{{name}} đăng lại",
|
||||||
"notification": "{{name}} đăng lại tút của bạn"
|
"notification": "{{name}} đăng lại tút của bạn"
|
||||||
},
|
},
|
||||||
"update": ""
|
"update": "Đăng lại đã được sửa"
|
||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
"reply": {
|
"reply": {
|
||||||
|
|
|
@ -13,8 +13,8 @@
|
||||||
"reblog": "$t(screenTabs:me.push.reblog.heading)",
|
"reblog": "$t(screenTabs:me.push.reblog.heading)",
|
||||||
"mention": "$t(screenTabs:me.push.mention.heading)",
|
"mention": "$t(screenTabs:me.push.mention.heading)",
|
||||||
"poll": "$t(screenTabs:me.push.poll.heading)",
|
"poll": "$t(screenTabs:me.push.poll.heading)",
|
||||||
"status": "",
|
"status": "Tút từ người đã theo dõi",
|
||||||
"update": ""
|
"update": "Đăng lại đã được sửa"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -168,6 +168,7 @@
|
||||||
"header": {
|
"header": {
|
||||||
"title": "Nháp"
|
"title": "Nháp"
|
||||||
},
|
},
|
||||||
|
"warning": "Tút nháp chỉ được lưu trữ trên điện thoại và có thể bị mất nếu có sự cố. Hãy cẩn thận.",
|
||||||
"content": {
|
"content": {
|
||||||
"accessibilityHint": "Đã lưu nháp, nhấn để tiếp tục viết",
|
"accessibilityHint": "Đã lưu nháp, nhấn để tiếp tục viết",
|
||||||
"textEmpty": "Chưa có nội dung"
|
"textEmpty": "Chưa có nội dung"
|
||||||
|
|
|
@ -169,6 +169,9 @@
|
||||||
"follow": {
|
"follow": {
|
||||||
"heading": "Người theo dõi mới"
|
"heading": "Người theo dõi mới"
|
||||||
},
|
},
|
||||||
|
"follow_request": {
|
||||||
|
"heading": "Yêu cầu theo dõi"
|
||||||
|
},
|
||||||
"favourite": {
|
"favourite": {
|
||||||
"heading": "Lượt thích"
|
"heading": "Lượt thích"
|
||||||
},
|
},
|
||||||
|
@ -181,6 +184,9 @@
|
||||||
"poll": {
|
"poll": {
|
||||||
"heading": "Kết quả bình chọn"
|
"heading": "Kết quả bình chọn"
|
||||||
},
|
},
|
||||||
|
"status": {
|
||||||
|
"heading": "Tút từ người đã theo dõi"
|
||||||
|
},
|
||||||
"howitworks": "Tìm hiểu cách truyền"
|
"howitworks": "Tìm hiểu cách truyền"
|
||||||
},
|
},
|
||||||
"root": {
|
"root": {
|
||||||
|
|
|
@ -168,6 +168,7 @@
|
||||||
"header": {
|
"header": {
|
||||||
"title": "草稿"
|
"title": "草稿"
|
||||||
},
|
},
|
||||||
|
"warning": "草稿只存储在本地,在部分情况下可能丢失。建议不要长期存储草稿。",
|
||||||
"content": {
|
"content": {
|
||||||
"accessibilityHint": "已保存的草稿,点击编辑此草稿",
|
"accessibilityHint": "已保存的草稿,点击编辑此草稿",
|
||||||
"textEmpty": "无正文内容"
|
"textEmpty": "无正文内容"
|
||||||
|
|
|
@ -169,6 +169,9 @@
|
||||||
"follow": {
|
"follow": {
|
||||||
"heading": "新关注者"
|
"heading": "新关注者"
|
||||||
},
|
},
|
||||||
|
"follow_request": {
|
||||||
|
"heading": "关注请求"
|
||||||
|
},
|
||||||
"favourite": {
|
"favourite": {
|
||||||
"heading": "嘟文被喜欢"
|
"heading": "嘟文被喜欢"
|
||||||
},
|
},
|
||||||
|
@ -181,6 +184,9 @@
|
||||||
"poll": {
|
"poll": {
|
||||||
"heading": "投票更新"
|
"heading": "投票更新"
|
||||||
},
|
},
|
||||||
|
"status": {
|
||||||
|
"heading": "订阅用户的嘟文"
|
||||||
|
},
|
||||||
"howitworks": "了解通知消息转发如何工作"
|
"howitworks": "了解通知消息转发如何工作"
|
||||||
},
|
},
|
||||||
"root": {
|
"root": {
|
||||||
|
|
|
@ -168,6 +168,7 @@
|
||||||
"header": {
|
"header": {
|
||||||
"title": ""
|
"title": ""
|
||||||
},
|
},
|
||||||
|
"warning": "",
|
||||||
"content": {
|
"content": {
|
||||||
"accessibilityHint": "",
|
"accessibilityHint": "",
|
||||||
"textEmpty": ""
|
"textEmpty": ""
|
||||||
|
|
|
@ -169,6 +169,9 @@
|
||||||
"follow": {
|
"follow": {
|
||||||
"heading": ""
|
"heading": ""
|
||||||
},
|
},
|
||||||
|
"follow_request": {
|
||||||
|
"heading": ""
|
||||||
|
},
|
||||||
"favourite": {
|
"favourite": {
|
||||||
"heading": ""
|
"heading": ""
|
||||||
},
|
},
|
||||||
|
@ -181,6 +184,9 @@
|
||||||
"poll": {
|
"poll": {
|
||||||
"heading": ""
|
"heading": ""
|
||||||
},
|
},
|
||||||
|
"status": {
|
||||||
|
"heading": ""
|
||||||
|
},
|
||||||
"howitworks": ""
|
"howitworks": ""
|
||||||
},
|
},
|
||||||
"root": {
|
"root": {
|
||||||
|
|
|
@ -100,8 +100,8 @@ const ComposeEmojis: React.FC<Props> = ({ accessibleRefEmojis }) => {
|
||||||
)}
|
)}
|
||||||
source={{ uri }}
|
source={{ uri }}
|
||||||
style={{
|
style={{
|
||||||
width: 32,
|
width: 36,
|
||||||
height: 32,
|
height: 36,
|
||||||
padding: StyleConstants.Spacing.S,
|
padding: StyleConstants.Spacing.S,
|
||||||
margin: StyleConstants.Spacing.S
|
margin: StyleConstants.Spacing.S
|
||||||
}}
|
}}
|
||||||
|
@ -119,8 +119,8 @@ const ComposeEmojis: React.FC<Props> = ({ accessibleRefEmojis }) => {
|
||||||
)}
|
)}
|
||||||
source={{ uri }}
|
source={{ uri }}
|
||||||
style={{
|
style={{
|
||||||
width: 32,
|
width: 36,
|
||||||
height: 32,
|
height: 36,
|
||||||
padding: StyleConstants.Spacing.S,
|
padding: StyleConstants.Spacing.S,
|
||||||
margin: StyleConstants.Spacing.S
|
margin: StyleConstants.Spacing.S
|
||||||
}}
|
}}
|
||||||
|
@ -145,7 +145,7 @@ const ComposeEmojis: React.FC<Props> = ({ accessibleRefEmojis }) => {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
flexWrap: 'wrap',
|
flexWrap: 'wrap',
|
||||||
justifyContent: 'space-around',
|
justifyContent: 'space-around',
|
||||||
height: 260
|
height: 280
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SectionList
|
<SectionList
|
||||||
|
|
|
@ -19,94 +19,6 @@ import {
|
||||||
import ImageViewer from './ImageViewer/Root'
|
import ImageViewer from './ImageViewer/Root'
|
||||||
import saveImage from './ImageViewer/save'
|
import saveImage from './ImageViewer/save'
|
||||||
|
|
||||||
const HeaderComponent = React.memo(
|
|
||||||
({
|
|
||||||
messageRef,
|
|
||||||
navigation,
|
|
||||||
currentIndex,
|
|
||||||
imageUrls
|
|
||||||
}: {
|
|
||||||
messageRef: RefObject<FlashMessage>
|
|
||||||
navigation: NativeStackNavigationProp<
|
|
||||||
RootStackParamList,
|
|
||||||
'Screen-ImagesViewer'
|
|
||||||
>
|
|
||||||
currentIndex: number
|
|
||||||
imageUrls: RootStackParamList['Screen-ImagesViewer']['imageUrls']
|
|
||||||
}) => {
|
|
||||||
const insets = useSafeAreaInsets()
|
|
||||||
const { mode, theme } = useTheme()
|
|
||||||
const { t } = useTranslation('screenImageViewer')
|
|
||||||
const { showActionSheetWithOptions } = useActionSheet()
|
|
||||||
|
|
||||||
const onPress = useCallback(() => {
|
|
||||||
analytics('imageviewer_more_press')
|
|
||||||
showActionSheetWithOptions(
|
|
||||||
{
|
|
||||||
options: [
|
|
||||||
t('content.options.save'),
|
|
||||||
t('content.options.share'),
|
|
||||||
t('content.options.cancel')
|
|
||||||
],
|
|
||||||
cancelButtonIndex: 2,
|
|
||||||
userInterfaceStyle: mode
|
|
||||||
},
|
|
||||||
async buttonIndex => {
|
|
||||||
switch (buttonIndex) {
|
|
||||||
case 0:
|
|
||||||
analytics('imageviewer_more_save_press')
|
|
||||||
saveImage({ messageRef, theme, image: imageUrls[currentIndex] })
|
|
||||||
break
|
|
||||||
case 1:
|
|
||||||
analytics('imageviewer_more_share_press')
|
|
||||||
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])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
flex: 1,
|
|
||||||
flexDirection: 'row',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
alignItems: 'center',
|
|
||||||
marginTop: insets.top
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<HeaderLeft
|
|
||||||
content='X'
|
|
||||||
native={false}
|
|
||||||
background
|
|
||||||
onPress={() => navigation.goBack()}
|
|
||||||
/>
|
|
||||||
<HeaderCenter
|
|
||||||
inverted
|
|
||||||
content={`${currentIndex + 1} / ${imageUrls.length}`}
|
|
||||||
/>
|
|
||||||
<HeaderRight
|
|
||||||
accessibilityLabel={t('content.actions.accessibilityLabel')}
|
|
||||||
accessibilityHint={t('content.actions.accessibilityHint')}
|
|
||||||
content='MoreHorizontal'
|
|
||||||
native={false}
|
|
||||||
background
|
|
||||||
onPress={onPress}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
(prev, next) => prev.currentIndex === next.currentIndex
|
|
||||||
)
|
|
||||||
|
|
||||||
const ScreenImagesViewer = ({
|
const ScreenImagesViewer = ({
|
||||||
route: {
|
route: {
|
||||||
params: { imageUrls, id }
|
params: { imageUrls, id }
|
||||||
|
@ -118,13 +30,51 @@ const ScreenImagesViewer = ({
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const { theme } = useTheme()
|
const insets = useSafeAreaInsets()
|
||||||
|
|
||||||
|
const { mode, theme } = useTheme()
|
||||||
|
const { t } = useTranslation('screenImageViewer')
|
||||||
|
|
||||||
const initialIndex = imageUrls.findIndex(image => image.id === id)
|
const initialIndex = imageUrls.findIndex(image => image.id === id)
|
||||||
const [currentIndex, setCurrentIndex] = useState(initialIndex)
|
const [currentIndex, setCurrentIndex] = useState(initialIndex)
|
||||||
|
|
||||||
const messageRef = useRef<FlashMessage>(null)
|
const messageRef = useRef<FlashMessage>(null)
|
||||||
|
|
||||||
|
const { showActionSheetWithOptions } = useActionSheet()
|
||||||
|
const onPress = useCallback(() => {
|
||||||
|
analytics('imageviewer_more_press')
|
||||||
|
showActionSheetWithOptions(
|
||||||
|
{
|
||||||
|
options: [
|
||||||
|
t('content.options.save'),
|
||||||
|
t('content.options.share'),
|
||||||
|
t('content.options.cancel')
|
||||||
|
],
|
||||||
|
cancelButtonIndex: 2,
|
||||||
|
userInterfaceStyle: mode
|
||||||
|
},
|
||||||
|
async buttonIndex => {
|
||||||
|
switch (buttonIndex) {
|
||||||
|
case 0:
|
||||||
|
analytics('imageviewer_more_save_press')
|
||||||
|
saveImage({ messageRef, theme, image: imageUrls[currentIndex] })
|
||||||
|
break
|
||||||
|
case 1:
|
||||||
|
analytics('imageviewer_more_share_press')
|
||||||
|
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])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeAreaProvider>
|
<SafeAreaProvider>
|
||||||
<StatusBar hidden />
|
<StatusBar hidden />
|
||||||
|
@ -133,14 +83,74 @@ const ScreenImagesViewer = ({
|
||||||
imageIndex={initialIndex}
|
imageIndex={initialIndex}
|
||||||
onImageIndexChange={index => setCurrentIndex(index)}
|
onImageIndexChange={index => setCurrentIndex(index)}
|
||||||
onRequestClose={() => navigation.goBack()}
|
onRequestClose={() => navigation.goBack()}
|
||||||
onLongPress={image => saveImage({ messageRef, theme, image })}
|
onLongPress={() => {
|
||||||
|
analytics('imageviewer_more_press')
|
||||||
|
showActionSheetWithOptions(
|
||||||
|
{
|
||||||
|
options: [
|
||||||
|
t('content.options.save'),
|
||||||
|
t('content.options.share'),
|
||||||
|
t('content.options.cancel')
|
||||||
|
],
|
||||||
|
cancelButtonIndex: 2,
|
||||||
|
userInterfaceStyle: mode
|
||||||
|
},
|
||||||
|
async buttonIndex => {
|
||||||
|
switch (buttonIndex) {
|
||||||
|
case 0:
|
||||||
|
analytics('imageviewer_more_save_press')
|
||||||
|
saveImage({
|
||||||
|
messageRef,
|
||||||
|
theme,
|
||||||
|
image: imageUrls[currentIndex]
|
||||||
|
})
|
||||||
|
break
|
||||||
|
case 1:
|
||||||
|
analytics('imageviewer_more_share_press')
|
||||||
|
switch (Platform.OS) {
|
||||||
|
case 'ios':
|
||||||
|
await Share.share({ url: imageUrls[currentIndex].url })
|
||||||
|
break
|
||||||
|
case 'android':
|
||||||
|
await Share.share({
|
||||||
|
message: imageUrls[currentIndex].url
|
||||||
|
})
|
||||||
|
break
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}}
|
||||||
HeaderComponent={() => (
|
HeaderComponent={() => (
|
||||||
<HeaderComponent
|
<View
|
||||||
messageRef={messageRef}
|
style={{
|
||||||
navigation={navigation}
|
flex: 1,
|
||||||
currentIndex={currentIndex}
|
flexDirection: 'row',
|
||||||
imageUrls={imageUrls}
|
justifyContent: 'space-between',
|
||||||
/>
|
alignItems: 'center',
|
||||||
|
marginTop: insets.top
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<HeaderLeft
|
||||||
|
content='X'
|
||||||
|
native={false}
|
||||||
|
background
|
||||||
|
onPress={() => navigation.goBack()}
|
||||||
|
/>
|
||||||
|
<HeaderCenter
|
||||||
|
inverted
|
||||||
|
content={`${currentIndex + 1} / ${imageUrls.length}`}
|
||||||
|
/>
|
||||||
|
<HeaderRight
|
||||||
|
accessibilityLabel={t('content.actions.accessibilityLabel')}
|
||||||
|
accessibilityHint={t('content.actions.accessibilityHint')}
|
||||||
|
content='MoreHorizontal'
|
||||||
|
native={false}
|
||||||
|
background
|
||||||
|
onPress={onPress}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<Message ref={messageRef} />
|
<Message ref={messageRef} />
|
||||||
|
|
Loading…
Reference in New Issue