mirror of
https://github.com/tooot-app/app
synced 2025-02-21 14:20:50 +01:00
Updates
This commit is contained in:
parent
284d6e46e0
commit
2df172d026
2
src/@types/react-navigation.d.ts
vendored
2
src/@types/react-navigation.d.ts
vendored
@ -52,7 +52,7 @@ declare namespace Nav {
|
|||||||
} & SharedStackParamList
|
} & SharedStackParamList
|
||||||
|
|
||||||
type MeStackParamList = {
|
type MeStackParamList = {
|
||||||
'Screen-Me-Root': undefined
|
'Screen-Me-Root': { navigateAway?: 'Screen-Me-Settings-UpdateRemote' }
|
||||||
'Screen-Me-Bookmarks': undefined
|
'Screen-Me-Bookmarks': undefined
|
||||||
'Screen-Me-Conversations': undefined
|
'Screen-Me-Conversations': undefined
|
||||||
'Screen-Me-Favourites': undefined
|
'Screen-Me-Favourites': undefined
|
||||||
|
@ -242,13 +242,6 @@ const Index: React.FC<Props> = ({ localCorrupt }) => {
|
|||||||
}),
|
}),
|
||||||
[localActiveIndex]
|
[localActiveIndex]
|
||||||
)
|
)
|
||||||
const tabScreenNotificationsOptions = useMemo(
|
|
||||||
() => ({
|
|
||||||
tabBarBadge: prevNotification && prevNotification.unread ? '' : undefined
|
|
||||||
}),
|
|
||||||
[theme, prevNotification]
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<StatusBar barStyle={barStyle[mode]} />
|
<StatusBar barStyle={barStyle[mode]} />
|
||||||
@ -280,13 +273,22 @@ const Index: React.FC<Props> = ({ localCorrupt }) => {
|
|||||||
name='Screen-Notifications'
|
name='Screen-Notifications'
|
||||||
component={ScreenNotifications}
|
component={ScreenNotifications}
|
||||||
listeners={tabScreenNotificationsListeners}
|
listeners={tabScreenNotificationsListeners}
|
||||||
options={{
|
options={
|
||||||
|
prevNotification && prevNotification.unread
|
||||||
|
? {
|
||||||
|
tabBarBadge: '',
|
||||||
tabBarBadgeStyle: {
|
tabBarBadgeStyle: {
|
||||||
transform: [{ scale: 0.5 }],
|
transform: [{ scale: 0.5 }],
|
||||||
backgroundColor: theme.red
|
backgroundColor: theme.red
|
||||||
},
|
}
|
||||||
...tabScreenNotificationsOptions
|
}
|
||||||
}}
|
: {
|
||||||
|
tabBarBadgeStyle: {
|
||||||
|
transform: [{ scale: 0.5 }],
|
||||||
|
backgroundColor: theme.red
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<Tab.Screen name='Screen-Me' component={ScreenMe} />
|
<Tab.Screen name='Screen-Me' component={ScreenMe} />
|
||||||
</Tab.Navigator>
|
</Tab.Navigator>
|
||||||
|
61
src/components/CustomRefreshControl.tsx
Normal file
61
src/components/CustomRefreshControl.tsx
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||||
|
import React, { useRef } from 'react'
|
||||||
|
import { RefreshControl } from 'react-native'
|
||||||
|
import { InfiniteData, useQueryClient } from 'react-query'
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
queryKey: QueryKeyTimeline
|
||||||
|
isFetchingPreviousPage: boolean
|
||||||
|
isFetching: boolean
|
||||||
|
fetchPreviousPage: () => void
|
||||||
|
refetch: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const CustomRefreshControl = React.memo(
|
||||||
|
({
|
||||||
|
queryKey,
|
||||||
|
isFetchingPreviousPage,
|
||||||
|
isFetching,
|
||||||
|
fetchPreviousPage,
|
||||||
|
refetch
|
||||||
|
}: Props) => {
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
const refreshCount = useRef(0)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RefreshControl
|
||||||
|
refreshing={
|
||||||
|
refreshCount.current < 2 ? isFetchingPreviousPage : isFetching
|
||||||
|
}
|
||||||
|
onRefresh={async () => {
|
||||||
|
if (refreshCount.current < 2) {
|
||||||
|
await fetchPreviousPage()
|
||||||
|
refreshCount.current++
|
||||||
|
} else {
|
||||||
|
queryClient.setQueryData<InfiniteData<any> | undefined>(
|
||||||
|
queryKey,
|
||||||
|
data => {
|
||||||
|
if (data) {
|
||||||
|
return {
|
||||||
|
pages: data.pages.slice(1),
|
||||||
|
pageParams: data.pageParams.slice(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
await refetch()
|
||||||
|
refreshCount.current = 0
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
(prev, next) => {
|
||||||
|
let skipUpdate = true
|
||||||
|
skipUpdate = prev.isFetchingPreviousPage === next.isFetchingPreviousPage
|
||||||
|
skipUpdate = prev.isFetching === next.isFetching
|
||||||
|
return skipUpdate
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
export default CustomRefreshControl
|
@ -3,16 +3,12 @@ import openLink from '@components/openLink'
|
|||||||
import ParseEmojis from '@components/Parse/Emojis'
|
import ParseEmojis from '@components/Parse/Emojis'
|
||||||
import { useNavigation, useRoute } from '@react-navigation/native'
|
import { useNavigation, useRoute } from '@react-navigation/native'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
|
import layoutAnimation from '@utils/styles/layoutAnimation'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import { LinearGradient } from 'expo-linear-gradient'
|
import { LinearGradient } from 'expo-linear-gradient'
|
||||||
import React, { useCallback, useState } from 'react'
|
import React, { useCallback, useState } from 'react'
|
||||||
import { Pressable, Text, View } from 'react-native'
|
import { Pressable, Text, View } from 'react-native'
|
||||||
import HTMLView from 'react-native-htmlview'
|
import HTMLView from 'react-native-htmlview'
|
||||||
import Animated, {
|
|
||||||
useAnimatedStyle,
|
|
||||||
useDerivedValue,
|
|
||||||
withTiming
|
|
||||||
} from 'react-native-reanimated'
|
|
||||||
|
|
||||||
// Prevent going to the same hashtag multiple times
|
// Prevent going to the same hashtag multiple times
|
||||||
const renderNode = ({
|
const renderNode = ({
|
||||||
@ -197,70 +193,36 @@ const ParseHTML: React.FC<Props> = ({
|
|||||||
({ children }) => {
|
({ children }) => {
|
||||||
const lineHeight = StyleConstants.Font.LineHeight[size]
|
const lineHeight = StyleConstants.Font.LineHeight[size]
|
||||||
|
|
||||||
const [lines, setLines] = useState<number | undefined>(undefined)
|
|
||||||
const [heightOriginal, setHeightOriginal] = useState<number>()
|
|
||||||
const [heightTruncated, setHeightTruncated] = useState<number>()
|
|
||||||
const [expandAllow, setExpandAllow] = useState(false)
|
const [expandAllow, setExpandAllow] = useState(false)
|
||||||
const [expanded, setExpanded] = useState(false)
|
const [expanded, setExpanded] = useState(false)
|
||||||
|
|
||||||
const viewHeight = useDerivedValue(() => {
|
const onTextLayout = useCallback(({ nativeEvent }) => {
|
||||||
if (expandAllow) {
|
if (
|
||||||
if (expanded) {
|
nativeEvent.lines &&
|
||||||
return heightOriginal as number
|
nativeEvent.lines.length === numberOfLines + 1
|
||||||
} else {
|
) {
|
||||||
return heightTruncated as number
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return heightOriginal as number
|
|
||||||
}
|
|
||||||
}, [heightOriginal, heightTruncated, expandAllow, expanded])
|
|
||||||
const ViewHeight = useAnimatedStyle(() => {
|
|
||||||
return {
|
|
||||||
height: expandAllow
|
|
||||||
? expanded
|
|
||||||
? withTiming(viewHeight.value)
|
|
||||||
: withTiming(viewHeight.value)
|
|
||||||
: viewHeight.value
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const onLayout = useCallback(
|
|
||||||
({ nativeEvent }) => {
|
|
||||||
if (!heightOriginal) {
|
|
||||||
setHeightOriginal(nativeEvent.layout.height)
|
|
||||||
setLines(numberOfLines === 0 ? 1 : numberOfLines)
|
|
||||||
} else {
|
|
||||||
if (!heightTruncated) {
|
|
||||||
setHeightTruncated(nativeEvent.layout.height)
|
|
||||||
setLines(undefined)
|
|
||||||
} else {
|
|
||||||
if (heightOriginal > heightTruncated) {
|
|
||||||
setExpandAllow(true)
|
setExpandAllow(true)
|
||||||
}
|
}
|
||||||
}
|
}, [])
|
||||||
}
|
|
||||||
},
|
|
||||||
[heightOriginal, heightTruncated]
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View>
|
<View>
|
||||||
<Animated.View style={[ViewHeight, { overflow: 'hidden' }]}>
|
<Text
|
||||||
<Animated.Text
|
|
||||||
children={children}
|
children={children}
|
||||||
onLayout={onLayout}
|
onTextLayout={onTextLayout}
|
||||||
numberOfLines={lines}
|
numberOfLines={expanded ? 999 : numberOfLines + 1}
|
||||||
style={{
|
style={{
|
||||||
...StyleConstants.FontStyle[size],
|
...StyleConstants.FontStyle[size],
|
||||||
color: theme.primary,
|
color: theme.primary
|
||||||
height: expandAllow ? heightOriginal : undefined
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Animated.View>
|
|
||||||
{expandAllow ? (
|
{expandAllow ? (
|
||||||
<Pressable
|
<Pressable
|
||||||
onPress={() => setExpanded(!expanded)}
|
onPress={() => {
|
||||||
style={{ marginTop: expanded ? 0 : -lineHeight * 1.25 }}
|
layoutAnimation()
|
||||||
|
setExpanded(!expanded)
|
||||||
|
}}
|
||||||
|
style={{ marginTop: expanded ? 0 : -lineHeight * 2.25 }}
|
||||||
>
|
>
|
||||||
<LinearGradient
|
<LinearGradient
|
||||||
colors={[
|
colors={[
|
||||||
@ -301,4 +263,5 @@ const ParseHTML: React.FC<Props> = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ParseHTML
|
// export default ParseHTML
|
||||||
|
export default React.memo(ParseHTML, () => true)
|
||||||
|
@ -9,18 +9,12 @@ import { useScrollToTop } from '@react-navigation/native'
|
|||||||
import { localUpdateNotification } from '@utils/slices/instancesSlice'
|
import { localUpdateNotification } from '@utils/slices/instancesSlice'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import React, { useCallback, useEffect, useMemo, useRef } from 'react'
|
import React, { useCallback, useEffect, useMemo, useRef } from 'react'
|
||||||
import { RefreshControl, StyleSheet } from 'react-native'
|
import { StyleSheet } from 'react-native'
|
||||||
import { FlatList } from 'react-native-gesture-handler'
|
import { FlatList } from 'react-native-gesture-handler'
|
||||||
import { InfiniteData } from 'react-query'
|
|
||||||
import { useDispatch } from 'react-redux'
|
import { useDispatch } from 'react-redux'
|
||||||
import { QueryKeyTimeline, useTimelineQuery } from '@utils/queryHooks/timeline'
|
import { QueryKeyTimeline, useTimelineQuery } from '@utils/queryHooks/timeline'
|
||||||
import { findIndex } from 'lodash'
|
import { findIndex } from 'lodash'
|
||||||
|
import CustomRefreshControl from '@components/CustomRefreshControl'
|
||||||
type TimelineData =
|
|
||||||
| InfiniteData<{
|
|
||||||
toots: Mastodon.Status[]
|
|
||||||
}>
|
|
||||||
| undefined
|
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
page: App.Pages
|
page: App.Pages
|
||||||
@ -54,6 +48,7 @@ const Timeline: React.FC<Props> = ({
|
|||||||
data,
|
data,
|
||||||
refetch,
|
refetch,
|
||||||
isSuccess,
|
isSuccess,
|
||||||
|
isFetching,
|
||||||
hasPreviousPage,
|
hasPreviousPage,
|
||||||
fetchPreviousPage,
|
fetchPreviousPage,
|
||||||
isFetchingPreviousPage,
|
isFetchingPreviousPage,
|
||||||
@ -122,10 +117,6 @@ const Timeline: React.FC<Props> = ({
|
|||||||
case 'Notifications':
|
case 'Notifications':
|
||||||
return <TimelineNotifications notification={item} queryKey={queryKey} />
|
return <TimelineNotifications notification={item} queryKey={queryKey} />
|
||||||
default:
|
default:
|
||||||
// if (item.poll) {
|
|
||||||
// console.log('Timeline')
|
|
||||||
// console.log(item.poll)
|
|
||||||
// }
|
|
||||||
return (
|
return (
|
||||||
<TimelineDefault
|
<TimelineDefault
|
||||||
item={item}
|
item={item}
|
||||||
@ -167,12 +158,15 @@ const Timeline: React.FC<Props> = ({
|
|||||||
)
|
)
|
||||||
const refreshControl = useMemo(
|
const refreshControl = useMemo(
|
||||||
() => (
|
() => (
|
||||||
<RefreshControl
|
<CustomRefreshControl
|
||||||
refreshing={isFetchingPreviousPage}
|
queryKey={queryKey}
|
||||||
onRefresh={() => fetchPreviousPage()}
|
isFetchingPreviousPage={isFetchingNextPage}
|
||||||
|
isFetching={isFetching}
|
||||||
|
fetchPreviousPage={fetchPreviousPage}
|
||||||
|
refetch={refetch}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
[isFetchingPreviousPage]
|
[isFetchingPreviousPage, isFetching]
|
||||||
)
|
)
|
||||||
const onScrollToIndexFailed = useCallback(error => {
|
const onScrollToIndexFailed = useCallback(error => {
|
||||||
const offset = error.averageItemLength * error.index
|
const offset = error.averageItemLength * error.index
|
||||||
@ -219,6 +213,4 @@ const styles = StyleSheet.create({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Timeline.whyDidYouRender = true
|
|
||||||
|
|
||||||
export default Timeline
|
export default Timeline
|
||||||
|
@ -15,7 +15,7 @@ import { Pressable, StyleSheet, View } from 'react-native'
|
|||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
item: Mastodon.Status
|
item: Mastodon.Status & { isPinned?: boolean }
|
||||||
queryKey?: QueryKeyTimeline
|
queryKey?: QueryKeyTimeline
|
||||||
highlighted?: boolean
|
highlighted?: boolean
|
||||||
disableDetails?: boolean
|
disableDetails?: boolean
|
||||||
@ -61,7 +61,6 @@ const TimelineDefault: React.FC<Props> = ({
|
|||||||
<TimelineHeaderDefault
|
<TimelineHeaderDefault
|
||||||
queryKey={disableOnPress ? undefined : queryKey}
|
queryKey={disableOnPress ? undefined : queryKey}
|
||||||
status={actualStatus}
|
status={actualStatus}
|
||||||
sameAccount={actualStatus.account.id === localAccount?.id}
|
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
@ -17,7 +17,10 @@ const TimelineHeader = React.memo(
|
|||||||
<Text
|
<Text
|
||||||
style={{ color: theme.blue }}
|
style={{ color: theme.blue }}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
navigation.navigate('Screen-Me')
|
navigation.navigate('Screen-Me', {
|
||||||
|
screen: 'Screen-Me-Root',
|
||||||
|
params: { navigateAway: 'Screen-Me-Settings-UpdateRemote' }
|
||||||
|
})
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
前往设置{' '}
|
前往设置{' '}
|
||||||
|
@ -61,7 +61,10 @@ const TimelineNotifications: React.FC<Props> = ({
|
|||||||
>
|
>
|
||||||
<View style={styles.header}>
|
<View style={styles.header}>
|
||||||
<TimelineAvatar queryKey={queryKey} account={actualAccount} />
|
<TimelineAvatar queryKey={queryKey} account={actualAccount} />
|
||||||
<TimelineHeaderNotification notification={notification} />
|
<TimelineHeaderNotification
|
||||||
|
queryKey={queryKey}
|
||||||
|
notification={notification}
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{notification.status ? (
|
{notification.status ? (
|
||||||
|
@ -32,6 +32,17 @@ const TimelineActions: React.FC<Props> = ({ queryKey, status, reblog }) => {
|
|||||||
const mutation = useTimelineMutation({
|
const mutation = useTimelineMutation({
|
||||||
queryClient,
|
queryClient,
|
||||||
onMutate: true,
|
onMutate: true,
|
||||||
|
onSuccess: (_, params) => {
|
||||||
|
const theParams = params as MutationVarsTimelineUpdateStatusProperty
|
||||||
|
if (
|
||||||
|
(queryKey[1].page === 'Bookmarks' &&
|
||||||
|
theParams.payload.property === 'bookmarked') ||
|
||||||
|
(queryKey[1].page === 'Favourites' &&
|
||||||
|
theParams.payload.property === 'favourited')
|
||||||
|
) {
|
||||||
|
queryClient.invalidateQueries(queryKey)
|
||||||
|
}
|
||||||
|
},
|
||||||
onError: (err: any, params, oldData) => {
|
onError: (err: any, params, oldData) => {
|
||||||
const correctParam = params as MutationVarsTimelineUpdateStatusProperty
|
const correctParam = params as MutationVarsTimelineUpdateStatusProperty
|
||||||
haptics('Error')
|
haptics('Error')
|
||||||
|
@ -15,10 +15,8 @@ const TimelineCard: React.FC<Props> = ({ card }) => {
|
|||||||
|
|
||||||
const [imageLoaded, setImageLoaded] = useState(false)
|
const [imageLoaded, setImageLoaded] = useState(false)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const preFetch = () => Image.getSize(card.image, () => setImageLoaded(true))
|
|
||||||
|
|
||||||
if (card.image) {
|
if (card.image) {
|
||||||
preFetch()
|
Image.getSize(card.image, () => setImageLoaded(true))
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
const cardVisual = useMemo(() => {
|
const cardVisual = useMemo(() => {
|
||||||
|
@ -15,7 +15,7 @@ export interface Props {
|
|||||||
setBottomSheetVisible: React.Dispatch<React.SetStateAction<boolean>>
|
setBottomSheetVisible: React.Dispatch<React.SetStateAction<boolean>>
|
||||||
}
|
}
|
||||||
|
|
||||||
const HeaderDefaultActionsAccount: React.FC<Props> = ({
|
const HeaderActionsAccount: React.FC<Props> = ({
|
||||||
queryKey,
|
queryKey,
|
||||||
account,
|
account,
|
||||||
setBottomSheetVisible
|
setBottomSheetVisible
|
||||||
@ -121,4 +121,4 @@ const HeaderDefaultActionsAccount: React.FC<Props> = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default HeaderDefaultActionsAccount
|
export default HeaderActionsAccount
|
@ -17,7 +17,7 @@ export interface Props {
|
|||||||
setBottomSheetVisible: React.Dispatch<React.SetStateAction<boolean>>
|
setBottomSheetVisible: React.Dispatch<React.SetStateAction<boolean>>
|
||||||
}
|
}
|
||||||
|
|
||||||
const HeaderDefaultActionsDomain: React.FC<Props> = ({
|
const HeaderActionsDomain: React.FC<Props> = ({
|
||||||
queryKey,
|
queryKey,
|
||||||
domain,
|
domain,
|
||||||
setBottomSheetVisible
|
setBottomSheetVisible
|
||||||
@ -77,4 +77,4 @@ const HeaderDefaultActionsDomain: React.FC<Props> = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default HeaderDefaultActionsDomain
|
export default HeaderActionsDomain
|
@ -17,7 +17,7 @@ export interface Props {
|
|||||||
setBottomSheetVisible: React.Dispatch<React.SetStateAction<boolean>>
|
setBottomSheetVisible: React.Dispatch<React.SetStateAction<boolean>>
|
||||||
}
|
}
|
||||||
|
|
||||||
const HeaderDefaultActionsStatus: React.FC<Props> = ({
|
const HeaderActionsStatus: React.FC<Props> = ({
|
||||||
queryKey,
|
queryKey,
|
||||||
status,
|
status,
|
||||||
setBottomSheetVisible
|
setBottomSheetVisible
|
||||||
@ -156,4 +156,4 @@ const HeaderDefaultActionsStatus: React.FC<Props> = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default HeaderDefaultActionsStatus
|
export default HeaderActionsStatus
|
@ -0,0 +1,93 @@
|
|||||||
|
import BottomSheet from '@components/BottomSheet'
|
||||||
|
import Icon from '@components/Icon'
|
||||||
|
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||||
|
import { getLocalAccount, getLocalUrl } from '@utils/slices/instancesSlice'
|
||||||
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
|
import React, { useCallback, useMemo, useState } from 'react'
|
||||||
|
import { Pressable, StyleSheet } from 'react-native'
|
||||||
|
import { useSelector } from 'react-redux'
|
||||||
|
import HeaderActionsAccount from './ActionsAccount'
|
||||||
|
import HeaderActionsDomain from './ActionsDomain'
|
||||||
|
import HeaderActionsStatus from './ActionsStatus'
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
queryKey: QueryKeyTimeline
|
||||||
|
status: Mastodon.Status
|
||||||
|
}
|
||||||
|
|
||||||
|
const HeaderActions = React.memo(
|
||||||
|
({ queryKey, status }: Props) => {
|
||||||
|
const { theme } = useTheme()
|
||||||
|
|
||||||
|
const localAccount = useSelector(getLocalAccount)
|
||||||
|
const sameAccount = localAccount?.id === status.account.id
|
||||||
|
|
||||||
|
const localDomain = useSelector(getLocalUrl)
|
||||||
|
const statusDomain = status.uri
|
||||||
|
? status.uri.split(new RegExp(/\/\/(.*?)\//))[1]
|
||||||
|
: ''
|
||||||
|
const sameDomain = localDomain === statusDomain
|
||||||
|
|
||||||
|
const [modalVisible, setBottomSheetVisible] = useState(false)
|
||||||
|
const onPress = useCallback(() => setBottomSheetVisible(true), [])
|
||||||
|
const children = useMemo(
|
||||||
|
() => (
|
||||||
|
<Icon
|
||||||
|
name='MoreHorizontal'
|
||||||
|
color={theme.secondary}
|
||||||
|
size={StyleConstants.Font.Size.M + 2}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Pressable style={styles.base} onPress={onPress} children={children} />
|
||||||
|
{modalVisible && (
|
||||||
|
<BottomSheet
|
||||||
|
visible={modalVisible}
|
||||||
|
handleDismiss={() => setBottomSheetVisible(false)}
|
||||||
|
>
|
||||||
|
{!sameAccount && (
|
||||||
|
<HeaderActionsAccount
|
||||||
|
queryKey={queryKey}
|
||||||
|
account={status.account}
|
||||||
|
setBottomSheetVisible={setBottomSheetVisible}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{sameAccount && (
|
||||||
|
<HeaderActionsStatus
|
||||||
|
queryKey={queryKey}
|
||||||
|
status={status}
|
||||||
|
setBottomSheetVisible={setBottomSheetVisible}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!sameDomain && (
|
||||||
|
<HeaderActionsDomain
|
||||||
|
queryKey={queryKey}
|
||||||
|
domain={statusDomain}
|
||||||
|
setBottomSheetVisible={setBottomSheetVisible}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</BottomSheet>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
() => true
|
||||||
|
)
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
base: {
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'center',
|
||||||
|
paddingBottom: StyleConstants.Spacing.S
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default HeaderActions
|
@ -1,53 +1,20 @@
|
|||||||
import BottomSheet from '@components/BottomSheet'
|
|
||||||
import Icon from '@components/Icon'
|
|
||||||
import { getLocalUrl } from '@utils/slices/instancesSlice'
|
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import React from 'react'
|
||||||
import HeaderDefaultActionsAccount from '@components/Timelines/Timeline/Shared/HeaderDefault/ActionsAccount'
|
import { StyleSheet, View } from 'react-native'
|
||||||
import HeaderDefaultActionsDomain from '@components/Timelines/Timeline/Shared/HeaderDefault/ActionsDomain'
|
|
||||||
import HeaderDefaultActionsStatus from '@components/Timelines/Timeline/Shared/HeaderDefault/ActionsStatus'
|
|
||||||
import React, { useCallback, useMemo, useState } from 'react'
|
|
||||||
import { Pressable, StyleSheet, View } from 'react-native'
|
|
||||||
import { useSelector } from 'react-redux'
|
|
||||||
import HeaderSharedAccount from './HeaderShared/Account'
|
import HeaderSharedAccount from './HeaderShared/Account'
|
||||||
import HeaderSharedApplication from './HeaderShared/Application'
|
import HeaderSharedApplication from './HeaderShared/Application'
|
||||||
import HeaderSharedCreated from './HeaderShared/Created'
|
import HeaderSharedCreated from './HeaderShared/Created'
|
||||||
import HeaderSharedVisibility from './HeaderShared/Visibility'
|
import HeaderSharedVisibility from './HeaderShared/Visibility'
|
||||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||||
import HeaderSharedMuted from './HeaderShared/Muted'
|
import HeaderSharedMuted from './HeaderShared/Muted'
|
||||||
|
import HeaderActions from './HeaderActions/Root'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
queryKey?: QueryKeyTimeline
|
queryKey?: QueryKeyTimeline
|
||||||
status: Mastodon.Status
|
status: Mastodon.Status
|
||||||
sameAccount: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const TimelineHeaderDefault: React.FC<Props> = ({
|
const TimelineHeaderDefault: React.FC<Props> = ({ queryKey, status }) => {
|
||||||
queryKey,
|
|
||||||
status,
|
|
||||||
sameAccount
|
|
||||||
}) => {
|
|
||||||
const domain = status.uri
|
|
||||||
? status.uri.split(new RegExp(/\/\/(.*?)\//))[1]
|
|
||||||
: ''
|
|
||||||
const { theme } = useTheme()
|
|
||||||
|
|
||||||
const localDomain = useSelector(getLocalUrl)
|
|
||||||
const [modalVisible, setBottomSheetVisible] = useState(false)
|
|
||||||
|
|
||||||
const onPressAction = useCallback(() => setBottomSheetVisible(true), [])
|
|
||||||
|
|
||||||
const pressableAction = useMemo(
|
|
||||||
() => (
|
|
||||||
<Icon
|
|
||||||
name='MoreHorizontal'
|
|
||||||
color={theme.secondary}
|
|
||||||
size={StyleConstants.Font.Size.M + 2}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
[]
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.base}>
|
<View style={styles.base}>
|
||||||
<View style={styles.accountAndMeta}>
|
<View style={styles.accountAndMeta}>
|
||||||
@ -60,44 +27,7 @@ const TimelineHeaderDefault: React.FC<Props> = ({
|
|||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{queryKey && (
|
{queryKey ? <HeaderActions queryKey={queryKey} status={status} /> : null}
|
||||||
<Pressable
|
|
||||||
style={styles.action}
|
|
||||||
onPress={onPressAction}
|
|
||||||
children={pressableAction}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{queryKey && modalVisible && (
|
|
||||||
<BottomSheet
|
|
||||||
visible={modalVisible}
|
|
||||||
handleDismiss={() => setBottomSheetVisible(false)}
|
|
||||||
>
|
|
||||||
{!sameAccount && (
|
|
||||||
<HeaderDefaultActionsAccount
|
|
||||||
queryKey={queryKey}
|
|
||||||
account={status.account}
|
|
||||||
setBottomSheetVisible={setBottomSheetVisible}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{sameAccount && (
|
|
||||||
<HeaderDefaultActionsStatus
|
|
||||||
queryKey={queryKey}
|
|
||||||
status={status}
|
|
||||||
setBottomSheetVisible={setBottomSheetVisible}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{domain !== localDomain && (
|
|
||||||
<HeaderDefaultActionsDomain
|
|
||||||
queryKey={queryKey}
|
|
||||||
domain={domain}
|
|
||||||
setBottomSheetVisible={setBottomSheetVisible}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</BottomSheet>
|
|
||||||
)}
|
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -118,12 +48,6 @@ const styles = StyleSheet.create({
|
|||||||
},
|
},
|
||||||
created_at: {
|
created_at: {
|
||||||
...StyleConstants.FontStyle.S
|
...StyleConstants.FontStyle.S
|
||||||
},
|
|
||||||
action: {
|
|
||||||
flex: 1,
|
|
||||||
flexDirection: 'row',
|
|
||||||
justifyContent: 'center',
|
|
||||||
paddingBottom: StyleConstants.Spacing.S
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { RelationshipOutgoing } from '@components/Relationship'
|
import { RelationshipOutgoing } from '@components/Relationship'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import React from 'react'
|
import React, { useMemo } from 'react'
|
||||||
import { StyleSheet, View } from 'react-native'
|
import { StyleSheet, View } from 'react-native'
|
||||||
import HeaderSharedAccount from './HeaderShared/Account'
|
import HeaderSharedAccount from './HeaderShared/Account'
|
||||||
import HeaderSharedApplication from './HeaderShared/Application'
|
import HeaderSharedApplication from './HeaderShared/Application'
|
||||||
@ -8,15 +8,41 @@ import HeaderSharedCreated from './HeaderShared/Created'
|
|||||||
import HeaderSharedVisibility from './HeaderShared/Visibility'
|
import HeaderSharedVisibility from './HeaderShared/Visibility'
|
||||||
import RelationshipIncoming from '@root/components/Relationship/Incoming'
|
import RelationshipIncoming from '@root/components/Relationship/Incoming'
|
||||||
import HeaderSharedMuted from './HeaderShared/Muted'
|
import HeaderSharedMuted from './HeaderShared/Muted'
|
||||||
|
import HeaderActions from './HeaderActions/Root'
|
||||||
|
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
|
queryKey: QueryKeyTimeline
|
||||||
notification: Mastodon.Notification
|
notification: Mastodon.Notification
|
||||||
}
|
}
|
||||||
|
|
||||||
const TimelineHeaderNotification: React.FC<Props> = ({ notification }) => {
|
const TimelineHeaderNotification: React.FC<Props> = ({
|
||||||
|
queryKey,
|
||||||
|
notification
|
||||||
|
}) => {
|
||||||
|
const actions = useMemo(() => {
|
||||||
|
switch (notification.type) {
|
||||||
|
case 'follow':
|
||||||
|
return <RelationshipOutgoing id={notification.account.id} />
|
||||||
|
case 'follow_request':
|
||||||
|
return <RelationshipIncoming id={notification.account.id} />
|
||||||
|
default:
|
||||||
|
return notification.status ? (
|
||||||
|
<HeaderActions queryKey={queryKey} status={notification.status} />
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
}, [notification.type])
|
||||||
return (
|
return (
|
||||||
<View style={styles.base}>
|
<View style={styles.base}>
|
||||||
<View style={styles.accountAndMeta}>
|
<View
|
||||||
|
style={{
|
||||||
|
flex:
|
||||||
|
notification.type === 'follow' ||
|
||||||
|
notification.type === 'follow_request'
|
||||||
|
? 1
|
||||||
|
: 4
|
||||||
|
}}
|
||||||
|
>
|
||||||
<HeaderSharedAccount
|
<HeaderSharedAccount
|
||||||
account={
|
account={
|
||||||
notification.status
|
notification.status
|
||||||
@ -38,16 +64,17 @@ const TimelineHeaderNotification: React.FC<Props> = ({ notification }) => {
|
|||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{notification.type === 'follow' && (
|
<View
|
||||||
<View style={styles.relationship}>
|
style={[
|
||||||
<RelationshipOutgoing id={notification.account.id} />
|
styles.relationship,
|
||||||
|
notification.type === 'follow' ||
|
||||||
|
notification.type === 'follow_request'
|
||||||
|
? { flexShrink: 1 }
|
||||||
|
: { flex: 1 }
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{actions}
|
||||||
</View>
|
</View>
|
||||||
)}
|
|
||||||
{notification.type === 'follow_request' && (
|
|
||||||
<View style={styles.relationship}>
|
|
||||||
<RelationshipIncoming id={notification.account.id} />
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -55,13 +82,7 @@ const TimelineHeaderNotification: React.FC<Props> = ({ notification }) => {
|
|||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
base: {
|
base: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
flexDirection: 'row',
|
flexDirection: 'row'
|
||||||
justifyContent: 'space-between',
|
|
||||||
alignItems: 'flex-start'
|
|
||||||
},
|
|
||||||
accountAndMeta: {
|
|
||||||
flex: 1,
|
|
||||||
flexGrow: 1
|
|
||||||
},
|
},
|
||||||
meta: {
|
meta: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
@ -70,7 +91,6 @@ const styles = StyleSheet.create({
|
|||||||
marginBottom: StyleConstants.Spacing.S
|
marginBottom: StyleConstants.Spacing.S
|
||||||
},
|
},
|
||||||
relationship: {
|
relationship: {
|
||||||
flexShrink: 1,
|
|
||||||
marginLeft: StyleConstants.Spacing.M
|
marginLeft: StyleConstants.Spacing.M
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -8,15 +8,29 @@ import accountReducer from '@screens/Shared/Account/utils/reducer'
|
|||||||
import accountInitialState from '@screens/Shared/Account/utils/initialState'
|
import accountInitialState from '@screens/Shared/Account/utils/initialState'
|
||||||
import AccountContext from '@screens/Shared/Account/utils/createContext'
|
import AccountContext from '@screens/Shared/Account/utils/createContext'
|
||||||
import { getLocalActiveIndex } from '@utils/slices/instancesSlice'
|
import { getLocalActiveIndex } from '@utils/slices/instancesSlice'
|
||||||
import React, { useReducer, useRef, useState } from 'react'
|
import React, { useEffect, useReducer, useRef, useState } from 'react'
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
import Animated, {
|
import Animated, {
|
||||||
useAnimatedScrollHandler,
|
useAnimatedScrollHandler,
|
||||||
useSharedValue
|
useSharedValue
|
||||||
} from 'react-native-reanimated'
|
} from 'react-native-reanimated'
|
||||||
import ComponentInstance from '@components/Instance'
|
import ComponentInstance from '@components/Instance'
|
||||||
|
import { StackScreenProps } from '@react-navigation/stack'
|
||||||
|
|
||||||
const ScreenMeRoot: React.FC = () => {
|
const ScreenMeRoot: React.FC<StackScreenProps<
|
||||||
|
Nav.MeStackParamList,
|
||||||
|
'Screen-Me-Root'
|
||||||
|
>> = ({
|
||||||
|
route: {
|
||||||
|
params: { navigateAway }
|
||||||
|
},
|
||||||
|
navigation
|
||||||
|
}) => {
|
||||||
|
useEffect(() => {
|
||||||
|
if (navigateAway) {
|
||||||
|
navigation.navigate(navigateAway)
|
||||||
|
}
|
||||||
|
}, [navigateAway])
|
||||||
const localActiveIndex = useSelector(getLocalActiveIndex)
|
const localActiveIndex = useSelector(getLocalActiveIndex)
|
||||||
|
|
||||||
const scrollRef = useRef<Animated.ScrollView>(null)
|
const scrollRef = useRef<Animated.ScrollView>(null)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import BottomSheet from '@components/BottomSheet'
|
import BottomSheet from '@components/BottomSheet'
|
||||||
import { HeaderRight } from '@components/Header'
|
import { HeaderRight } from '@components/Header'
|
||||||
import HeaderDefaultActionsAccount from '@components/Timelines/Timeline/Shared/HeaderDefault/ActionsAccount'
|
import HeaderActionsAccount from '@components/Timelines/Timeline/Shared/HeaderActions/ActionsAccount'
|
||||||
import { useAccountQuery } from '@utils/queryHooks/account'
|
import { useAccountQuery } from '@utils/queryHooks/account'
|
||||||
import { getLocalAccount } from '@utils/slices/instancesSlice'
|
import { getLocalAccount } from '@utils/slices/instancesSlice'
|
||||||
import React, { useEffect, useReducer, useState } from 'react'
|
import React, { useEffect, useReducer, useState } from 'react'
|
||||||
@ -77,7 +77,7 @@ const ScreenSharedAccount: React.FC<SharedAccountProp> = ({
|
|||||||
>
|
>
|
||||||
{/* 添加到列表 */}
|
{/* 添加到列表 */}
|
||||||
{localAccount?.id !== account.id && (
|
{localAccount?.id !== account.id && (
|
||||||
<HeaderDefaultActionsAccount
|
<HeaderActionsAccount
|
||||||
account={account}
|
account={account}
|
||||||
setBottomSheetVisible={setBottomSheetVisible}
|
setBottomSheetVisible={setBottomSheetVisible}
|
||||||
/>
|
/>
|
||||||
|
@ -381,7 +381,8 @@ const useTimelineMutation = ({
|
|||||||
onSettled,
|
onSettled,
|
||||||
...(typeof onSuccess === 'function'
|
...(typeof onSuccess === 'function'
|
||||||
? { onSuccess }
|
? { onSuccess }
|
||||||
: {
|
: onSuccess
|
||||||
|
? {
|
||||||
onSuccess: (data, params) => {
|
onSuccess: (data, params) => {
|
||||||
queryClient.cancelQueries(params.queryKey)
|
queryClient.cancelQueries(params.queryKey)
|
||||||
|
|
||||||
@ -397,7 +398,8 @@ const useTimelineMutation = ({
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}),
|
}
|
||||||
|
: undefined),
|
||||||
...(onMutate && {
|
...(onMutate && {
|
||||||
onMutate: params => {
|
onMutate: params => {
|
||||||
queryClient.cancelQueries(params.queryKey)
|
queryClient.cancelQueries(params.queryKey)
|
||||||
|
@ -11,7 +11,6 @@ const updateStatus = ({
|
|||||||
}) => {
|
}) => {
|
||||||
switch (payload.property) {
|
switch (payload.property) {
|
||||||
case 'poll':
|
case 'poll':
|
||||||
console.log(payload.data)
|
|
||||||
if (reblog) {
|
if (reblog) {
|
||||||
item.reblog!.poll = payload.data
|
item.reblog!.poll = payload.data
|
||||||
} else {
|
} else {
|
||||||
|
@ -19,7 +19,7 @@ export const ManageThemeContext = createContext<ContextType>({
|
|||||||
|
|
||||||
export const useTheme = () => useContext(ManageThemeContext)
|
export const useTheme = () => useContext(ManageThemeContext)
|
||||||
|
|
||||||
const useColorSchemeDelay = (delay = 250) => {
|
const useColorSchemeDelay = (delay = 500) => {
|
||||||
const [colorScheme, setColorScheme] = React.useState(
|
const [colorScheme, setColorScheme] = React.useState(
|
||||||
Appearance.getColorScheme()
|
Appearance.getColorScheme()
|
||||||
)
|
)
|
||||||
|
@ -23,15 +23,15 @@ const themeColors: {
|
|||||||
} = {
|
} = {
|
||||||
primary: {
|
primary: {
|
||||||
light: 'rgb(18, 18, 18)',
|
light: 'rgb(18, 18, 18)',
|
||||||
dark: 'rgb(218, 218, 218)'
|
dark: 'rgb(180, 180, 180)'
|
||||||
},
|
},
|
||||||
primaryOverlay: {
|
primaryOverlay: {
|
||||||
light: 'rgb(250, 250, 250)',
|
light: 'rgb(250, 250, 250)',
|
||||||
dark: 'rgb(218, 218, 218)'
|
dark: 'rgb(180, 180, 180)'
|
||||||
},
|
},
|
||||||
secondary: {
|
secondary: {
|
||||||
light: 'rgb(135, 135, 135)',
|
light: 'rgb(135, 135, 135)',
|
||||||
dark: 'rgb(135, 135, 135)'
|
dark: 'rgb(130, 130, 130)'
|
||||||
},
|
},
|
||||||
disabled: {
|
disabled: {
|
||||||
light: 'rgb(200, 200, 200)',
|
light: 'rgb(200, 200, 200)',
|
||||||
@ -48,30 +48,30 @@ const themeColors: {
|
|||||||
|
|
||||||
background: {
|
background: {
|
||||||
light: 'rgb(250, 250, 250)',
|
light: 'rgb(250, 250, 250)',
|
||||||
dark: 'rgb(18, 18, 18)'
|
dark: 'rgb(25, 25, 25)'
|
||||||
},
|
},
|
||||||
backgroundGradientStart: {
|
backgroundGradientStart: {
|
||||||
light: 'rgba(250, 250, 250, 0.5)',
|
light: 'rgba(250, 250, 250, 0.5)',
|
||||||
dark: 'rgba(18, 18, 18, 0.5)'
|
dark: 'rgba(25, 25, 25, 0.5)'
|
||||||
},
|
},
|
||||||
backgroundGradientEnd: {
|
backgroundGradientEnd: {
|
||||||
light: 'rgba(250, 250, 250, 1)',
|
light: 'rgba(250, 250, 250, 1)',
|
||||||
dark: 'rgba(18, 18, 18, 1)'
|
dark: 'rgba(25, 25, 25, 1)'
|
||||||
},
|
},
|
||||||
backgroundOverlay: {
|
backgroundOverlay: {
|
||||||
light: 'rgba(18, 18, 18, 0.5)',
|
light: 'rgba(25, 25, 25, 0.5)',
|
||||||
dark: 'rgba(0, 0, 0, 0.5)'
|
dark: 'rgba(0, 0, 0, 0.5)'
|
||||||
},
|
},
|
||||||
border: {
|
border: {
|
||||||
light: 'rgba(18, 18, 18, 0.3)',
|
light: 'rgba(25, 25, 25, 0.3)',
|
||||||
dark: 'rgba(255, 255, 255, 0.3)'
|
dark: 'rgba(255, 255, 255, 0.3)'
|
||||||
},
|
},
|
||||||
shimmerDefault: {
|
shimmerDefault: {
|
||||||
light: 'rgba(18, 18, 18, 0.05)',
|
light: 'rgba(25, 25, 25, 0.05)',
|
||||||
dark: 'rgba(250, 250, 250, 0.05)'
|
dark: 'rgba(250, 250, 250, 0.05)'
|
||||||
},
|
},
|
||||||
shimmerHighlight: {
|
shimmerHighlight: {
|
||||||
light: 'rgba(18, 18, 18, 0.15)',
|
light: 'rgba(25, 25, 25, 0.15)',
|
||||||
dark: 'rgba(250, 250, 250, 0.15)'
|
dark: 'rgba(250, 250, 250, 0.15)'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user