mirror of
https://github.com/tooot-app/app
synced 2025-06-05 22:19:13 +02:00
Refine structure
This commit is contained in:
66
src/components/Account.tsx
Normal file
66
src/components/Account.tsx
Normal file
@ -0,0 +1,66 @@
|
||||
import { ParseEmojis } from '@components/Parse'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React from 'react'
|
||||
import { Image, Pressable, StyleSheet, Text, View } from 'react-native'
|
||||
|
||||
export interface Props {
|
||||
account: Mastodon.Account
|
||||
}
|
||||
|
||||
const ComponentAccount: React.FC<Props> = ({ account }) => {
|
||||
const navigation = useNavigation()
|
||||
const { theme } = useTheme()
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
style={[styles.itemDefault, styles.itemAccount]}
|
||||
onPress={() => {
|
||||
navigation.push('Screen-Shared-Account', { account })
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
source={{ uri: account.avatar_static }}
|
||||
style={styles.itemAccountAvatar}
|
||||
/>
|
||||
<View>
|
||||
<Text numberOfLines={1}>
|
||||
<ParseEmojis
|
||||
content={account.display_name || account.username}
|
||||
emojis={account.emojis}
|
||||
size='S'
|
||||
fontBold
|
||||
/>
|
||||
</Text>
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
style={[styles.itemAccountAcct, { color: theme.secondary }]}
|
||||
>
|
||||
@{account.acct}
|
||||
</Text>
|
||||
</View>
|
||||
</Pressable>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
itemDefault: {
|
||||
paddingHorizontal: StyleConstants.Spacing.Global.PagePadding,
|
||||
paddingVertical: StyleConstants.Spacing.M
|
||||
},
|
||||
itemAccount: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center'
|
||||
},
|
||||
itemAccountAvatar: {
|
||||
alignSelf: 'flex-start',
|
||||
width: StyleConstants.Avatar.S,
|
||||
height: StyleConstants.Avatar.S,
|
||||
borderRadius: 6,
|
||||
marginRight: StyleConstants.Spacing.S
|
||||
},
|
||||
itemAccountAcct: { marginTop: StyleConstants.Spacing.XS }
|
||||
})
|
||||
|
||||
export default ComponentAccount
|
@ -27,7 +27,8 @@ const renderNode = ({
|
||||
navigation,
|
||||
mentions,
|
||||
tags,
|
||||
showFullLink
|
||||
showFullLink,
|
||||
disableDetails
|
||||
}: {
|
||||
theme: any
|
||||
node: any
|
||||
@ -37,6 +38,7 @@ const renderNode = ({
|
||||
mentions?: Mastodon.Mention[]
|
||||
tags?: Mastodon.Tag[]
|
||||
showFullLink: boolean
|
||||
disableDetails: boolean
|
||||
}) => {
|
||||
if (node.name == 'a') {
|
||||
const classes = node.attribs.class
|
||||
@ -52,9 +54,10 @@ const renderNode = ({
|
||||
}}
|
||||
onPress={() => {
|
||||
const tag = href.split(new RegExp(/\/tag\/(.*)|\/tags\/(.*)/))
|
||||
navigation.push('Screen-Shared-Hashtag', {
|
||||
hashtag: tag[1] || tag[2]
|
||||
})
|
||||
!disableDetails &&
|
||||
navigation.push('Screen-Shared-Hashtag', {
|
||||
hashtag: tag[1] || tag[2]
|
||||
})
|
||||
}}
|
||||
>
|
||||
{node.children[0].data}
|
||||
@ -72,6 +75,7 @@ const renderNode = ({
|
||||
}}
|
||||
onPress={() => {
|
||||
accountIndex !== -1 &&
|
||||
!disableDetails &&
|
||||
navigation.push('Screen-Shared-Account', {
|
||||
account: mentions[accountIndex]
|
||||
})
|
||||
@ -96,7 +100,7 @@ const renderNode = ({
|
||||
...StyleConstants.FontStyle[size]
|
||||
}}
|
||||
onPress={async () =>
|
||||
!shouldBeTag
|
||||
!disableDetails && !shouldBeTag
|
||||
? await openLink(href)
|
||||
: navigation.push('Screen-Shared-Hashtag', {
|
||||
hashtag: content.substring(1)
|
||||
@ -132,6 +136,7 @@ export interface Props {
|
||||
showFullLink?: boolean
|
||||
numberOfLines?: number
|
||||
expandHint?: string
|
||||
disableDetails?: boolean
|
||||
}
|
||||
|
||||
const ParseHTML: React.FC<Props> = ({
|
||||
@ -142,7 +147,8 @@ const ParseHTML: React.FC<Props> = ({
|
||||
tags,
|
||||
showFullLink = false,
|
||||
numberOfLines = 10,
|
||||
expandHint = '全文'
|
||||
expandHint = '全文',
|
||||
disableDetails = false
|
||||
}) => {
|
||||
const navigation = useNavigation()
|
||||
const { theme } = useTheme()
|
||||
@ -157,7 +163,8 @@ const ParseHTML: React.FC<Props> = ({
|
||||
navigation,
|
||||
mentions,
|
||||
tags,
|
||||
showFullLink
|
||||
showFullLink,
|
||||
disableDetails
|
||||
}),
|
||||
[]
|
||||
)
|
||||
|
@ -15,7 +15,7 @@ export interface Props {
|
||||
const RelationshipIncoming: React.FC<Props> = ({ id }) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const relationshipQueryKey = ['Relationship', { id }]
|
||||
const relationshipQueryKey: QueryKey.Relationship = ['Relationship', { id }]
|
||||
|
||||
const queryClient = useQueryClient()
|
||||
const fireMutation = useCallback(
|
||||
|
@ -14,7 +14,7 @@ export interface Props {
|
||||
const RelationshipOutgoing: React.FC<Props> = ({ id }) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const relationshipQueryKey = ['Relationship', { id }]
|
||||
const relationshipQueryKey: QueryKey.Relationship = ['Relationship', { id }]
|
||||
const query = useQuery(relationshipQueryKey, relationshipFetch)
|
||||
|
||||
const queryClient = useQueryClient()
|
||||
|
31
src/components/Separator.tsx
Normal file
31
src/components/Separator.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, View } from 'react-native'
|
||||
|
||||
export interface Props {
|
||||
extraMarginLeft?: number
|
||||
extraMarginRight?: number
|
||||
}
|
||||
|
||||
const ComponentSeparator = React.memo(
|
||||
({ extraMarginLeft = 0, extraMarginRight = 0 }: Props) => {
|
||||
const { theme } = useTheme()
|
||||
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
borderTopColor: theme.border,
|
||||
borderTopWidth: StyleSheet.hairlineWidth,
|
||||
marginLeft:
|
||||
StyleConstants.Spacing.Global.PagePadding + extraMarginLeft,
|
||||
marginRight:
|
||||
StyleConstants.Spacing.Global.PagePadding + extraMarginRight
|
||||
}}
|
||||
/>
|
||||
)
|
||||
},
|
||||
() => true
|
||||
)
|
||||
|
||||
export default ComponentSeparator
|
@ -1,18 +1,18 @@
|
||||
import ComponentSeparator from '@components/Separator'
|
||||
import TimelineConversation from '@components/Timelines/Timeline/Conversation'
|
||||
import TimelineDefault from '@components/Timelines/Timeline/Default'
|
||||
import TimelineEmpty from '@components/Timelines/Timeline/Empty'
|
||||
import TimelineEnd from '@root/components/Timelines/Timeline/End'
|
||||
import TimelineNotifications from '@components/Timelines/Timeline/Notifications'
|
||||
import { useScrollToTop } from '@react-navigation/native'
|
||||
import { timelineFetch } from '@utils/fetches/timelineFetch'
|
||||
import { updateNotification } 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 { InfiniteData, useInfiniteQuery } from 'react-query'
|
||||
|
||||
import TimelineNotifications from '@components/Timelines/Timeline/Notifications'
|
||||
import TimelineDefault from '@components/Timelines/Timeline/Default'
|
||||
import TimelineConversation from '@components/Timelines/Timeline/Conversation'
|
||||
import { timelineFetch } from '@utils/fetches/timelineFetch'
|
||||
import TimelineSeparator from '@components/Timelines/Timeline/Separator'
|
||||
import TimelineEmpty from '@components/Timelines/Timeline/Empty'
|
||||
import TimelineEnd from '@components/Timelines/Timeline/Shared/End'
|
||||
import { useScrollToTop } from '@react-navigation/native'
|
||||
import { FlatList } from 'react-native-gesture-handler'
|
||||
import { InfiniteData, useInfiniteQuery } from 'react-query'
|
||||
import { useDispatch } from 'react-redux'
|
||||
import { updateNotification } from '@root/utils/slices/instancesSlice'
|
||||
|
||||
export type TimelineData =
|
||||
| InfiniteData<{
|
||||
@ -130,6 +130,10 @@ const Timeline: React.FC<Props> = ({
|
||||
item={item}
|
||||
queryKey={queryKey}
|
||||
index={index}
|
||||
{...(queryKey[0] === 'RemotePublic' && {
|
||||
disableDetails: true,
|
||||
disableOnPress: true
|
||||
})}
|
||||
{...(flattenPinnedLength &&
|
||||
flattenPinnedLength[0] && {
|
||||
pinnedLength: flattenPinnedLength[0]
|
||||
@ -143,8 +147,13 @@ const Timeline: React.FC<Props> = ({
|
||||
)
|
||||
const ItemSeparatorComponent = useCallback(
|
||||
({ leadingItem }) => (
|
||||
<TimelineSeparator
|
||||
{...(toot === leadingItem.id && { highlighted: true })}
|
||||
<ComponentSeparator
|
||||
{...(toot === leadingItem.id
|
||||
? { extraMarginLeft: 0 }
|
||||
: {
|
||||
extraMarginLeft:
|
||||
StyleConstants.Avatar.M + StyleConstants.Spacing.S
|
||||
})}
|
||||
/>
|
||||
),
|
||||
[]
|
||||
|
@ -15,10 +15,12 @@ import { useSelector } from 'react-redux'
|
||||
|
||||
export interface Props {
|
||||
item: Mastodon.Status
|
||||
queryKey: QueryKey.Timeline
|
||||
queryKey?: QueryKey.Timeline
|
||||
index: number
|
||||
pinnedLength?: number
|
||||
highlighted?: boolean
|
||||
disableDetails?: boolean
|
||||
disableOnPress?: boolean
|
||||
}
|
||||
|
||||
// When the poll is long
|
||||
@ -27,17 +29,18 @@ const TimelineDefault: React.FC<Props> = ({
|
||||
queryKey,
|
||||
index,
|
||||
pinnedLength,
|
||||
highlighted = false
|
||||
highlighted = false,
|
||||
disableDetails = false,
|
||||
disableOnPress = false
|
||||
}) => {
|
||||
const localAccountId = useSelector(getLocalAccountId)
|
||||
const isRemotePublic = queryKey[0] === 'RemotePublic'
|
||||
const navigation = useNavigation()
|
||||
|
||||
let actualStatus = item.reblog ? item.reblog : item
|
||||
|
||||
const onPress = useCallback(
|
||||
() =>
|
||||
!isRemotePublic &&
|
||||
!disableOnPress &&
|
||||
!highlighted &&
|
||||
navigation.push('Screen-Shared-Toot', {
|
||||
toot: actualStatus
|
||||
@ -55,11 +58,11 @@ const TimelineDefault: React.FC<Props> = ({
|
||||
|
||||
<View style={styles.header}>
|
||||
<TimelineAvatar
|
||||
{...(!isRemotePublic && { queryKey })}
|
||||
queryKey={disableOnPress ? undefined : queryKey}
|
||||
account={actualStatus.account}
|
||||
/>
|
||||
<TimelineHeaderDefault
|
||||
{...(!isRemotePublic && { queryKey })}
|
||||
queryKey={disableOnPress ? undefined : queryKey}
|
||||
status={actualStatus}
|
||||
sameAccount={actualStatus.account.id === localAccountId}
|
||||
/>
|
||||
@ -74,9 +77,13 @@ const TimelineDefault: React.FC<Props> = ({
|
||||
}}
|
||||
>
|
||||
{actualStatus.content.length > 0 && (
|
||||
<TimelineContent status={actualStatus} highlighted={highlighted} />
|
||||
<TimelineContent
|
||||
status={actualStatus}
|
||||
highlighted={highlighted}
|
||||
disableDetails={disableDetails}
|
||||
/>
|
||||
)}
|
||||
{actualStatus.poll && (
|
||||
{queryKey && actualStatus.poll && (
|
||||
<TimelinePoll
|
||||
queryKey={queryKey}
|
||||
poll={actualStatus.poll}
|
||||
@ -84,13 +91,15 @@ const TimelineDefault: React.FC<Props> = ({
|
||||
sameAccount={actualStatus.account.id === localAccountId}
|
||||
/>
|
||||
)}
|
||||
{actualStatus.media_attachments.length > 0 && (
|
||||
{!disableDetails && actualStatus.media_attachments.length > 0 && (
|
||||
<TimelineAttachment status={actualStatus} />
|
||||
)}
|
||||
{actualStatus.card && <TimelineCard card={actualStatus.card} />}
|
||||
{!disableDetails && actualStatus.card && (
|
||||
<TimelineCard card={actualStatus.card} />
|
||||
)}
|
||||
</View>
|
||||
|
||||
{!isRemotePublic && (
|
||||
{queryKey && !disableDetails && (
|
||||
<View
|
||||
style={{
|
||||
paddingLeft: highlighted
|
||||
|
@ -23,7 +23,6 @@ const TimelineEnd: React.FC<Props> = ({ hasNextPage }) => {
|
||||
i18nKey='timeline:shared.end.message' // optional -> fallbacks to defaults if not provided
|
||||
components={[
|
||||
<Icon
|
||||
inline
|
||||
name='Coffee'
|
||||
size={StyleConstants.Font.Size.S}
|
||||
color={theme.secondary}
|
@ -1,38 +0,0 @@
|
||||
import React from 'react'
|
||||
import { StyleSheet, View } from 'react-native'
|
||||
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
|
||||
export interface Props {
|
||||
highlighted?: boolean
|
||||
}
|
||||
|
||||
const TimelineSeparator: React.FC<Props> = ({ highlighted = false }) => {
|
||||
const { theme } = useTheme()
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
styles.base,
|
||||
{
|
||||
borderTopColor: theme.border,
|
||||
marginLeft: highlighted
|
||||
? StyleConstants.Spacing.Global.PagePadding
|
||||
: StyleConstants.Spacing.Global.PagePadding +
|
||||
StyleConstants.Avatar.M +
|
||||
StyleConstants.Spacing.S
|
||||
}
|
||||
]}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
base: {
|
||||
borderTopWidth: StyleSheet.hairlineWidth,
|
||||
marginRight: StyleConstants.Spacing.Global.PagePadding
|
||||
}
|
||||
})
|
||||
|
||||
export default TimelineSeparator
|
@ -8,12 +8,14 @@ export interface Props {
|
||||
status: Mastodon.Status
|
||||
numberOfLines?: number
|
||||
highlighted?: boolean
|
||||
disableDetails?: boolean
|
||||
}
|
||||
|
||||
const TimelineContent: React.FC<Props> = ({
|
||||
status,
|
||||
numberOfLines,
|
||||
highlighted = false
|
||||
highlighted = false,
|
||||
disableDetails = false
|
||||
}) => {
|
||||
const { t } = useTranslation('timeline')
|
||||
|
||||
@ -29,6 +31,7 @@ const TimelineContent: React.FC<Props> = ({
|
||||
mentions={status.mentions}
|
||||
tags={status.tags}
|
||||
numberOfLines={999}
|
||||
disableDetails={disableDetails}
|
||||
/>
|
||||
</View>
|
||||
<ParseHTML
|
||||
@ -39,6 +42,7 @@ const TimelineContent: React.FC<Props> = ({
|
||||
tags={status.tags}
|
||||
numberOfLines={0}
|
||||
expandHint={t('shared.content.expandHint')}
|
||||
disableDetails={disableDetails}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
@ -49,6 +53,7 @@ const TimelineContent: React.FC<Props> = ({
|
||||
mentions={status.mentions}
|
||||
tags={status.tags}
|
||||
numberOfLines={numberOfLines}
|
||||
disableDetails={disableDetails}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
@ -1,9 +1,11 @@
|
||||
import client from '@api/client'
|
||||
import haptics from '@components/haptics'
|
||||
import Icon from '@components/Icon'
|
||||
import { TimelineData } from '@components/Timelines/Timeline'
|
||||
import { toast } from '@components/toast'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import { findIndex } from 'lodash'
|
||||
import React, { useCallback, useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Pressable, StyleSheet, View } from 'react-native'
|
||||
@ -33,14 +35,24 @@ const HeaderConversation: React.FC<Props> = ({ queryKey, conversation }) => {
|
||||
const oldData = queryClient.getQueryData(queryKey)
|
||||
|
||||
haptics('Success')
|
||||
queryClient.setQueryData(queryKey, (old: any) =>
|
||||
old.pages.map((paging: any) => ({
|
||||
toots: paging.toots.filter(
|
||||
(toot: Mastodon.Conversation) => toot.id !== conversation.id
|
||||
),
|
||||
pointer: paging.pointer
|
||||
}))
|
||||
)
|
||||
queryClient.setQueryData<TimelineData>(queryKey, old => {
|
||||
let tootIndex = -1
|
||||
const pageIndex = findIndex(old?.pages, page => {
|
||||
const tempIndex = findIndex(page.toots, ['id', conversation.id])
|
||||
if (tempIndex >= 0) {
|
||||
tootIndex = tempIndex
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
if (pageIndex >= 0 && tootIndex >= 0) {
|
||||
old!.pages[pageIndex].toots.splice(tootIndex, 1)
|
||||
}
|
||||
|
||||
return old
|
||||
})
|
||||
|
||||
return oldData
|
||||
},
|
||||
|
Reference in New Issue
Block a user