mirror of
https://github.com/tooot-app/app
synced 2025-06-05 22:19:13 +02:00
Using tab-view
Cannot scroll separately
This commit is contained in:
@ -44,6 +44,7 @@
|
|||||||
"react-native-screens": "~2.15.0",
|
"react-native-screens": "~2.15.0",
|
||||||
"react-native-shimmer-placeholder": "^2.0.6",
|
"react-native-shimmer-placeholder": "^2.0.6",
|
||||||
"react-native-svg": "12.1.0",
|
"react-native-svg": "12.1.0",
|
||||||
|
"react-native-tab-view": "^2.15.2",
|
||||||
"react-native-toast-message": "^1.3.4",
|
"react-native-toast-message": "^1.3.4",
|
||||||
"react-native-webview": "11.0.0",
|
"react-native-webview": "11.0.0",
|
||||||
"react-navigation": "^4.4.3",
|
"react-navigation": "^4.4.3",
|
||||||
@ -70,4 +71,4 @@
|
|||||||
"typescript": "~4.0.0"
|
"typescript": "~4.0.0"
|
||||||
},
|
},
|
||||||
"private": true
|
"private": true
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React, { useCallback, useState } from 'react'
|
import React, { useCallback, useState } from 'react'
|
||||||
import { Pressable, Text } from 'react-native'
|
import { Pressable, Text, View } from 'react-native'
|
||||||
import HTMLView from 'react-native-htmlview'
|
import HTMLView from 'react-native-htmlview'
|
||||||
import { useNavigation } from '@react-navigation/native'
|
import { useNavigation } from '@react-navigation/native'
|
||||||
|
|
||||||
@ -98,6 +98,12 @@ const renderNode = ({
|
|||||||
</Text>
|
</Text>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if (node.name === 'p') {
|
||||||
|
if (!node.children.length) {
|
||||||
|
return <View key={index}></View> // bug when the tag is empty
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,26 +140,24 @@ const ParseContent: React.FC<Props> = ({
|
|||||||
}),
|
}),
|
||||||
[]
|
[]
|
||||||
)
|
)
|
||||||
const textComponent = useCallback(
|
const textComponent = useCallback(({ children }) => {
|
||||||
({ children }) =>
|
return emojis && children ? (
|
||||||
emojis && children ? (
|
<Emojis
|
||||||
<Emojis
|
content={children.toString()}
|
||||||
content={children.toString()}
|
emojis={emojis}
|
||||||
emojis={emojis}
|
size={StyleConstants.Font.Size[size]}
|
||||||
size={StyleConstants.Font.Size[size]}
|
/>
|
||||||
/>
|
) : (
|
||||||
) : (
|
<Text>{children}</Text>
|
||||||
<Text>{children}</Text>
|
)
|
||||||
),
|
}, [])
|
||||||
[]
|
|
||||||
)
|
|
||||||
const rootComponent = useCallback(({ children }) => {
|
const rootComponent = useCallback(({ children }) => {
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const [textLoaded, setTextLoaded] = useState(false)
|
const [textLoaded, setTextLoaded] = useState(false)
|
||||||
const [totalLines, setTotalLines] = useState<number | undefined>()
|
const [totalLines, setTotalLines] = useState<number | undefined>()
|
||||||
const [lineHeight, setLineHeight] = useState<number | undefined>()
|
const [lineHeight, setLineHeight] = useState<number | undefined>()
|
||||||
const [shownLines, setShownLines] = useState(numberOfLines)
|
const [shownLines, setShownLines] = useState(numberOfLines)
|
||||||
|
// console.log(children)
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Text
|
<Text
|
||||||
|
@ -39,7 +39,7 @@ const fireMutation = async ({
|
|||||||
} else {
|
} else {
|
||||||
toast({
|
toast({
|
||||||
type: 'error',
|
type: 'error',
|
||||||
content: '隐藏域名失败,请重试',
|
content: '投票失败,请重试',
|
||||||
autoHide: false
|
autoHide: false
|
||||||
})
|
})
|
||||||
return Promise.reject()
|
return Promise.reject()
|
||||||
@ -104,7 +104,7 @@ const TimelinePoll: React.FC<Props> = ({ queryKey, status: { poll } }) => {
|
|||||||
|
|
||||||
const pollExpiration = useMemo(() => {
|
const pollExpiration = useMemo(() => {
|
||||||
// how many voted
|
// how many voted
|
||||||
if (poll.expired) {
|
if (poll!.expired) {
|
||||||
return (
|
return (
|
||||||
<Text style={[styles.expiration, { color: theme.secondary }]}>
|
<Text style={[styles.expiration, { color: theme.secondary }]}>
|
||||||
投票已结束
|
投票已结束
|
||||||
@ -113,20 +113,20 @@ const TimelinePoll: React.FC<Props> = ({ queryKey, status: { poll } }) => {
|
|||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<Text style={[styles.expiration, { color: theme.secondary }]}>
|
<Text style={[styles.expiration, { color: theme.secondary }]}>
|
||||||
截止至{relativeTime(poll.expires_at)}
|
{relativeTime(poll!.expires_at)}截止
|
||||||
</Text>
|
</Text>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const [singleOptions, setSingleOptions] = useState({
|
const [singleOptions, setSingleOptions] = useState({
|
||||||
...[false, false, false, false].slice(0, poll.options.length)
|
...[false, false, false, false].slice(0, poll!.options.length)
|
||||||
})
|
})
|
||||||
const [multipleOptions, setMultipleOptions] = useState({
|
const [multipleOptions, setMultipleOptions] = useState({
|
||||||
...[false, false, false, false].slice(0, poll.options.length)
|
...[false, false, false, false].slice(0, poll!.options.length)
|
||||||
})
|
})
|
||||||
const isSelected = (index: number) => {
|
const isSelected = (index: number) => {
|
||||||
if (poll.multiple) {
|
if (poll!.multiple) {
|
||||||
return multipleOptions[index] ? 'check-square' : 'square'
|
return multipleOptions[index] ? 'check-square' : 'square'
|
||||||
} else {
|
} else {
|
||||||
return singleOptions[index] ? 'check-circle' : 'circle'
|
return singleOptions[index] ? 'check-circle' : 'circle'
|
||||||
@ -135,28 +135,28 @@ const TimelinePoll: React.FC<Props> = ({ queryKey, status: { poll } }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.base}>
|
<View style={styles.base}>
|
||||||
{poll.options.map((option, index) =>
|
{poll!.options.map((option, index) =>
|
||||||
poll.voted ? (
|
poll!.voted ? (
|
||||||
<View key={index} style={styles.poll}>
|
<View key={index} style={styles.poll}>
|
||||||
<View style={styles.optionSelected}>
|
<View style={styles.optionSelected}>
|
||||||
<View style={styles.contentSelected}>
|
<View style={styles.contentSelected}>
|
||||||
<Emojis
|
<Emojis
|
||||||
content={option.title}
|
content={option.title}
|
||||||
emojis={poll.emojis}
|
emojis={poll!.emojis}
|
||||||
size={StyleConstants.Font.Size.M}
|
size={StyleConstants.Font.Size.M}
|
||||||
numberOfLines={1}
|
numberOfLines={1}
|
||||||
/>
|
/>
|
||||||
{poll.own_votes!.includes(index) && (
|
{poll!.own_votes!.includes(index) && (
|
||||||
<Feather
|
<Feather
|
||||||
style={styles.voted}
|
style={styles.voted}
|
||||||
name={poll.multiple ? 'check-square' : 'check-circle'}
|
name={poll!.multiple ? 'check-square' : 'check-circle'}
|
||||||
size={StyleConstants.Font.Size.M}
|
size={StyleConstants.Font.Size.M}
|
||||||
color={theme.primary}
|
color={theme.primary}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
<Text style={[styles.percentage, { color: theme.primary }]}>
|
<Text style={[styles.percentage, { color: theme.primary }]}>
|
||||||
{Math.round((option.votes_count / poll.votes_count) * 100)}%
|
{Math.round((option.votes_count / poll!.votes_count) * 100)}%
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
@ -165,7 +165,7 @@ const TimelinePoll: React.FC<Props> = ({ queryKey, status: { poll } }) => {
|
|||||||
styles.background,
|
styles.background,
|
||||||
{
|
{
|
||||||
width: `${Math.round(
|
width: `${Math.round(
|
||||||
(option.votes_count / poll.votes_count) * 100
|
(option.votes_count / poll!.votes_count) * 100
|
||||||
)}%`,
|
)}%`,
|
||||||
backgroundColor: theme.border
|
backgroundColor: theme.border
|
||||||
}
|
}
|
||||||
@ -177,7 +177,7 @@ const TimelinePoll: React.FC<Props> = ({ queryKey, status: { poll } }) => {
|
|||||||
<Pressable
|
<Pressable
|
||||||
style={[styles.optionUnselected]}
|
style={[styles.optionUnselected]}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
if (poll.multiple) {
|
if (poll!.multiple) {
|
||||||
setMultipleOptions({
|
setMultipleOptions({
|
||||||
...multipleOptions,
|
...multipleOptions,
|
||||||
[index]: !multipleOptions[index]
|
[index]: !multipleOptions[index]
|
||||||
@ -189,7 +189,7 @@ const TimelinePoll: React.FC<Props> = ({ queryKey, status: { poll } }) => {
|
|||||||
index === 1,
|
index === 1,
|
||||||
index === 2,
|
index === 2,
|
||||||
index === 3
|
index === 3
|
||||||
].slice(0, poll.options.length)
|
].slice(0, poll!.options.length)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
@ -203,7 +203,7 @@ const TimelinePoll: React.FC<Props> = ({ queryKey, status: { poll } }) => {
|
|||||||
<View style={styles.contentUnselected}>
|
<View style={styles.contentUnselected}>
|
||||||
<Emojis
|
<Emojis
|
||||||
content={option.title}
|
content={option.title}
|
||||||
emojis={poll.emojis}
|
emojis={poll!.emojis}
|
||||||
size={StyleConstants.Font.Size.M}
|
size={StyleConstants.Font.Size.M}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
@ -227,7 +227,7 @@ const TimelinePoll: React.FC<Props> = ({ queryKey, status: { poll } }) => {
|
|||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
<Text style={[styles.votes, { color: theme.secondary }]}>
|
<Text style={[styles.votes, { color: theme.secondary }]}>
|
||||||
已投{poll.voters_count}人{' • '}
|
已投{poll.voters_count || 0}人{' • '}
|
||||||
</Text>
|
</Text>
|
||||||
{pollExpiration}
|
{pollExpiration}
|
||||||
</View>
|
</View>
|
||||||
|
@ -21,9 +21,10 @@ import { useTranslation } from 'react-i18next'
|
|||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { ButtonRow } from '@components/Button'
|
import { ButtonRow } from '@components/Button'
|
||||||
import ParseContent from '@root/components/ParseContent'
|
import ParseContent from '@root/components/ParseContent'
|
||||||
import ShimmerPlaceholder from 'react-native-shimmer-placeholder'
|
import { createShimmerPlaceholder } from 'react-native-shimmer-placeholder'
|
||||||
import { Feather } from '@expo/vector-icons'
|
import { Feather } from '@expo/vector-icons'
|
||||||
import { applicationFetch } from '@root/utils/fetches/applicationFetch'
|
import { applicationFetch } from '@root/utils/fetches/applicationFetch'
|
||||||
|
import { LinearGradient } from 'expo-linear-gradient'
|
||||||
|
|
||||||
const Login: React.FC = () => {
|
const Login: React.FC = () => {
|
||||||
const { t } = useTranslation('meRoot')
|
const { t } = useTranslation('meRoot')
|
||||||
@ -36,6 +37,8 @@ const Login: React.FC = () => {
|
|||||||
clientSecret: string
|
clientSecret: string
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
const ShimmerPlaceholder = createShimmerPlaceholder(LinearGradient)
|
||||||
|
|
||||||
const instanceQuery = useQuery(
|
const instanceQuery = useQuery(
|
||||||
['Instance', { instanceDomain }],
|
['Instance', { instanceDomain }],
|
||||||
instanceFetch,
|
instanceFetch,
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React from 'react'
|
import React, { createContext, Dispatch, useReducer, useRef } from 'react'
|
||||||
import { ScrollView } from 'react-native'
|
import { Animated, ScrollView } from 'react-native'
|
||||||
|
|
||||||
// import * as relationshipsSlice from 'src/stacks/common/relationshipsSlice'
|
// import * as relationshipsSlice from 'src/stacks/common/relationshipsSlice'
|
||||||
|
|
||||||
@ -8,6 +8,8 @@ import { accountFetch } from '@utils/fetches/accountFetch'
|
|||||||
import AccountToots from '@screens/Shared/Account/Toots'
|
import AccountToots from '@screens/Shared/Account/Toots'
|
||||||
import AccountHeader from '@screens/Shared/Account/Header'
|
import AccountHeader from '@screens/Shared/Account/Header'
|
||||||
import AccountInformation from '@screens/Shared/Account/Information'
|
import AccountInformation from '@screens/Shared/Account/Information'
|
||||||
|
import AccountNav from './Account/Nav'
|
||||||
|
import AccountSegmentedControl from './Account/SegmentedControl'
|
||||||
|
|
||||||
// Moved account example: https://m.cmx.im/web/accounts/27812
|
// Moved account example: https://m.cmx.im/web/accounts/27812
|
||||||
|
|
||||||
@ -19,6 +21,53 @@ export interface Props {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type AccountState = {
|
||||||
|
headerRatio: number
|
||||||
|
informationLayout?: {
|
||||||
|
y: number
|
||||||
|
height: number
|
||||||
|
}
|
||||||
|
segmentedIndex: number
|
||||||
|
}
|
||||||
|
export type AccountAction =
|
||||||
|
| {
|
||||||
|
type: 'headerRatio'
|
||||||
|
payload: AccountState['headerRatio']
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'informationLayout'
|
||||||
|
payload: AccountState['informationLayout']
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'segmentedIndex'
|
||||||
|
payload: AccountState['segmentedIndex']
|
||||||
|
}
|
||||||
|
const AccountInitialState: AccountState = {
|
||||||
|
headerRatio: 0.4,
|
||||||
|
informationLayout: { height: 0, y: 100 },
|
||||||
|
segmentedIndex: 0
|
||||||
|
}
|
||||||
|
const accountReducer = (
|
||||||
|
state: AccountState,
|
||||||
|
action: AccountAction
|
||||||
|
): AccountState => {
|
||||||
|
switch (action.type) {
|
||||||
|
case 'headerRatio':
|
||||||
|
return { ...state, headerRatio: action.payload }
|
||||||
|
case 'informationLayout':
|
||||||
|
return { ...state, informationLayout: action.payload }
|
||||||
|
case 'segmentedIndex':
|
||||||
|
return { ...state, segmentedIndex: action.payload }
|
||||||
|
default:
|
||||||
|
throw new Error('Unexpected action')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
type ContextType = {
|
||||||
|
accountState: AccountState
|
||||||
|
accountDispatch: Dispatch<AccountAction>
|
||||||
|
}
|
||||||
|
export const AccountContext = createContext<ContextType>({} as ContextType)
|
||||||
|
|
||||||
const ScreenSharedAccount: React.FC<Props> = ({
|
const ScreenSharedAccount: React.FC<Props> = ({
|
||||||
route: {
|
route: {
|
||||||
params: { id }
|
params: { id }
|
||||||
@ -27,13 +76,30 @@ const ScreenSharedAccount: React.FC<Props> = ({
|
|||||||
const { data } = useQuery(['Account', { id }], accountFetch)
|
const { data } = useQuery(['Account', { id }], accountFetch)
|
||||||
|
|
||||||
// const stateRelationships = useSelector(relationshipsState)
|
// const stateRelationships = useSelector(relationshipsState)
|
||||||
|
const scrollY = useRef(new Animated.Value(0)).current
|
||||||
|
const [accountState, accountDispatch] = useReducer(
|
||||||
|
accountReducer,
|
||||||
|
AccountInitialState
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollView bounces={false}>
|
<AccountContext.Provider value={{ accountState, accountDispatch }}>
|
||||||
<AccountHeader uri={data?.header} />
|
<AccountNav scrollY={scrollY} account={data} />
|
||||||
<AccountInformation account={data} />
|
<AccountSegmentedControl scrollY={scrollY} />
|
||||||
<AccountToots id={id} />
|
<ScrollView
|
||||||
</ScrollView>
|
contentContainerStyle={{ zIndex: 99 }}
|
||||||
|
bounces={false}
|
||||||
|
onScroll={Animated.event(
|
||||||
|
[{ nativeEvent: { contentOffset: { y: scrollY } } }],
|
||||||
|
{ useNativeDriver: false }
|
||||||
|
)}
|
||||||
|
scrollEventThrottle={16}
|
||||||
|
>
|
||||||
|
<AccountHeader uri={data?.header} />
|
||||||
|
<AccountInformation account={data} />
|
||||||
|
<AccountToots id={id} />
|
||||||
|
</ScrollView>
|
||||||
|
</AccountContext.Provider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,37 +1,44 @@
|
|||||||
import React, { useEffect, useRef } from 'react'
|
import React, { useContext, useEffect, useRef } from 'react'
|
||||||
import { Animated, Dimensions, Image, StyleSheet } from 'react-native'
|
import { Animated, Dimensions, Image, StyleSheet } from 'react-native'
|
||||||
|
import { AccountContext } from '../Account'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
uri?: Mastodon.Account['header']
|
uri?: Mastodon.Account['header']
|
||||||
limitHeight?: boolean
|
limitHeight?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const limitRatio = 0.4
|
|
||||||
|
|
||||||
const AccountHeader: React.FC<Props> = ({ uri, limitHeight = false }) => {
|
const AccountHeader: React.FC<Props> = ({ uri, limitHeight = false }) => {
|
||||||
|
const { accountState, accountDispatch } = useContext(AccountContext)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (uri) {
|
if (uri) {
|
||||||
if (uri.includes('/headers/original/missing.png')) {
|
if (uri.includes('/headers/original/missing.png')) {
|
||||||
animateNewSize(limitRatio)
|
animateNewSize(accountState.headerRatio)
|
||||||
} else {
|
} else {
|
||||||
Image.getSize(
|
Image.getSize(
|
||||||
uri,
|
uri,
|
||||||
(width, height) => {
|
(width, height) => {
|
||||||
animateNewSize(limitHeight ? limitRatio : height / width)
|
if (!limitHeight) {
|
||||||
|
accountDispatch({ type: 'headerRatio', payload: height / width })
|
||||||
|
}
|
||||||
|
animateNewSize(
|
||||||
|
limitHeight ? accountState.headerRatio : height / width
|
||||||
|
)
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
animateNewSize(limitRatio)
|
animateNewSize(accountState.headerRatio)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
animateNewSize(limitRatio)
|
animateNewSize(accountState.headerRatio)
|
||||||
}
|
}
|
||||||
}, [uri])
|
}, [uri])
|
||||||
|
|
||||||
const windowWidth = Dimensions.get('window').width
|
const windowWidth = Dimensions.get('window').width
|
||||||
const imageHeight = useRef(new Animated.Value(windowWidth * limitRatio))
|
const imageHeight = useRef(
|
||||||
.current
|
new Animated.Value(windowWidth * accountState.headerRatio)
|
||||||
|
).current
|
||||||
const animateNewSize = (ratio: number) => {
|
const animateNewSize = (ratio: number) => {
|
||||||
Animated.timing(imageHeight, {
|
Animated.timing(imageHeight, {
|
||||||
toValue: windowWidth * ratio,
|
toValue: windowWidth * ratio,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React, { createRef, useEffect, useState } from 'react'
|
import React, { createRef, useContext, useEffect, useState } from 'react'
|
||||||
import { Animated, Image, StyleSheet, Text, View } from 'react-native'
|
import { Animated, Image, StyleSheet, Text, View } from 'react-native'
|
||||||
import ShimmerPlaceholder from 'react-native-shimmer-placeholder'
|
import { createShimmerPlaceholder } from 'react-native-shimmer-placeholder'
|
||||||
import { Feather } from '@expo/vector-icons'
|
import { Feather } from '@expo/vector-icons'
|
||||||
|
|
||||||
import ParseContent from '@components/ParseContent'
|
import ParseContent from '@components/ParseContent'
|
||||||
@ -8,23 +8,27 @@ import { useTheme } from '@utils/styles/ThemeManager'
|
|||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Emojis from '@components/Timelines/Timeline/Shared/Emojis'
|
import Emojis from '@components/Timelines/Timeline/Shared/Emojis'
|
||||||
|
import { LinearGradient } from 'expo-linear-gradient'
|
||||||
|
import { AccountContext } from '../Account'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
account: Mastodon.Account | undefined
|
account: Mastodon.Account | undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
const AccountInformation: React.FC<Props> = ({ account }) => {
|
const AccountInformation: React.FC<Props> = ({ account }) => {
|
||||||
|
const { accountDispatch } = useContext(AccountContext)
|
||||||
const { t } = useTranslation('sharedAccount')
|
const { t } = useTranslation('sharedAccount')
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const [avatarLoaded, setAvatarLoaded] = useState(false)
|
const [avatarLoaded, setAvatarLoaded] = useState(false)
|
||||||
|
|
||||||
const shimmerAvatarRef = createRef<ShimmerPlaceholder>()
|
const ShimmerPlaceholder = createShimmerPlaceholder(LinearGradient)
|
||||||
const shimmerNameRef = createRef<ShimmerPlaceholder>()
|
const shimmerAvatarRef = createRef<any>()
|
||||||
const shimmerAccountRef = createRef<ShimmerPlaceholder>()
|
const shimmerNameRef = createRef<any>()
|
||||||
const shimmerCreatedRef = createRef<ShimmerPlaceholder>()
|
const shimmerAccountRef = createRef<any>()
|
||||||
const shimmerStatTootRef = createRef<ShimmerPlaceholder>()
|
const shimmerCreatedRef = createRef<any>()
|
||||||
const shimmerStatFolloingRef = createRef<ShimmerPlaceholder>()
|
const shimmerStatTootRef = createRef<any>()
|
||||||
const shimmerStatFollowerRef = createRef<ShimmerPlaceholder>()
|
const shimmerStatFolloingRef = createRef<any>()
|
||||||
|
const shimmerStatFollowerRef = createRef<any>()
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const informationAnimated = Animated.stagger(400, [
|
const informationAnimated = Animated.stagger(400, [
|
||||||
Animated.parallel([
|
Animated.parallel([
|
||||||
@ -41,7 +45,18 @@ const AccountInformation: React.FC<Props> = ({ account }) => {
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.information}>
|
<View
|
||||||
|
style={styles.information}
|
||||||
|
onLayout={({ nativeEvent }) =>
|
||||||
|
accountDispatch({
|
||||||
|
type: 'informationLayout',
|
||||||
|
payload: {
|
||||||
|
y: nativeEvent.layout.y,
|
||||||
|
height: nativeEvent.layout.height
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
{/* <Text>Moved or not: {account.moved}</Text> */}
|
{/* <Text>Moved or not: {account.moved}</Text> */}
|
||||||
<ShimmerPlaceholder
|
<ShimmerPlaceholder
|
||||||
ref={shimmerAvatarRef}
|
ref={shimmerAvatarRef}
|
||||||
@ -246,7 +261,7 @@ const AccountInformation: React.FC<Props> = ({ account }) => {
|
|||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
information: {
|
information: {
|
||||||
marginTop: -30 - StyleConstants.Spacing.Global.PagePadding,
|
marginTop: -StyleConstants.Spacing.Global.PagePadding * 3,
|
||||||
padding: StyleConstants.Spacing.Global.PagePadding
|
padding: StyleConstants.Spacing.Global.PagePadding
|
||||||
},
|
},
|
||||||
avatar: {
|
avatar: {
|
||||||
|
101
src/screens/Shared/Account/Nav.tsx
Normal file
101
src/screens/Shared/Account/Nav.tsx
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
import Emojis from '@root/components/Timelines/Timeline/Shared/Emojis'
|
||||||
|
import { StyleConstants } from '@root/utils/styles/constants'
|
||||||
|
import { useTheme } from '@root/utils/styles/ThemeManager'
|
||||||
|
import React, { useContext } from 'react'
|
||||||
|
import { Animated, Dimensions, StyleSheet, Text, View } from 'react-native'
|
||||||
|
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
||||||
|
import { AccountContext } from '../Account'
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
scrollY: Animated.Value
|
||||||
|
account: Mastodon.Account | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const AccountNav: React.FC<Props> = ({ scrollY, account }) => {
|
||||||
|
const { accountState } = useContext(AccountContext)
|
||||||
|
const { theme } = useTheme()
|
||||||
|
const headerHeight = useSafeAreaInsets().top + 44
|
||||||
|
|
||||||
|
const nameY =
|
||||||
|
Dimensions.get('screen').width * accountState.headerRatio +
|
||||||
|
StyleConstants.Avatar.L -
|
||||||
|
StyleConstants.Spacing.Global.PagePadding * 2 +
|
||||||
|
StyleConstants.Spacing.M -
|
||||||
|
headerHeight
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Animated.View
|
||||||
|
style={[
|
||||||
|
styles.base,
|
||||||
|
{
|
||||||
|
backgroundColor: theme.background,
|
||||||
|
opacity: scrollY.interpolate({
|
||||||
|
inputRange: [0, 200],
|
||||||
|
outputRange: [0, 1],
|
||||||
|
extrapolate: 'clamp'
|
||||||
|
}),
|
||||||
|
height: headerHeight
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
styles.content,
|
||||||
|
{
|
||||||
|
marginTop:
|
||||||
|
useSafeAreaInsets().top + (44 - StyleConstants.Font.Size.L) / 2
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Animated.View
|
||||||
|
style={[
|
||||||
|
styles.display_name,
|
||||||
|
{
|
||||||
|
marginTop: scrollY.interpolate({
|
||||||
|
inputRange: [nameY, nameY + 20],
|
||||||
|
outputRange: [50, 0],
|
||||||
|
extrapolate: 'clamp'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{account?.emojis ? (
|
||||||
|
<Emojis
|
||||||
|
content={account?.display_name || account?.username}
|
||||||
|
emojis={account.emojis}
|
||||||
|
size={StyleConstants.Font.Size.L}
|
||||||
|
fontBold={true}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
color: theme.primary,
|
||||||
|
fontSize: StyleConstants.Font.Size.L,
|
||||||
|
fontWeight: StyleConstants.Font.Weight.Bold
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{account?.display_name || account?.username}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</Animated.View>
|
||||||
|
</View>
|
||||||
|
</Animated.View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
base: {
|
||||||
|
...StyleSheet.absoluteFillObject,
|
||||||
|
zIndex: 99
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
flex: 1,
|
||||||
|
alignItems: 'center',
|
||||||
|
overflow: 'hidden'
|
||||||
|
},
|
||||||
|
display_name: {
|
||||||
|
flexDirection: 'row'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default AccountNav
|
81
src/screens/Shared/Account/SegmentedControl.tsx
Normal file
81
src/screens/Shared/Account/SegmentedControl.tsx
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
import SegmentedControl from '@react-native-community/segmented-control'
|
||||||
|
import { StyleConstants } from '@root/utils/styles/constants'
|
||||||
|
import { useTheme } from '@root/utils/styles/ThemeManager'
|
||||||
|
import React, { useContext } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { Animated, StyleSheet } from 'react-native'
|
||||||
|
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
||||||
|
import { AccountContext } from '../Account'
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
scrollY: Animated.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
const AccountSegmentedControl: React.FC<Props> = ({ scrollY }) => {
|
||||||
|
const { accountState, accountDispatch } = useContext(AccountContext)
|
||||||
|
const { t } = useTranslation('sharedAccount')
|
||||||
|
const { mode, theme } = useTheme()
|
||||||
|
|
||||||
|
const headerHeight = useSafeAreaInsets().top + 44
|
||||||
|
const translateY = scrollY.interpolate({
|
||||||
|
inputRange: [
|
||||||
|
0,
|
||||||
|
(accountState.informationLayout?.y || 0) +
|
||||||
|
(accountState.informationLayout?.height || 0) -
|
||||||
|
headerHeight
|
||||||
|
],
|
||||||
|
outputRange: [
|
||||||
|
0,
|
||||||
|
-(accountState.informationLayout?.y || 0) -
|
||||||
|
(accountState.informationLayout?.height || 0) +
|
||||||
|
headerHeight
|
||||||
|
],
|
||||||
|
extrapolate: 'clamp'
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Animated.View
|
||||||
|
style={[
|
||||||
|
styles.base,
|
||||||
|
{
|
||||||
|
top:
|
||||||
|
(accountState.informationLayout?.y || 0) +
|
||||||
|
(accountState.informationLayout?.height || 0),
|
||||||
|
transform: [{ translateY }],
|
||||||
|
borderTopColor: theme.border,
|
||||||
|
backgroundColor: theme.background
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<SegmentedControl
|
||||||
|
values={[
|
||||||
|
t('content.segments.left'),
|
||||||
|
t('content.segments.middle'),
|
||||||
|
t('content.segments.right')
|
||||||
|
]}
|
||||||
|
selectedIndex={accountState.segmentedIndex}
|
||||||
|
onChange={({ nativeEvent }) =>
|
||||||
|
accountDispatch({
|
||||||
|
type: 'segmentedIndex',
|
||||||
|
payload: nativeEvent.selectedSegmentIndex
|
||||||
|
})
|
||||||
|
}
|
||||||
|
appearance={mode}
|
||||||
|
/>
|
||||||
|
</Animated.View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
base: {
|
||||||
|
...StyleSheet.absoluteFillObject,
|
||||||
|
position: 'absolute',
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
zIndex: 99,
|
||||||
|
borderTopWidth: StyleSheet.hairlineWidth,
|
||||||
|
padding: StyleConstants.Spacing.Global.PagePadding
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default AccountSegmentedControl
|
@ -1,87 +1,60 @@
|
|||||||
import React, { useRef, useState } from 'react'
|
import React, { useCallback, useContext } from 'react'
|
||||||
import { Dimensions, FlatList, View } from 'react-native'
|
import { Dimensions, StyleSheet } from 'react-native'
|
||||||
import SegmentedControl from '@react-native-community/segmented-control'
|
import { TabView, SceneMap } from 'react-native-tab-view'
|
||||||
|
|
||||||
import Timeline from '@components/Timelines/Timeline'
|
import Timeline from '@components/Timelines/Timeline'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { AccountContext } from '../Account'
|
||||||
|
import { StyleConstants } from '@root/utils/styles/constants'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
id: Mastodon.Account['id']
|
id: Mastodon.Account['id']
|
||||||
}
|
}
|
||||||
|
|
||||||
const AccountToots: React.FC<Props> = ({ id }) => {
|
const AccountToots: React.FC<Props> = ({ id }) => {
|
||||||
const { t } = useTranslation('sharedAccount')
|
const { accountState, accountDispatch } = useContext(AccountContext)
|
||||||
const [segment, setSegment] = useState(0)
|
|
||||||
const [segmentManuallyTriggered, setSegmentManuallyTriggered] = useState(
|
|
||||||
false
|
|
||||||
)
|
|
||||||
const horizontalPaging = useRef<any>()
|
|
||||||
|
|
||||||
const pages: ['Account_Default', 'Account_All', 'Account_Media'] = [
|
const [routes] = React.useState([
|
||||||
'Account_Default',
|
{ key: 'Account_Default' },
|
||||||
'Account_All',
|
{ key: 'Account_All' },
|
||||||
'Account_Media'
|
{ key: 'Account_Media' }
|
||||||
]
|
])
|
||||||
|
const singleScene = useCallback(
|
||||||
|
({ route }) => (
|
||||||
|
<Timeline
|
||||||
|
page={route.key}
|
||||||
|
account={id}
|
||||||
|
disableRefresh
|
||||||
|
scrollEnabled={false}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
const renderScene = SceneMap({
|
||||||
|
Account_Default: singleScene,
|
||||||
|
Account_All: singleScene,
|
||||||
|
Account_Media: singleScene
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<TabView
|
||||||
<SegmentedControl
|
style={styles.base}
|
||||||
values={[
|
navigationState={{ index: accountState.segmentedIndex, routes }}
|
||||||
t('content.segments.left'),
|
renderScene={renderScene}
|
||||||
t('content.segments.middle'),
|
renderTabBar={() => null}
|
||||||
t('content.segments.right')
|
onIndexChange={index =>
|
||||||
]}
|
accountDispatch({ type: 'segmentedIndex', payload: index })
|
||||||
selectedIndex={segment}
|
}
|
||||||
onChange={({ nativeEvent }) => {
|
initialLayout={{ width: Dimensions.get('window').width }}
|
||||||
setSegmentManuallyTriggered(true)
|
lazy
|
||||||
setSegment(nativeEvent.selectedSegmentIndex)
|
swipeEnabled
|
||||||
horizontalPaging.current.scrollToIndex({
|
/>
|
||||||
index: nativeEvent.selectedSegmentIndex
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
style={{ width: '100%', height: 30 }}
|
|
||||||
/>
|
|
||||||
<FlatList
|
|
||||||
style={{ width: Dimensions.get('window').width, height: '100%' }}
|
|
||||||
data={pages}
|
|
||||||
keyExtractor={page => page}
|
|
||||||
renderItem={({ item, index }) => {
|
|
||||||
return (
|
|
||||||
<View style={{ width: Dimensions.get('window').width }}>
|
|
||||||
<Timeline
|
|
||||||
key={index}
|
|
||||||
page={item}
|
|
||||||
account={id}
|
|
||||||
disableRefresh
|
|
||||||
scrollEnabled={false}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
ref={horizontalPaging}
|
|
||||||
bounces={false}
|
|
||||||
getItemLayout={(data, index) => ({
|
|
||||||
length: Dimensions.get('window').width,
|
|
||||||
offset: Dimensions.get('window').width * index,
|
|
||||||
index
|
|
||||||
})}
|
|
||||||
horizontal
|
|
||||||
onMomentumScrollEnd={() => {
|
|
||||||
setSegmentManuallyTriggered(false)
|
|
||||||
}}
|
|
||||||
onScroll={({ nativeEvent }) =>
|
|
||||||
!segmentManuallyTriggered &&
|
|
||||||
setSegment(
|
|
||||||
nativeEvent.contentOffset.x <= Dimensions.get('window').width / 3
|
|
||||||
? 0
|
|
||||||
: 1
|
|
||||||
)
|
|
||||||
}
|
|
||||||
pagingEnabled
|
|
||||||
showsHorizontalScrollIndicator={false}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
base: {
|
||||||
|
marginTop: StyleConstants.Spacing.Global.PagePadding + 33
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
export default AccountToots
|
export default AccountToots
|
||||||
|
@ -90,7 +90,7 @@ export type ComposeState = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PostAction =
|
export type ComposeAction =
|
||||||
| {
|
| {
|
||||||
type: 'spoiler'
|
type: 'spoiler'
|
||||||
payload: Partial<ComposeState['spoiler']>
|
payload: Partial<ComposeState['spoiler']>
|
||||||
@ -251,7 +251,10 @@ const composeExistingState = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const postReducer = (state: ComposeState, action: PostAction): ComposeState => {
|
const composeReducer = (
|
||||||
|
state: ComposeState,
|
||||||
|
action: ComposeAction
|
||||||
|
): ComposeState => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case 'spoiler':
|
case 'spoiler':
|
||||||
return { ...state, spoiler: { ...state.spoiler, ...action.payload } }
|
return { ...state, spoiler: { ...state.spoiler, ...action.payload } }
|
||||||
@ -294,7 +297,7 @@ const postReducer = (state: ComposeState, action: PostAction): ComposeState => {
|
|||||||
|
|
||||||
type ContextType = {
|
type ContextType = {
|
||||||
composeState: ComposeState
|
composeState: ComposeState
|
||||||
composeDispatch: Dispatch<PostAction>
|
composeDispatch: Dispatch<ComposeAction>
|
||||||
}
|
}
|
||||||
export const ComposeContext = createContext<ContextType>({} as ContextType)
|
export const ComposeContext = createContext<ContextType>({} as ContextType)
|
||||||
|
|
||||||
@ -332,7 +335,7 @@ const Compose: React.FC<Props> = ({ route: { params }, navigation }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const [composeState, composeDispatch] = useReducer(
|
const [composeState, composeDispatch] = useReducer(
|
||||||
postReducer,
|
composeReducer,
|
||||||
params?.type && params?.incomingStatus
|
params?.type && params?.incomingStatus
|
||||||
? composeExistingState({
|
? composeExistingState({
|
||||||
type: params.type,
|
type: params.type,
|
||||||
|
@ -12,10 +12,11 @@ import { ComposeContext } from '@screens/Shared/Compose'
|
|||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import { useNavigation } from '@react-navigation/native'
|
import { useNavigation } from '@react-navigation/native'
|
||||||
import ShimmerPlaceholder from 'react-native-shimmer-placeholder'
|
import { createShimmerPlaceholder } from 'react-native-shimmer-placeholder'
|
||||||
import { ButtonRound } from '@components/Button'
|
import { ButtonRound } from '@components/Button'
|
||||||
import addAttachments from '@screens/Shared/Compose/addAttachments'
|
import addAttachments from '@screens/Shared/Compose/addAttachments'
|
||||||
import { Feather } from '@expo/vector-icons'
|
import { Feather } from '@expo/vector-icons'
|
||||||
|
import { LinearGradient } from 'expo-linear-gradient'
|
||||||
|
|
||||||
const DEFAULT_HEIGHT = 200
|
const DEFAULT_HEIGHT = 200
|
||||||
|
|
||||||
@ -97,6 +98,8 @@ const ComposeAttachments: React.FC = () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
const listFooter = useCallback(() => {
|
const listFooter = useCallback(() => {
|
||||||
|
const ShimmerPlaceholder = createShimmerPlaceholder(LinearGradient)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ShimmerPlaceholder
|
<ShimmerPlaceholder
|
||||||
style={styles.progressContainer}
|
style={styles.progressContainer}
|
||||||
|
@ -18,14 +18,21 @@ const sharedScreens = (Stack: any) => {
|
|||||||
key='Screen-Shared-Account'
|
key='Screen-Shared-Account'
|
||||||
name='Screen-Shared-Account'
|
name='Screen-Shared-Account'
|
||||||
component={ScreenSharedAccount}
|
component={ScreenSharedAccount}
|
||||||
options={({ navigation }: any) => ({
|
options={({ navigation }: any) => {
|
||||||
headerTranslucent: true,
|
return {
|
||||||
headerStyle: { backgroundColor: 'rgba(255, 255, 255, 0)' },
|
headerTranslucent: true,
|
||||||
headerCenter: () => null,
|
headerStyle: {
|
||||||
headerLeft: () => (
|
backgroundColor: `rgba(255, 255, 255, 0)`
|
||||||
<HeaderLeft icon='chevron-left' onPress={() => navigation.goBack()} />
|
},
|
||||||
)
|
headerCenter: () => null,
|
||||||
})}
|
headerLeft: () => (
|
||||||
|
<HeaderLeft
|
||||||
|
icon='chevron-left'
|
||||||
|
onPress={() => navigation.goBack()}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>,
|
/>,
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
key='Screen-Shared-Hashtag'
|
key='Screen-Shared-Hashtag'
|
||||||
|
@ -91,26 +91,53 @@ export const timelineFetch = async (
|
|||||||
return Promise.resolve({ toots: res.body })
|
return Promise.resolve({ toots: res.body })
|
||||||
|
|
||||||
case 'Account_Default':
|
case 'Account_Default':
|
||||||
res = await client({
|
if (pagination && pagination.id) {
|
||||||
method: 'get',
|
if (pagination.direction === 'prev') {
|
||||||
instance: 'local',
|
res = await client({
|
||||||
url: `accounts/${account}/statuses`,
|
method: 'get',
|
||||||
params: {
|
instance: 'local',
|
||||||
pinned: 'true'
|
url: `accounts/${account}/statuses`,
|
||||||
|
params: {
|
||||||
|
pinned: 'true',
|
||||||
|
...params
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return Promise.resolve({ toots: res.body })
|
||||||
|
} else {
|
||||||
|
res = await client({
|
||||||
|
method: 'get',
|
||||||
|
instance: 'local',
|
||||||
|
url: `accounts/${account}/statuses`,
|
||||||
|
params: {
|
||||||
|
exclude_replies: 'true',
|
||||||
|
...params
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return Promise.resolve({ toots: res.body })
|
||||||
}
|
}
|
||||||
})
|
} else {
|
||||||
const pinnedLength = res.body.length
|
res = await client({
|
||||||
let toots: Mastodon.Status[] = res.body
|
method: 'get',
|
||||||
res = await client({
|
instance: 'local',
|
||||||
method: 'get',
|
url: `accounts/${account}/statuses`,
|
||||||
instance: 'local',
|
params: {
|
||||||
url: `accounts/${account}/statuses`,
|
pinned: 'true'
|
||||||
params: {
|
}
|
||||||
exclude_replies: 'true'
|
})
|
||||||
}
|
const pinnedLength = res.body.length
|
||||||
})
|
let toots: Mastodon.Status[] = res.body
|
||||||
toots = uniqBy([...toots, ...res.body], 'id')
|
res = await client({
|
||||||
return Promise.resolve({ toots: toots, pinnedLength })
|
method: 'get',
|
||||||
|
instance: 'local',
|
||||||
|
url: `accounts/${account}/statuses`,
|
||||||
|
params: {
|
||||||
|
exclude_replies: 'true'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
toots = uniqBy([...toots, ...res.body], 'id')
|
||||||
|
return Promise.resolve({ toots: toots, pinnedLength })
|
||||||
|
}
|
||||||
|
break
|
||||||
|
|
||||||
case 'Account_All':
|
case 'Account_All':
|
||||||
res = await client({
|
res = await client({
|
||||||
|
14
yarn.lock
14
yarn.lock
@ -2775,13 +2775,6 @@ expand-brackets@^2.1.4:
|
|||||||
snapdragon "^0.8.1"
|
snapdragon "^0.8.1"
|
||||||
to-regex "^3.0.1"
|
to-regex "^3.0.1"
|
||||||
|
|
||||||
expo-app-loading@^1.0.1:
|
|
||||||
version "1.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/expo-app-loading/-/expo-app-loading-1.0.1.tgz#a2ef73235c5dd0a99c0319ad9d3f4d278237564c"
|
|
||||||
integrity sha512-qgsystchwMHep43YMAMHJAD5bRUikolQpb1d+ySWF4ZFTtNInmLAPqiBcVSey5taRuHD3cAvEijx3wc0W/HfVw==
|
|
||||||
dependencies:
|
|
||||||
expo-splash-screen "0.8.1"
|
|
||||||
|
|
||||||
expo-application@~2.4.1:
|
expo-application@~2.4.1:
|
||||||
version "2.4.1"
|
version "2.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/expo-application/-/expo-application-2.4.1.tgz#f8eb4a3a05a0b8a8f38f2e981f587a815945b685"
|
resolved "https://registry.yarnpkg.com/expo-application/-/expo-application-2.4.1.tgz#f8eb4a3a05a0b8a8f38f2e981f587a815945b685"
|
||||||
@ -2915,7 +2908,7 @@ expo-secure-store@~9.3.0:
|
|||||||
resolved "https://registry.yarnpkg.com/expo-secure-store/-/expo-secure-store-9.3.0.tgz#b716d5d115cc50a34037d1afef84fe4b8ea0745c"
|
resolved "https://registry.yarnpkg.com/expo-secure-store/-/expo-secure-store-9.3.0.tgz#b716d5d115cc50a34037d1afef84fe4b8ea0745c"
|
||||||
integrity sha512-dNhKcoUUn+1kmEfFVxSU7h+YsqODqlExZQJcQgxgeiuCeeDvJWkE10t3jjrO6aNfrdM5i/X2l3oh401EDslWsQ==
|
integrity sha512-dNhKcoUUn+1kmEfFVxSU7h+YsqODqlExZQJcQgxgeiuCeeDvJWkE10t3jjrO6aNfrdM5i/X2l3oh401EDslWsQ==
|
||||||
|
|
||||||
expo-splash-screen@0.8.1, expo-splash-screen@~0.8.1:
|
expo-splash-screen@~0.8.1:
|
||||||
version "0.8.1"
|
version "0.8.1"
|
||||||
resolved "https://registry.yarnpkg.com/expo-splash-screen/-/expo-splash-screen-0.8.1.tgz#de4a018c82af879eeaa9b697013ef37d9567453a"
|
resolved "https://registry.yarnpkg.com/expo-splash-screen/-/expo-splash-screen-0.8.1.tgz#de4a018c82af879eeaa9b697013ef37d9567453a"
|
||||||
integrity sha512-7NQo8OgkfQ4sv4mIHE58fkiQTGyl5dOP70uAUTb5YXC/QUsHIqEpKQ6ZXDpkLU1cbZ32c8Vtok4r2FRIUrzxcg==
|
integrity sha512-7NQo8OgkfQ4sv4mIHE58fkiQTGyl5dOP70uAUTb5YXC/QUsHIqEpKQ6ZXDpkLU1cbZ32c8Vtok4r2FRIUrzxcg==
|
||||||
@ -5305,6 +5298,11 @@ react-native-svg@12.1.0:
|
|||||||
css-select "^2.1.0"
|
css-select "^2.1.0"
|
||||||
css-tree "^1.0.0-alpha.39"
|
css-tree "^1.0.0-alpha.39"
|
||||||
|
|
||||||
|
react-native-tab-view@^2.15.2:
|
||||||
|
version "2.15.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-native-tab-view/-/react-native-tab-view-2.15.2.tgz#4bc7832d33a119306614efee667509672a7ee64e"
|
||||||
|
integrity sha512-2hxLkBnZtEKFDyfvNO5EUywhy3f/EiLOBO8SWqKj4BMBTO0QwnybaPE5MVF00Fhz+VA4+h/iI40Dkrrtq70dGg==
|
||||||
|
|
||||||
react-native-toast-message@^1.3.4:
|
react-native-toast-message@^1.3.4:
|
||||||
version "1.4.1"
|
version "1.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-native-toast-message/-/react-native-toast-message-1.4.1.tgz#8be7b91d8a5405e86179f83e52e56de58bbce25f"
|
resolved "https://registry.yarnpkg.com/react-native-toast-message/-/react-native-toast-message-1.4.1.tgz#8be7b91d8a5405e86179f83e52e56de58bbce25f"
|
||||||
|
Reference in New Issue
Block a user