mirror of
https://github.com/tooot-app/app
synced 2024-12-26 17:32:26 +01:00
More adaption to Android
This commit is contained in:
parent
49715bba0d
commit
95f500ae72
6
App.tsx
6
App.tsx
@ -15,10 +15,6 @@ import { QueryClient, QueryClientProvider } from 'react-query'
|
||||
import { Provider } from 'react-redux'
|
||||
import { PersistGate } from 'redux-persist/integration/react'
|
||||
import { LogBox, Platform } from 'react-native'
|
||||
import Moment from 'react-moment'
|
||||
|
||||
import moment from 'moment'
|
||||
import 'moment/min/locales'
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
LogBox.ignoreLogs(['Setting a timer for a long period of time'])
|
||||
@ -28,8 +24,6 @@ dev()
|
||||
sentry()
|
||||
audio()
|
||||
onlineStatus()
|
||||
Moment.globalMoment = moment
|
||||
Moment.startPooledTimer(1000)
|
||||
|
||||
log('log', 'react-query', 'initializing')
|
||||
const queryClient = new QueryClient()
|
||||
|
@ -44,12 +44,10 @@
|
||||
"gl-react-expo": "^4.0.1",
|
||||
"i18next": "^19.8.4",
|
||||
"lodash": "^4.17.20",
|
||||
"moment": "^2.29.1",
|
||||
"pretty-bytes": "^5.5.0",
|
||||
"react": "16.13.1",
|
||||
"react-dom": "16.13.1",
|
||||
"react-i18next": "^11.8.5",
|
||||
"react-moment": "^1.1.1",
|
||||
"react-native": "https://github.com/expo/react-native/archive/sdk-40.0.0.tar.gz",
|
||||
"react-native-animated-spinkit": "^1.4.2",
|
||||
"react-native-expo-image-cache": "^4.1.0",
|
||||
@ -67,6 +65,7 @@
|
||||
"react-navigation": "^4.4.3",
|
||||
"react-query": "^3.5.6",
|
||||
"react-redux": "^7.2.2",
|
||||
"react-timeago": "^5.2.0",
|
||||
"redux-persist": "^6.0.0",
|
||||
"redux-persist-expo-securestore": "^2.0.0",
|
||||
"sentry-expo": "^3.0.4",
|
||||
@ -93,6 +92,7 @@
|
||||
"@types/react-navigation": "^3.4.0",
|
||||
"@types/react-redux": "^7.1.12",
|
||||
"@types/react-test-renderer": "^17.0.0",
|
||||
"@types/react-timeago": "^4.1.2",
|
||||
"@welldone-software/why-did-you-render": "^6.0.4",
|
||||
"babel-plugin-module-resolver": "^4.1.0",
|
||||
"chalk": "^4.1.0",
|
||||
|
@ -7,6 +7,7 @@ import {
|
||||
createBottomTabNavigator
|
||||
} from '@react-navigation/bottom-tabs'
|
||||
import {
|
||||
getFocusedRouteNameFromRoute,
|
||||
NavigationContainer,
|
||||
NavigationContainerRef
|
||||
} from '@react-navigation/native'
|
||||
@ -31,7 +32,7 @@ import React, {
|
||||
useMemo,
|
||||
useRef
|
||||
} from 'react'
|
||||
import { StatusBar } from 'react-native'
|
||||
import { Platform, StatusBar } from 'react-native'
|
||||
import Toast from 'react-native-toast-message'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
|
||||
@ -172,6 +173,7 @@ const Index: React.FC<Props> = ({ localCorrupt }) => {
|
||||
}) => {
|
||||
let name: any
|
||||
let updateColor: string = color
|
||||
console.log()
|
||||
switch (route.name) {
|
||||
case 'Screen-Local':
|
||||
name = 'Home'
|
||||
@ -195,7 +197,16 @@ const Index: React.FC<Props> = ({ localCorrupt }) => {
|
||||
break
|
||||
}
|
||||
return <Icon name={name} size={size} color={updateColor} />
|
||||
}
|
||||
},
|
||||
...(Platform.OS === 'android' && {
|
||||
tabBarVisible:
|
||||
getFocusedRouteNameFromRoute(route) !== 'Screen-Shared-Compose' &&
|
||||
getFocusedRouteNameFromRoute(route) !==
|
||||
'Screen-Shared-Announcements' &&
|
||||
getFocusedRouteNameFromRoute(route) !==
|
||||
'Screen-Shared-ImagesViewer' &&
|
||||
getFocusedRouteNameFromRoute(route) !== 'Screen-Me-Switch'
|
||||
})
|
||||
}),
|
||||
[]
|
||||
)
|
||||
@ -204,7 +215,8 @@ const Index: React.FC<Props> = ({ localCorrupt }) => {
|
||||
activeTintColor: theme.primary,
|
||||
inactiveTintColor:
|
||||
localActiveIndex !== null ? theme.secondary : theme.disabled,
|
||||
showLabel: false
|
||||
showLabel: false,
|
||||
...(Platform.OS === 'android' && { keyboardHidesTabBar: true })
|
||||
}),
|
||||
[theme, localActiveIndex]
|
||||
)
|
||||
@ -292,7 +304,9 @@ const Index: React.FC<Props> = ({ localCorrupt }) => {
|
||||
<Tab.Screen name='Screen-Me' component={ScreenMe} />
|
||||
</Tab.Navigator>
|
||||
|
||||
{/* <Toast ref={Toast.setRef} config={toastConfig} /> */}
|
||||
{Platform.OS === 'ios' ? (
|
||||
<Toast ref={Toast.setRef} config={toastConfig} />
|
||||
) : null}
|
||||
</NavigationContainer>
|
||||
</>
|
||||
)
|
||||
|
@ -1,4 +1,5 @@
|
||||
import HeaderLeft from '@components/Header/Left'
|
||||
import HeaderCenter from '@components/Header/Center'
|
||||
import HeaderRight from '@components/Header/Right'
|
||||
|
||||
export { HeaderLeft, HeaderRight }
|
||||
export { HeaderLeft, HeaderCenter, HeaderRight }
|
||||
|
31
src/components/Header/Center.tsx
Normal file
31
src/components/Header/Center.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React from 'react'
|
||||
import { StyleSheet, Text } from 'react-native'
|
||||
|
||||
export interface Props {
|
||||
content: string
|
||||
}
|
||||
|
||||
// Used for Android mostly
|
||||
const HeaderCenter = React.memo(
|
||||
({ content }: Props) => {
|
||||
const { theme } = useTheme()
|
||||
|
||||
return (
|
||||
<Text
|
||||
style={[styles.text, { color: theme.primary }]}
|
||||
children={content}
|
||||
/>
|
||||
)
|
||||
},
|
||||
() => true
|
||||
)
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
text: {
|
||||
...StyleConstants.FontStyle.M
|
||||
}
|
||||
})
|
||||
|
||||
export default HeaderCenter
|
@ -45,9 +45,12 @@ const renderNode = ({
|
||||
? routeParams.hashtag !== tag[1] && routeParams.hashtag !== tag[2]
|
||||
: true
|
||||
return (
|
||||
<Pressable
|
||||
<Text
|
||||
key={index}
|
||||
hitSlop={StyleConstants.Font.Size[size] / 2}
|
||||
style={{
|
||||
color: theme.blue,
|
||||
...StyleConstants.FontStyle[size]
|
||||
}}
|
||||
onPress={() => {
|
||||
!disableDetails &&
|
||||
differentTag &&
|
||||
@ -56,16 +59,9 @@ const renderNode = ({
|
||||
})
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.blue,
|
||||
...StyleConstants.FontStyle[size]
|
||||
}}
|
||||
>
|
||||
{node.children[0].data}
|
||||
{node.children[1]?.children[0].data}
|
||||
</Text>
|
||||
</Pressable>
|
||||
{node.children[0].data}
|
||||
{node.children[1]?.children[0].data}
|
||||
</Text>
|
||||
)
|
||||
} else if (classes.includes('mention') && mentions) {
|
||||
const accountIndex = mentions.findIndex(
|
||||
@ -75,9 +71,12 @@ const renderNode = ({
|
||||
? routeParams.account.id !== mentions[accountIndex].id
|
||||
: true
|
||||
return (
|
||||
<Pressable
|
||||
<Text
|
||||
key={index}
|
||||
hitSlop={StyleConstants.Font.Size[size] / 2}
|
||||
style={{
|
||||
color: accountIndex !== -1 ? theme.blue : undefined,
|
||||
...StyleConstants.FontStyle[size]
|
||||
}}
|
||||
onPress={() => {
|
||||
accountIndex !== -1 &&
|
||||
!disableDetails &&
|
||||
@ -87,16 +86,9 @@ const renderNode = ({
|
||||
})
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
color: accountIndex !== -1 ? theme.blue : undefined,
|
||||
...StyleConstants.FontStyle[size]
|
||||
}}
|
||||
>
|
||||
{node.children[0].data}
|
||||
{node.children[1]?.children[0].data}
|
||||
</Text>
|
||||
</Pressable>
|
||||
{node.children[0].data}
|
||||
{node.children[1]?.children[0].data}
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
} else {
|
||||
@ -107,9 +99,12 @@ const renderNode = ({
|
||||
const shouldBeTag =
|
||||
tags && tags.filter(tag => `#${tag.name}` === content).length > 0
|
||||
return (
|
||||
<Pressable
|
||||
<Text
|
||||
key={index}
|
||||
hitSlop={StyleConstants.Font.Size[size] / 2}
|
||||
style={{
|
||||
color: theme.blue,
|
||||
...StyleConstants.FontStyle[size]
|
||||
}}
|
||||
onPress={async () =>
|
||||
!disableDetails && !shouldBeTag
|
||||
? await openLink(href)
|
||||
@ -118,22 +113,15 @@ const renderNode = ({
|
||||
})
|
||||
}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.blue,
|
||||
...StyleConstants.FontStyle[size]
|
||||
}}
|
||||
>
|
||||
{!shouldBeTag ? (
|
||||
<Icon
|
||||
color={theme.blue}
|
||||
name='ExternalLink'
|
||||
size={StyleConstants.Font.Size[size]}
|
||||
/>
|
||||
) : null}
|
||||
{content || (showFullLink ? href : domain[1])}
|
||||
</Text>
|
||||
</Pressable>
|
||||
{!shouldBeTag ? (
|
||||
<Icon
|
||||
color={theme.blue}
|
||||
name='ExternalLink'
|
||||
size={StyleConstants.Font.Size[size]}
|
||||
/>
|
||||
) : null}
|
||||
{content || (showFullLink ? href : domain[1])}
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
break
|
||||
|
26
src/components/RelativeTime.tsx
Normal file
26
src/components/RelativeTime.tsx
Normal file
@ -0,0 +1,26 @@
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Text } from 'react-native'
|
||||
import TimeAgo from 'react-timeago'
|
||||
// @ts-ignore
|
||||
import buildFormatter from 'react-timeago/lib/formatters/buildFormatter'
|
||||
|
||||
import zh from '@root/i18n/zh/components/relativeTime'
|
||||
import en from '@root/i18n/en/components/relativeTime'
|
||||
|
||||
export interface Props {
|
||||
date: string
|
||||
}
|
||||
|
||||
const RelativeTime: React.FC<Props> = ({ date }) => {
|
||||
const { i18n } = useTranslation()
|
||||
const mapLanguageToTranslation: { [key: string]: Object } = {
|
||||
'zh-CN': zh,
|
||||
'en-US': en
|
||||
}
|
||||
const formatter = buildFormatter(mapLanguageToTranslation[i18n.language])
|
||||
|
||||
return <TimeAgo date={date} formatter={formatter} component={Text} />
|
||||
}
|
||||
|
||||
export default RelativeTime
|
@ -1,12 +1,12 @@
|
||||
import { HeaderRight } from '@components/Header'
|
||||
import { HeaderCenter, HeaderRight } from '@components/Header'
|
||||
import Timeline from '@components/Timelines/Timeline'
|
||||
import SegmentedControl from '@react-native-community/segmented-control'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import sharedScreens from '@screens/Shared/sharedScreens'
|
||||
import { getLocalActiveIndex, getRemoteUrl } from '@utils/slices/instancesSlice'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import { Dimensions, StyleSheet, View } from 'react-native'
|
||||
import React, { useCallback, useMemo, useState } from 'react'
|
||||
import { Dimensions, Platform, StyleSheet, View } from 'react-native'
|
||||
import { createNativeStackNavigator } from 'react-native-screens/native-stack'
|
||||
import { TabView } from 'react-native-tab-view'
|
||||
import { useSelector } from 'react-redux'
|
||||
@ -68,6 +68,40 @@ const Timelines: React.FC<Props> = ({ name, content }) => {
|
||||
[segment, localActiveIndex]
|
||||
)
|
||||
|
||||
const screenOptions = useMemo(() => {
|
||||
if (localActiveIndex === null) {
|
||||
if (name === 'Public') {
|
||||
return {
|
||||
headerTitle: publicDomain,
|
||||
...(Platform.OS === 'android' && {
|
||||
headerCenter: () => <HeaderCenter content={publicDomain} />
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
headerCenter: () => (
|
||||
<View style={styles.segmentsContainer}>
|
||||
<SegmentedControl
|
||||
appearance={mode}
|
||||
values={[
|
||||
content[0].title,
|
||||
content[1].remote ? remoteUrl : content[1].title
|
||||
]}
|
||||
selectedIndex={segment}
|
||||
onChange={({ nativeEvent }) =>
|
||||
setSegment(nativeEvent.selectedSegmentIndex)
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
),
|
||||
headerRight: () => (
|
||||
<HeaderRight content='Search' onPress={onPressSearch} />
|
||||
)
|
||||
}
|
||||
}
|
||||
}, [localActiveIndex, mode, segment])
|
||||
|
||||
return (
|
||||
<Stack.Navigator
|
||||
screenOptions={{ headerHideShadow: true, headerTopInsetEnabled: false }}
|
||||
@ -76,29 +110,7 @@ const Timelines: React.FC<Props> = ({ name, content }) => {
|
||||
// @ts-ignore
|
||||
name={`Screen-${name}-Root`}
|
||||
component={screenComponent}
|
||||
options={{
|
||||
headerTitle: name === 'Public' ? publicDomain : '',
|
||||
...(localActiveIndex !== null && {
|
||||
headerCenter: () => (
|
||||
<View style={styles.segmentsContainer}>
|
||||
<SegmentedControl
|
||||
appearance={mode}
|
||||
values={[
|
||||
content[0].title,
|
||||
content[1].remote ? remoteUrl : content[1].title
|
||||
]}
|
||||
selectedIndex={segment}
|
||||
onChange={({ nativeEvent }) =>
|
||||
setSegment(nativeEvent.selectedSegmentIndex)
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
),
|
||||
headerRight: () => (
|
||||
<HeaderRight content='Search' onPress={onPressSearch} />
|
||||
)
|
||||
})
|
||||
}}
|
||||
options={screenOptions}
|
||||
/>
|
||||
|
||||
{sharedScreens(Stack)}
|
||||
|
@ -9,13 +9,14 @@ import { useScrollToTop } from '@react-navigation/native'
|
||||
import { localUpdateNotification } from '@utils/slices/instancesSlice'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import React, { useCallback, useEffect, useMemo, useRef } from 'react'
|
||||
import { RefreshControl, StyleSheet } from 'react-native'
|
||||
import { Platform, RefreshControl, StyleSheet } from 'react-native'
|
||||
import { FlatList } from 'react-native-gesture-handler'
|
||||
import { useDispatch } from 'react-redux'
|
||||
import { QueryKeyTimeline, useTimelineQuery } from '@utils/queryHooks/timeline'
|
||||
import { findIndex } from 'lodash'
|
||||
import CustomRefreshControl from '@components/CustomRefreshControl'
|
||||
import { InfiniteData, useQueryClient } from 'react-query'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
|
||||
export interface Props {
|
||||
page: App.Pages
|
||||
@ -36,6 +37,8 @@ const Timeline: React.FC<Props> = ({
|
||||
disableRefresh = false,
|
||||
disableInfinity = false
|
||||
}) => {
|
||||
const { theme } = useTheme()
|
||||
|
||||
const queryKeyParams = {
|
||||
page,
|
||||
...(hashtag && { hashtag }),
|
||||
@ -163,8 +166,13 @@ const Timeline: React.FC<Props> = ({
|
||||
const refreshControl = useMemo(
|
||||
() => (
|
||||
<RefreshControl
|
||||
{...(Platform.OS === 'android' && { enabled: true })}
|
||||
refreshing={
|
||||
refreshCount.current < 2 ? isFetchingPreviousPage : isFetching
|
||||
refreshCount.current < 2
|
||||
? Platform.OS === 'ios'
|
||||
? isFetchingPreviousPage
|
||||
: isFetchingPreviousPage || isFetching
|
||||
: isFetching
|
||||
}
|
||||
onRefresh={async () => {
|
||||
if (refreshCount.current < 2) {
|
||||
|
@ -1,8 +1,8 @@
|
||||
import RelativeTime from '@components/RelativeTime'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Moment from 'react-moment'
|
||||
import { StyleSheet, Text } from 'react-native'
|
||||
|
||||
export interface Props {
|
||||
@ -15,7 +15,7 @@ const HeaderSharedCreated: React.FC<Props> = ({ created_at }) => {
|
||||
|
||||
return (
|
||||
<Text style={[styles.created_at, { color: theme.secondary }]}>
|
||||
<Moment date={created_at} locale={i18n.language} element={Text} fromNow />
|
||||
<RelativeTime date={created_at} />
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import Button from '@components/Button'
|
||||
import haptics from '@components/haptics'
|
||||
import Icon from '@components/Icon'
|
||||
import relativeTime from '@components/relativeTime'
|
||||
import RelativeTime from '@components/RelativeTime'
|
||||
import { ParseEmojis } from '@root/components/Parse'
|
||||
import { toast } from '@root/components/toast'
|
||||
import {
|
||||
@ -13,7 +13,6 @@ import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import { maxBy } from 'lodash'
|
||||
import React, { useCallback, useMemo, useState } from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import Moment from 'react-moment'
|
||||
import { Pressable, StyleSheet, Text, View } from 'react-native'
|
||||
import { useQueryClient } from 'react-query'
|
||||
|
||||
@ -127,14 +126,7 @@ const TimelinePoll: React.FC<Props> = ({
|
||||
<Text style={[styles.expiration, { color: theme.secondary }]}>
|
||||
<Trans
|
||||
i18nKey='timeline:shared.poll.meta.expiration.until'
|
||||
components={[
|
||||
<Moment
|
||||
date={poll.expires_at}
|
||||
locale={i18n.language}
|
||||
element={Text}
|
||||
fromNow
|
||||
/>
|
||||
]}
|
||||
components={[<RelativeTime date={poll.expires_at} />]}
|
||||
/>
|
||||
</Text>
|
||||
)
|
||||
|
@ -1,9 +1,17 @@
|
||||
import * as Haptics from 'expo-haptics'
|
||||
import { Platform } from 'react-native'
|
||||
import * as Sentry from 'sentry-expo'
|
||||
|
||||
const haptics = (
|
||||
type: 'Success' | 'Warning' | 'Error' | 'Light' | 'Medium' | 'Heavy'
|
||||
) => {
|
||||
if (Platform.OS === 'android') {
|
||||
Haptics.impactAsync(Haptics.ImpactFeedbackStyle['Light']).catch(error =>
|
||||
Sentry.Native.captureException(error)
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case 'Success':
|
||||
case 'Warning':
|
||||
|
@ -2,7 +2,7 @@ import Icon from '@components/Icon'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React from 'react'
|
||||
import { StyleSheet, Text, View } from 'react-native'
|
||||
import { Platform, StyleSheet, Text, ToastAndroid, View } from 'react-native'
|
||||
import { SafeAreaView } from 'react-native-safe-area-context'
|
||||
import Toast from 'react-native-toast-message'
|
||||
import * as Sentry from 'sentry-expo'
|
||||
@ -33,18 +33,23 @@ const toast = ({
|
||||
onShow,
|
||||
onHide
|
||||
}: Params) => {
|
||||
Toast.show({
|
||||
type,
|
||||
position,
|
||||
text1: message,
|
||||
text2: description,
|
||||
visibilityTime: 1500,
|
||||
autoHide,
|
||||
topOffset: 0,
|
||||
bottomOffset: 0,
|
||||
onShow: onShow,
|
||||
onHide: onHide
|
||||
})
|
||||
switch (Platform.OS) {
|
||||
case 'ios':
|
||||
return Toast.show({
|
||||
type,
|
||||
position,
|
||||
text1: message,
|
||||
text2: description,
|
||||
visibilityTime: 1500,
|
||||
autoHide,
|
||||
topOffset: 0,
|
||||
bottomOffset: 0,
|
||||
onShow: onShow,
|
||||
onHide: onHide
|
||||
})
|
||||
case 'android':
|
||||
return ToastAndroid.show(message, ToastAndroid.SHORT)
|
||||
}
|
||||
}
|
||||
|
||||
const ToastBase = ({ config }: { config: Config }) => {
|
||||
|
20
src/i18n/en/components/relativeTime.ts
Normal file
20
src/i18n/en/components/relativeTime.ts
Normal file
@ -0,0 +1,20 @@
|
||||
const strings = {
|
||||
prefixAgo: null,
|
||||
prefixFromNow: null,
|
||||
suffixAgo: 'ago',
|
||||
suffixFromNow: 'from now',
|
||||
seconds: '%d seconds',
|
||||
minute: 'about a minute',
|
||||
minutes: '%d minutes',
|
||||
hour: 'about an hour',
|
||||
hours: 'about %d hours',
|
||||
day: 'a day',
|
||||
days: '%d days',
|
||||
month: 'about a month',
|
||||
months: '%d months',
|
||||
year: 'about a year',
|
||||
years: '%d years',
|
||||
wordSeparator: ' '
|
||||
}
|
||||
|
||||
export default strings
|
21
src/i18n/zh/components/relativeTime.ts
Normal file
21
src/i18n/zh/components/relativeTime.ts
Normal file
@ -0,0 +1,21 @@
|
||||
const strings = {
|
||||
prefixAgo: null,
|
||||
prefixFromNow: null,
|
||||
suffixAgo: '之前',
|
||||
suffixFromNow: '之后',
|
||||
seconds: '%d秒',
|
||||
minute: '大约1分钟',
|
||||
minutes: '%d分钟',
|
||||
hour: '大约1小时',
|
||||
hours: '大约%d小时',
|
||||
day: '1天',
|
||||
days: '%d天',
|
||||
month: '大约1个月',
|
||||
months: '%d月',
|
||||
year: '大约1年',
|
||||
years: '%d年',
|
||||
|
||||
wordSeparator: ''
|
||||
}
|
||||
|
||||
export default strings
|
@ -1,4 +1,4 @@
|
||||
import { HeaderLeft } from '@components/Header'
|
||||
import { HeaderCenter, HeaderLeft } from '@components/Header'
|
||||
import ScreenMeBookmarks from '@screens/Me/Bookmarks'
|
||||
import ScreenMeConversations from '@screens/Me/Cconversations'
|
||||
import ScreenMeFavourites from '@screens/Me/Favourites'
|
||||
@ -11,6 +11,7 @@ import UpdateRemote from '@screens/Me/UpdateRemote'
|
||||
import sharedScreens from '@screens/Shared/sharedScreens'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Platform } from 'react-native'
|
||||
import { createNativeStackNavigator } from 'react-native-screens/native-stack'
|
||||
|
||||
const Stack = createNativeStackNavigator<Nav.MeStackParamList>()
|
||||
@ -19,7 +20,9 @@ const ScreenMe: React.FC = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<Stack.Navigator screenOptions={{ headerHideShadow: true, headerTopInsetEnabled: false }}>
|
||||
<Stack.Navigator
|
||||
screenOptions={{ headerHideShadow: true, headerTopInsetEnabled: false }}
|
||||
>
|
||||
<Stack.Screen
|
||||
name='Screen-Me-Root'
|
||||
component={ScreenMeRoot}
|
||||
@ -34,6 +37,11 @@ const ScreenMe: React.FC = () => {
|
||||
component={ScreenMeBookmarks}
|
||||
options={({ navigation }: any) => ({
|
||||
headerTitle: t('meBookmarks:heading'),
|
||||
...(Platform.OS === 'android' && {
|
||||
headerCenter: () => (
|
||||
<HeaderCenter content={t('meBookmarks:heading')} />
|
||||
)
|
||||
}),
|
||||
headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} />
|
||||
})}
|
||||
/>
|
||||
@ -42,6 +50,11 @@ const ScreenMe: React.FC = () => {
|
||||
component={ScreenMeConversations}
|
||||
options={({ navigation }: any) => ({
|
||||
headerTitle: t('meConversations:heading'),
|
||||
...(Platform.OS === 'android' && {
|
||||
headerCenter: () => (
|
||||
<HeaderCenter content={t('meConversations:heading')} />
|
||||
)
|
||||
}),
|
||||
headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} />
|
||||
})}
|
||||
/>
|
||||
@ -50,6 +63,11 @@ const ScreenMe: React.FC = () => {
|
||||
component={ScreenMeFavourites}
|
||||
options={({ navigation }: any) => ({
|
||||
headerTitle: t('meFavourites:heading'),
|
||||
...(Platform.OS === 'android' && {
|
||||
headerCenter: () => (
|
||||
<HeaderCenter content={t('meFavourites:heading')} />
|
||||
)
|
||||
}),
|
||||
headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} />
|
||||
})}
|
||||
/>
|
||||
@ -58,6 +76,9 @@ const ScreenMe: React.FC = () => {
|
||||
component={ScreenMeLists}
|
||||
options={({ navigation }: any) => ({
|
||||
headerTitle: t('meLists:heading'),
|
||||
...(Platform.OS === 'android' && {
|
||||
headerCenter: () => <HeaderCenter content={t('meLists:heading')} />
|
||||
}),
|
||||
headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} />
|
||||
})}
|
||||
/>
|
||||
@ -66,6 +87,11 @@ const ScreenMe: React.FC = () => {
|
||||
component={ScreenMeListsList}
|
||||
options={({ route, navigation }: any) => ({
|
||||
headerTitle: t('meListsList:heading', { list: route.params.title }),
|
||||
...(Platform.OS === 'android' && {
|
||||
headerCenter: () => (
|
||||
<HeaderCenter content={t('meListsList:heading')} />
|
||||
)
|
||||
}),
|
||||
headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} />
|
||||
})}
|
||||
/>
|
||||
@ -74,6 +100,11 @@ const ScreenMe: React.FC = () => {
|
||||
component={ScreenMeSettings}
|
||||
options={({ navigation }: any) => ({
|
||||
headerTitle: t('meSettings:heading'),
|
||||
...(Platform.OS === 'android' && {
|
||||
headerCenter: () => (
|
||||
<HeaderCenter content={t('meSettings:heading')} />
|
||||
)
|
||||
}),
|
||||
headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} />
|
||||
})}
|
||||
/>
|
||||
@ -82,6 +113,11 @@ const ScreenMe: React.FC = () => {
|
||||
component={UpdateRemote}
|
||||
options={({ navigation }: any) => ({
|
||||
headerTitle: t('meSettingsUpdateRemote:heading'),
|
||||
...(Platform.OS === 'android' && {
|
||||
headerCenter: () => (
|
||||
<HeaderCenter content={t('meSettingsUpdateRemote:heading')} />
|
||||
)
|
||||
}),
|
||||
headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} />
|
||||
})}
|
||||
/>
|
||||
@ -90,7 +126,13 @@ const ScreenMe: React.FC = () => {
|
||||
component={ScreenMeSwitch}
|
||||
options={({ navigation }: any) => ({
|
||||
stackPresentation: 'fullScreenModal',
|
||||
headerShown: false,
|
||||
headerTitle: t('meSettings:heading'),
|
||||
...(Platform.OS === 'android' && {
|
||||
headerCenter: () => (
|
||||
<HeaderCenter content={t('meSettings:heading')} />
|
||||
)
|
||||
}),
|
||||
headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} />
|
||||
})}
|
||||
/>
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { HeaderLeft, HeaderRight } from '@components/Header'
|
||||
import { HeaderCenter, HeaderLeft } from '@components/Header'
|
||||
import React from 'react'
|
||||
import { StyleSheet } from 'react-native'
|
||||
import { Platform, StyleSheet } from 'react-native'
|
||||
import { createNativeStackNavigator } from 'react-native-screens/native-stack'
|
||||
import ScreenMeSwitchRoot from './Switch/Root'
|
||||
|
||||
@ -8,12 +8,17 @@ const Stack = createNativeStackNavigator()
|
||||
|
||||
const ScreenMeSwitch: React.FC = ({ navigation }) => {
|
||||
return (
|
||||
<Stack.Navigator screenOptions={{ headerHideShadow: true, headerTopInsetEnabled: false }}>
|
||||
<Stack.Navigator
|
||||
screenOptions={{ headerHideShadow: true, headerTopInsetEnabled: false }}
|
||||
>
|
||||
<Stack.Screen
|
||||
name='Screen-Me-Switch-Root'
|
||||
component={ScreenMeSwitchRoot}
|
||||
options={{
|
||||
headerTitle: '切换账号',
|
||||
...(Platform.OS === 'android' && {
|
||||
headerCenter: () => <HeaderCenter content='切换账号' />
|
||||
}),
|
||||
headerLeft: () => (
|
||||
<HeaderLeft content='X' onPress={() => navigation.goBack()} />
|
||||
)
|
||||
|
@ -12,7 +12,13 @@ import {
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React from 'react'
|
||||
import { KeyboardAvoidingView, StyleSheet, Text, View } from 'react-native'
|
||||
import {
|
||||
KeyboardAvoidingView,
|
||||
Platform,
|
||||
StyleSheet,
|
||||
Text,
|
||||
View
|
||||
} from 'react-native'
|
||||
import { ScrollView } from 'react-native-gesture-handler'
|
||||
import { useQueryClient } from 'react-query'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
@ -59,7 +65,10 @@ const ScreenMeSwitchRoot = () => {
|
||||
const localActiveIndex = useSelector(getLocalActiveIndex)
|
||||
|
||||
return (
|
||||
<KeyboardAvoidingView behavior='padding' style={{ flex: 1 }}>
|
||||
<KeyboardAvoidingView
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||
style={{ flex: 1 }}
|
||||
>
|
||||
<ScrollView keyboardShouldPersistTaps='handled'>
|
||||
<View style={styles.firstSection}>
|
||||
<Text style={[styles.header, { color: theme.primary }]}>
|
||||
|
@ -1,11 +1,14 @@
|
||||
import ComponentInstance from '@components/Instance'
|
||||
import React from 'react'
|
||||
import { KeyboardAvoidingView } from 'react-native'
|
||||
import { KeyboardAvoidingView, Platform } from 'react-native'
|
||||
import { ScrollView } from 'react-native-gesture-handler'
|
||||
|
||||
const UpdateRemote: React.FC = () => {
|
||||
return (
|
||||
<KeyboardAvoidingView behavior='padding' style={{ flex: 1 }}>
|
||||
<KeyboardAvoidingView
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||
style={{ flex: 1 }}
|
||||
>
|
||||
<ScrollView keyboardShouldPersistTaps='handled'>
|
||||
<ComponentInstance type='remote' disableHeaderImage />
|
||||
</ScrollView>
|
||||
|
@ -1,8 +1,10 @@
|
||||
import { HeaderCenter } from '@components/Header'
|
||||
import Timeline from '@components/Timelines/Timeline'
|
||||
import sharedScreens from '@screens/Shared/sharedScreens'
|
||||
import { getLocalActiveIndex } from '@utils/slices/instancesSlice'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Platform } from 'react-native'
|
||||
import { createNativeStackNavigator } from 'react-native-screens/native-stack'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
@ -15,7 +17,13 @@ const ScreenNotifications: React.FC = () => {
|
||||
return (
|
||||
<Stack.Navigator
|
||||
screenOptions={{
|
||||
headerLeft: () => null,
|
||||
headerTitle: t('notifications:heading'),
|
||||
...(Platform.OS === 'android' && {
|
||||
headerCenter: () => (
|
||||
<HeaderCenter content={t('notifications:heading')} />
|
||||
)
|
||||
}),
|
||||
headerHideShadow: true,
|
||||
headerTopInsetEnabled: false
|
||||
}}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import Button from '@components/Button'
|
||||
import haptics from '@components/haptics'
|
||||
import { ParseHTML } from '@components/Parse'
|
||||
import RelativeTime from '@components/RelativeTime'
|
||||
import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs'
|
||||
import {
|
||||
useAnnouncementMutation,
|
||||
@ -10,7 +11,6 @@ import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Moment from 'react-moment'
|
||||
import {
|
||||
Dimensions,
|
||||
Image,
|
||||
@ -80,13 +80,7 @@ const ScreenSharedAnnouncements: React.FC<SharedAnnouncementsProp> = ({
|
||||
]}
|
||||
>
|
||||
<Text style={[styles.published, { color: theme.secondary }]}>
|
||||
发布于{' '}
|
||||
<Moment
|
||||
date={item.published_at}
|
||||
locale={i18n.language}
|
||||
element={Text}
|
||||
fromNow
|
||||
/>
|
||||
发布于 <RelativeTime date={item.published_at} />
|
||||
</Text>
|
||||
<ScrollView style={styles.scrollView} showsVerticalScrollIndicator>
|
||||
<ParseHTML
|
||||
|
@ -13,6 +13,7 @@ import {
|
||||
Alert,
|
||||
Keyboard,
|
||||
KeyboardAvoidingView,
|
||||
Platform,
|
||||
StyleSheet,
|
||||
Text
|
||||
} from 'react-native'
|
||||
@ -208,7 +209,10 @@ const Compose: React.FC<SharedComposeProp> = ({
|
||||
)
|
||||
|
||||
return (
|
||||
<KeyboardAvoidingView behavior='padding' style={{ flex: 1 }}>
|
||||
<KeyboardAvoidingView
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||
style={{ flex: 1 }}
|
||||
>
|
||||
<SafeAreaView
|
||||
style={{ flex: 1 }}
|
||||
edges={hasKeyboard ? ['left', 'right'] : ['left', 'right', 'bottom']}
|
||||
@ -223,7 +227,7 @@ const Compose: React.FC<SharedComposeProp> = ({
|
||||
<Stack.Screen
|
||||
name='Screen-Shared-Compose-EditAttachment'
|
||||
component={ComposeEditAttachment}
|
||||
options={{ stackPresentation: 'modal' }}
|
||||
options={{ stackPresentation: 'modal', headerShown: false }}
|
||||
/>
|
||||
</Stack.Navigator>
|
||||
</ComposeContext.Provider>
|
||||
|
@ -8,7 +8,7 @@ import React, {
|
||||
useRef,
|
||||
useState
|
||||
} from 'react'
|
||||
import { Alert, KeyboardAvoidingView } from 'react-native'
|
||||
import { Alert, KeyboardAvoidingView, Platform } from 'react-native'
|
||||
import { SafeAreaView } from 'react-native-safe-area-context'
|
||||
import { createNativeStackNavigator } from 'react-native-screens/native-stack'
|
||||
import ComposeEditAttachmentRoot from './EditAttachment/Root'
|
||||
@ -144,7 +144,10 @@ const ComposeEditAttachment: React.FC<Props> = ({
|
||||
)
|
||||
|
||||
return (
|
||||
<KeyboardAvoidingView behavior='padding' style={{ flex: 1 }}>
|
||||
<KeyboardAvoidingView
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||
style={{ flex: 1 }}
|
||||
>
|
||||
<SafeAreaView style={{ flex: 1 }} edges={['left', 'right', 'bottom']}>
|
||||
<Stack.Navigator screenOptions={{ headerTopInsetEnabled: false }}>
|
||||
<Stack.Screen
|
||||
|
@ -122,30 +122,26 @@ const ComposeEditAttachmentImage: React.FC<Props> = ({ index, focus }) => {
|
||||
styleTransform,
|
||||
{
|
||||
position: 'absolute',
|
||||
top: -1000 + imageDimensionis.height / 2,
|
||||
left: -1000 + imageDimensionis.width / 2
|
||||
top: -500 + imageDimensionis.height / 2,
|
||||
left: -500 + imageDimensionis.width / 2
|
||||
}
|
||||
]}
|
||||
>
|
||||
<Svg width='2000' height='2000' viewBox='0 0 2000 2000'>
|
||||
<G>
|
||||
<G id='Mask'>
|
||||
<Svg width='1000' height='1000' viewBox='0 0 1000 1000'>
|
||||
<G stroke='none' stroke-width='1' fill='none' fill-rule='evenodd'>
|
||||
<G>
|
||||
<Path
|
||||
d={
|
||||
'M2000,0 L2000,2000 L0,2000 L0,0 L2000,0 Z M1000,967 C981.774603,967 967,981.774603 967,1000 C967,1018.2254 981.774603,1033 1000,1033 C1018.2254,1033 1033,1018.2254 1033,1000 C1033,981.774603 1018.2254,967 1000,967 Z'
|
||||
}
|
||||
d='M1000,0 L1000,1000 L0,1000 L0,0 L1000,0 Z M500,475 C486.192881,475 475,486.192881 475,500 C475,513.807119 486.192881,525 500,525 C513.807119,525 525,513.807119 525,500 C525,486.192881 513.807119,475 500,475 Z'
|
||||
fill={theme.backgroundOverlay}
|
||||
/>
|
||||
<G transform='translate(967, 967)'>
|
||||
<Circle
|
||||
stroke={theme.primaryOverlay}
|
||||
strokeWidth='2'
|
||||
cx='33'
|
||||
cy='33'
|
||||
r='33'
|
||||
/>
|
||||
<Circle fill={theme.primaryOverlay} cx='33' cy='33' r='2' />
|
||||
</G>
|
||||
<Circle
|
||||
stroke={theme.primaryOverlay}
|
||||
stroke-width='2'
|
||||
cx='500'
|
||||
cy='500'
|
||||
r='24'
|
||||
/>
|
||||
<Circle fill={theme.primaryOverlay} cx='500' cy='500' r='2' />
|
||||
</G>
|
||||
</G>
|
||||
</Svg>
|
||||
|
@ -10,27 +10,40 @@ import { ActionSheetOptions } from '@expo/react-native-action-sheet'
|
||||
|
||||
export interface Props {
|
||||
composeDispatch: Dispatch<ComposeAction>
|
||||
showActionSheetWithOptions: (options: ActionSheetOptions, callback: (i: number) => void) => void
|
||||
showActionSheetWithOptions: (
|
||||
options: ActionSheetOptions,
|
||||
callback: (i: number) => void
|
||||
) => void
|
||||
}
|
||||
|
||||
const addAttachment = async ({ composeDispatch, showActionSheetWithOptions }: Props): Promise<any> => {
|
||||
const addAttachment = async ({
|
||||
composeDispatch,
|
||||
showActionSheetWithOptions
|
||||
}: Props): Promise<any> => {
|
||||
const uploadAttachment = async (result: ImageInfo) => {
|
||||
const hash = await Crypto.digestStringAsync(
|
||||
Crypto.CryptoDigestAlgorithm.SHA256,
|
||||
result.uri + Math.random()
|
||||
)
|
||||
|
||||
let attachmentType: string
|
||||
// https://github.com/expo/expo/issues/11214
|
||||
const attachmentUri = result.uri.replace('file:/data', 'file:///data')
|
||||
|
||||
switch (result.type) {
|
||||
case 'image':
|
||||
attachmentType = `image/${attachmentUri.split('.')[1]}`
|
||||
composeDispatch({
|
||||
type: 'attachment/upload/start',
|
||||
payload: {
|
||||
local: { ...result, local_thumbnail: result.uri, hash },
|
||||
local: { ...result, local_thumbnail: attachmentUri, hash },
|
||||
uploading: true
|
||||
}
|
||||
})
|
||||
break
|
||||
case 'video':
|
||||
VideoThumbnails.getThumbnailAsync(result.uri)
|
||||
attachmentType = `video/${attachmentUri.split('.')[1]}`
|
||||
VideoThumbnails.getThumbnailAsync(attachmentUri)
|
||||
.then(({ uri }) =>
|
||||
composeDispatch({
|
||||
type: 'attachment/upload/start',
|
||||
@ -51,6 +64,7 @@ const addAttachment = async ({ composeDispatch, showActionSheetWithOptions }: Pr
|
||||
)
|
||||
break
|
||||
default:
|
||||
attachmentType = 'unknown'
|
||||
composeDispatch({
|
||||
type: 'attachment/upload/start',
|
||||
payload: {
|
||||
@ -64,9 +78,9 @@ const addAttachment = async ({ composeDispatch, showActionSheetWithOptions }: Pr
|
||||
const formData = new FormData()
|
||||
formData.append('file', {
|
||||
// @ts-ignore
|
||||
uri: result.uri,
|
||||
name: result.uri.split('/').pop(),
|
||||
type: 'image/jpeg/jpg'
|
||||
uri: attachmentUri,
|
||||
name: attachmentType,
|
||||
type: attachmentType
|
||||
})
|
||||
|
||||
return client<Mastodon.Attachment>({
|
||||
|
@ -8,6 +8,7 @@ import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import {
|
||||
KeyboardAvoidingView,
|
||||
Platform,
|
||||
Pressable,
|
||||
SectionList,
|
||||
StyleSheet,
|
||||
@ -163,14 +164,17 @@ const ScreenSharedSearch: React.FC<Props> = ({ searchTerm }) => {
|
||||
</Pressable>
|
||||
)
|
||||
case 'statuses':
|
||||
return <TimelineDefault item={item} index={index} disableDetails />
|
||||
return <TimelineDefault item={item} disableDetails />
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<KeyboardAvoidingView behavior='padding' style={{ flex: 1 }}>
|
||||
<KeyboardAvoidingView
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||
style={{ flex: 1 }}
|
||||
>
|
||||
<SectionList
|
||||
style={styles.base}
|
||||
renderItem={listItem}
|
||||
|
@ -107,7 +107,8 @@ const sharedScreens = (
|
||||
component={ScreenSharedAnnouncements}
|
||||
options={{
|
||||
stackPresentation: 'transparentModal',
|
||||
stackAnimation: 'fade'
|
||||
stackAnimation: 'fade',
|
||||
headerShown: false
|
||||
}}
|
||||
/>,
|
||||
<Stack.Screen
|
||||
@ -115,7 +116,8 @@ const sharedScreens = (
|
||||
name='Screen-Shared-Compose'
|
||||
component={Compose}
|
||||
options={{
|
||||
stackPresentation: 'fullScreenModal'
|
||||
stackPresentation: 'fullScreenModal',
|
||||
headerShown: false
|
||||
}}
|
||||
/>,
|
||||
<Stack.Screen
|
||||
@ -133,7 +135,8 @@ const sharedScreens = (
|
||||
component={ScreenSharedImagesViewer}
|
||||
options={{
|
||||
stackPresentation: 'transparentModal',
|
||||
stackAnimation: 'none'
|
||||
stackAnimation: 'none',
|
||||
headerShown: false
|
||||
}}
|
||||
/>,
|
||||
<Stack.Screen
|
||||
|
22
yarn.lock
22
yarn.lock
@ -2210,6 +2210,13 @@
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react-timeago@^4.1.2":
|
||||
version "4.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-timeago/-/react-timeago-4.1.2.tgz#fc365ac4483888e9b47267259416be2fd5cf765f"
|
||||
integrity sha512-gkhU3rH7aZgeRybbm9ie9wHOM9i1I5YhUoto/uqY/DAbeRZuLU8ugl6E97jp65XCl9QTij32Vs7BAX2E/MqOAw==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react@*":
|
||||
version "17.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.0.tgz#5af3eb7fad2807092f0046a1302b7823e27919b8"
|
||||
@ -7315,11 +7322,6 @@ mkdirp@^1.0.3:
|
||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
|
||||
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
|
||||
|
||||
moment@^2.29.1:
|
||||
version "2.29.1"
|
||||
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3"
|
||||
integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==
|
||||
|
||||
ms@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
||||
@ -8186,11 +8188,6 @@ react-is@^17.0.1:
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.1.tgz#5b3531bd76a645a4c9fb6e693ed36419e3301339"
|
||||
integrity sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==
|
||||
|
||||
react-moment@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/react-moment/-/react-moment-1.1.1.tgz#5fe9fb257039590c804e2b3aedfc3ceb0a6ffb16"
|
||||
integrity sha512-WjwvxBSnmLMRcU33do0KixDB+9vP3e84eCse+rd+HNklAMNWyRgZTDEQlay/qK6lcXFPRuEIASJTpEt6pyK7Ww==
|
||||
|
||||
react-native-animated-spinkit@^1.4.2:
|
||||
version "1.4.2"
|
||||
resolved "https://registry.yarnpkg.com/react-native-animated-spinkit/-/react-native-animated-spinkit-1.4.2.tgz#cb60ff8bcc2bb848409d9aa85ed528646ad1f953"
|
||||
@ -8380,6 +8377,11 @@ react-test-renderer@~16.11.0:
|
||||
react-is "^16.8.6"
|
||||
scheduler "^0.17.0"
|
||||
|
||||
react-timeago@^5.2.0:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/react-timeago/-/react-timeago-5.2.0.tgz#d655d40aa55e4fe08a92234481a6aea7f656ab5d"
|
||||
integrity sha512-wCEEDGQHMdFh/PLp+Hj5vk9ZoC4KjQ5u0u6+KrrY9rny5LqJ2gZvNNEAS4mhSZDV1i7JLgQI5VQTAux7f+vj2w==
|
||||
|
||||
react@16.13.1:
|
||||
version "16.13.1"
|
||||
resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e"
|
||||
|
Loading…
Reference in New Issue
Block a user