mirror of https://github.com/tooot-app/app
Show pinned posts in Account page
This commit is contained in:
parent
177afe1dd1
commit
fe1ca72a3e
|
@ -65,13 +65,14 @@ const Timeline: React.FC<Props> = ({
|
|||
})
|
||||
const flattenData = data ? data.flatMap(d => [...d?.toots]) : []
|
||||
const flattenPointer = data ? data.flatMap(d => [d?.pointer]) : []
|
||||
const flattenPinnedLength = data ? data.flatMap(d => [d?.pinnedLength]) : []
|
||||
|
||||
const flRef = useRef<FlatList>(null)
|
||||
useEffect(() => {
|
||||
if (toot && isSuccess) {
|
||||
setTimeout(() => {
|
||||
flRef.current?.scrollToIndex({
|
||||
index: flattenPointer[0],
|
||||
index: flattenPointer[0]!,
|
||||
viewOffset: 100
|
||||
})
|
||||
}, 500)
|
||||
|
@ -79,7 +80,7 @@ const Timeline: React.FC<Props> = ({
|
|||
}, [isSuccess])
|
||||
|
||||
const flKeyExtrator = useCallback(({ id }) => id, [])
|
||||
const flRenderItem = useCallback(({ item }) => {
|
||||
const flRenderItem = useCallback(({ item, index }) => {
|
||||
switch (page) {
|
||||
case 'Conversations':
|
||||
return <TimelineConversation item={item} queryKey={queryKey} />
|
||||
|
@ -90,6 +91,11 @@ const Timeline: React.FC<Props> = ({
|
|||
<TimelineDefault
|
||||
item={item}
|
||||
queryKey={queryKey}
|
||||
index={index}
|
||||
{...(flattenPinnedLength &&
|
||||
flattenPinnedLength[0] && {
|
||||
pinnedLength: flattenPinnedLength[0]
|
||||
})}
|
||||
{...(toot && toot.id === item.id && { highlighted: true })}
|
||||
/>
|
||||
)
|
||||
|
|
|
@ -16,6 +16,8 @@ import { StyleConstants } from '@utils/styles/constants'
|
|||
export interface Props {
|
||||
item: Mastodon.Status
|
||||
queryKey: App.QueryKey
|
||||
index: number
|
||||
pinnedLength?: number
|
||||
highlighted?: boolean
|
||||
}
|
||||
|
||||
|
@ -23,6 +25,8 @@ export interface Props {
|
|||
const TimelineDefault: React.FC<Props> = ({
|
||||
item,
|
||||
queryKey,
|
||||
index,
|
||||
pinnedLength,
|
||||
highlighted = false
|
||||
}) => {
|
||||
const isRemotePublic = queryKey[0] === 'RemotePublic'
|
||||
|
@ -72,9 +76,11 @@ const TimelineDefault: React.FC<Props> = ({
|
|||
|
||||
return (
|
||||
<View style={styles.statusView}>
|
||||
{item.reblog && (
|
||||
{item.reblog ? (
|
||||
<TimelineActioned action='reblog' account={item.account} />
|
||||
)}
|
||||
) : pinnedLength && index < pinnedLength ? (
|
||||
<TimelineActioned action='pinned' account={item.account} />
|
||||
) : null}
|
||||
|
||||
<View style={styles.header}>
|
||||
<TimelineAvatar
|
||||
|
|
|
@ -8,13 +8,12 @@ import { StyleConstants } from '@utils/styles/constants'
|
|||
|
||||
export interface Props {
|
||||
account: Mastodon.Account
|
||||
action: 'favourite' | 'follow' | 'mention' | 'poll' | 'reblog'
|
||||
action: 'favourite' | 'follow' | 'mention' | 'poll' | 'reblog' | 'pinned'
|
||||
notification?: boolean
|
||||
}
|
||||
|
||||
const TimelineActioned: React.FC<Props> = ({
|
||||
account,
|
||||
|
||||
action,
|
||||
notification = false
|
||||
}) => {
|
||||
|
@ -25,6 +24,17 @@ const TimelineActioned: React.FC<Props> = ({
|
|||
let icon
|
||||
let content
|
||||
switch (action) {
|
||||
case 'pinned':
|
||||
icon = (
|
||||
<Feather
|
||||
name='anchor'
|
||||
size={StyleConstants.Font.Size.S}
|
||||
color={iconColor}
|
||||
style={styles.icon}
|
||||
/>
|
||||
)
|
||||
content = `置顶`
|
||||
break
|
||||
case 'favourite':
|
||||
icon = (
|
||||
<Feather
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useState } from 'react'
|
||||
import { Image, StyleSheet, Text, View } from 'react-native'
|
||||
import React, { createRef, useEffect, useState } from 'react'
|
||||
import { Animated, Image, StyleSheet, Text, View } from 'react-native'
|
||||
import ShimmerPlaceholder from 'react-native-shimmer-placeholder'
|
||||
import { Feather } from '@expo/vector-icons'
|
||||
|
||||
|
@ -18,10 +18,37 @@ const AccountInformation: React.FC<Props> = ({ account }) => {
|
|||
const { theme } = useTheme()
|
||||
const [avatarLoaded, setAvatarLoaded] = useState(false)
|
||||
|
||||
const shimmerAvatarRef = createRef<ShimmerPlaceholder>()
|
||||
const shimmerNameRef = createRef<ShimmerPlaceholder>()
|
||||
const shimmerAccountRef = createRef<ShimmerPlaceholder>()
|
||||
const shimmerCreatedRef = createRef<ShimmerPlaceholder>()
|
||||
const shimmerStatTootRef = createRef<ShimmerPlaceholder>()
|
||||
const shimmerStatFolloingRef = createRef<ShimmerPlaceholder>()
|
||||
const shimmerStatFollowerRef = createRef<ShimmerPlaceholder>()
|
||||
useEffect(() => {
|
||||
const informationAnimated = Animated.stagger(400, [
|
||||
Animated.parallel([
|
||||
shimmerAvatarRef.current!.getAnimated(),
|
||||
shimmerNameRef.current!.getAnimated(),
|
||||
shimmerAccountRef.current!.getAnimated(),
|
||||
shimmerCreatedRef.current!.getAnimated(),
|
||||
shimmerStatTootRef.current!.getAnimated(),
|
||||
shimmerStatFolloingRef.current!.getAnimated(),
|
||||
shimmerStatFollowerRef.current!.getAnimated()
|
||||
])
|
||||
])
|
||||
Animated.loop(informationAnimated).start()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<View style={styles.information}>
|
||||
{/* <Text>Moved or not: {account.moved}</Text> */}
|
||||
<ShimmerPlaceholder visible={avatarLoaded} width={90} height={90}>
|
||||
<ShimmerPlaceholder
|
||||
ref={shimmerAvatarRef}
|
||||
visible={avatarLoaded}
|
||||
width={StyleConstants.Avatar.L}
|
||||
height={StyleConstants.Avatar.L}
|
||||
>
|
||||
<Image
|
||||
source={{ uri: account?.avatar }}
|
||||
style={styles.avatar}
|
||||
|
@ -29,52 +56,68 @@ const AccountInformation: React.FC<Props> = ({ account }) => {
|
|||
/>
|
||||
</ShimmerPlaceholder>
|
||||
|
||||
<View style={styles.display_name}>
|
||||
{account?.emojis ? (
|
||||
<Emojis
|
||||
content={account?.display_name || account?.username}
|
||||
emojis={account.emojis}
|
||||
size={StyleConstants.Font.Size.L}
|
||||
fontBold={true}
|
||||
/>
|
||||
) : (
|
||||
<ShimmerPlaceholder
|
||||
ref={shimmerNameRef}
|
||||
visible={account !== undefined}
|
||||
width={StyleConstants.Font.Size.L * 8}
|
||||
height={StyleConstants.Font.Size.L}
|
||||
style={styles.display_name}
|
||||
>
|
||||
<View>
|
||||
{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>
|
||||
)}
|
||||
</View>
|
||||
</ShimmerPlaceholder>
|
||||
|
||||
<ShimmerPlaceholder
|
||||
ref={shimmerAccountRef}
|
||||
visible={account !== undefined}
|
||||
width={StyleConstants.Font.Size.M * 8}
|
||||
height={StyleConstants.Font.Size.M}
|
||||
style={{ marginBottom: StyleConstants.Spacing.L }}
|
||||
>
|
||||
<View style={styles.account}>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.primary,
|
||||
fontSize: StyleConstants.Font.Size.L,
|
||||
fontWeight: StyleConstants.Font.Weight.Bold
|
||||
color: theme.secondary,
|
||||
fontSize: StyleConstants.Font.Size.M
|
||||
}}
|
||||
selectable
|
||||
>
|
||||
{account?.display_name || account?.username}
|
||||
@{account?.acct}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
|
||||
<View style={styles.account}>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.secondary,
|
||||
fontSize: StyleConstants.Font.Size.M
|
||||
}}
|
||||
selectable
|
||||
>
|
||||
@{account?.acct}
|
||||
</Text>
|
||||
{account?.locked && (
|
||||
<Feather
|
||||
name='lock'
|
||||
style={styles.account_types}
|
||||
color={theme.secondary}
|
||||
/>
|
||||
)}
|
||||
{account?.bot && (
|
||||
<Feather
|
||||
name='hard-drive'
|
||||
style={styles.account_types}
|
||||
color={theme.secondary}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
{account?.locked && (
|
||||
<Feather
|
||||
name='lock'
|
||||
style={styles.account_types}
|
||||
color={theme.secondary}
|
||||
/>
|
||||
)}
|
||||
{account?.bot && (
|
||||
<Feather
|
||||
name='hard-drive'
|
||||
style={styles.account_types}
|
||||
color={theme.secondary}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
</ShimmerPlaceholder>
|
||||
|
||||
{account?.fields && account.fields.length > 0 && (
|
||||
<View style={[styles.fields, { borderTopColor: theme.border }]}>
|
||||
|
@ -84,15 +127,7 @@ const AccountInformation: React.FC<Props> = ({ account }) => {
|
|||
style={[styles.field, { borderBottomColor: theme.border }]}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
flexBasis: '30%',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
borderRightWidth: 1,
|
||||
borderRightColor: theme.border,
|
||||
paddingLeft: StyleConstants.Spacing.S,
|
||||
paddingRight: StyleConstants.Spacing.S
|
||||
}}
|
||||
style={[styles.fieldLeft, { borderRightColor: theme.border }]}
|
||||
>
|
||||
<ParseContent
|
||||
content={field.name}
|
||||
|
@ -105,18 +140,11 @@ const AccountInformation: React.FC<Props> = ({ account }) => {
|
|||
name='check-circle'
|
||||
size={StyleConstants.Font.Size.M}
|
||||
color={theme.primary}
|
||||
style={styles.fieldCheck}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
<View
|
||||
style={{
|
||||
flexBasis: '70%',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
paddingLeft: StyleConstants.Spacing.S,
|
||||
paddingRight: StyleConstants.Spacing.S
|
||||
}}
|
||||
>
|
||||
<View style={styles.fieldRight}>
|
||||
<ParseContent
|
||||
content={field.value}
|
||||
size={'M'}
|
||||
|
@ -139,48 +167,78 @@ const AccountInformation: React.FC<Props> = ({ account }) => {
|
|||
</View>
|
||||
)}
|
||||
|
||||
<View style={styles.created_at}>
|
||||
<Feather
|
||||
name='calendar'
|
||||
size={StyleConstants.Font.Size.M + 2}
|
||||
color={theme.secondary}
|
||||
style={styles.created_at_icon}
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.secondary,
|
||||
fontSize: StyleConstants.Font.Size.M
|
||||
}}
|
||||
>
|
||||
{t(
|
||||
'content.created_at',
|
||||
{
|
||||
<ShimmerPlaceholder
|
||||
ref={shimmerCreatedRef}
|
||||
visible={account !== undefined}
|
||||
width={StyleConstants.Font.Size.S * 8}
|
||||
height={StyleConstants.Font.Size.S}
|
||||
style={{ marginBottom: StyleConstants.Spacing.M }}
|
||||
>
|
||||
<View style={styles.created_at}>
|
||||
<Feather
|
||||
name='calendar'
|
||||
size={StyleConstants.Font.Size.S}
|
||||
color={theme.secondary}
|
||||
style={styles.created_at_icon}
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.secondary,
|
||||
fontSize: StyleConstants.Font.Size.S
|
||||
}}
|
||||
>
|
||||
{t('content.created_at', {
|
||||
date: new Date(account?.created_at!).toLocaleDateString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
})
|
||||
} || null
|
||||
)}
|
||||
</Text>
|
||||
</View>
|
||||
})}
|
||||
</Text>
|
||||
</View>
|
||||
</ShimmerPlaceholder>
|
||||
|
||||
<View style={styles.summary}>
|
||||
<Text style={{ color: theme.primary }}>
|
||||
{t('content.summary.statuses_count', {
|
||||
count: account?.statuses_count || 0
|
||||
})}
|
||||
</Text>
|
||||
<Text style={{ color: theme.primary, textAlign: 'center' }}>
|
||||
{t('content.summary.followers_count', {
|
||||
count: account?.followers_count || 0
|
||||
})}
|
||||
</Text>
|
||||
<Text style={{ color: theme.primary, textAlign: 'right' }}>
|
||||
{t('content.summary.following_count', {
|
||||
count: account?.following_count || 0
|
||||
})}
|
||||
</Text>
|
||||
<View style={styles.stats}>
|
||||
<ShimmerPlaceholder
|
||||
ref={shimmerStatTootRef}
|
||||
visible={account !== undefined}
|
||||
width={StyleConstants.Font.Size.S * 5}
|
||||
height={StyleConstants.Font.Size.S}
|
||||
>
|
||||
<Text style={[styles.stat, { color: theme.primary }]}>
|
||||
{t('content.summary.statuses_count', {
|
||||
count: account?.statuses_count || 0
|
||||
})}
|
||||
</Text>
|
||||
</ShimmerPlaceholder>
|
||||
<ShimmerPlaceholder
|
||||
ref={shimmerStatFolloingRef}
|
||||
visible={account !== undefined}
|
||||
width={StyleConstants.Font.Size.S * 5}
|
||||
height={StyleConstants.Font.Size.S}
|
||||
>
|
||||
<Text
|
||||
style={[styles.stat, { color: theme.primary, textAlign: 'center' }]}
|
||||
>
|
||||
{t('content.summary.followers_count', {
|
||||
count: account?.followers_count || 0
|
||||
})}
|
||||
</Text>
|
||||
</ShimmerPlaceholder>
|
||||
<ShimmerPlaceholder
|
||||
ref={shimmerStatFollowerRef}
|
||||
visible={account !== undefined}
|
||||
width={StyleConstants.Font.Size.S * 5}
|
||||
height={StyleConstants.Font.Size.S}
|
||||
>
|
||||
<Text
|
||||
style={[styles.stat, { color: theme.primary, textAlign: 'right' }]}
|
||||
>
|
||||
{t('content.summary.following_count', {
|
||||
count: account?.following_count || 0
|
||||
})}
|
||||
</Text>
|
||||
</ShimmerPlaceholder>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
|
@ -203,8 +261,7 @@ const styles = StyleSheet.create({
|
|||
},
|
||||
account: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginBottom: StyleConstants.Spacing.L
|
||||
alignItems: 'center'
|
||||
},
|
||||
account_types: { marginLeft: StyleConstants.Spacing.S },
|
||||
fields: {
|
||||
|
@ -218,21 +275,40 @@ const styles = StyleSheet.create({
|
|||
paddingTop: StyleConstants.Spacing.S,
|
||||
paddingBottom: StyleConstants.Spacing.S
|
||||
},
|
||||
fieldLeft: {
|
||||
flexBasis: '30%',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
borderRightWidth: 1,
|
||||
paddingLeft: StyleConstants.Spacing.S,
|
||||
paddingRight: StyleConstants.Spacing.S
|
||||
},
|
||||
fieldCheck: { marginLeft: StyleConstants.Spacing.XS },
|
||||
fieldRight: {
|
||||
flexBasis: '70%',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
paddingLeft: StyleConstants.Spacing.S,
|
||||
paddingRight: StyleConstants.Spacing.S
|
||||
},
|
||||
note: {
|
||||
marginBottom: StyleConstants.Spacing.L
|
||||
},
|
||||
created_at: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginBottom: StyleConstants.Spacing.M
|
||||
alignItems: 'center'
|
||||
},
|
||||
created_at_icon: {
|
||||
marginRight: StyleConstants.Spacing.XS
|
||||
},
|
||||
summary: {
|
||||
stats: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between'
|
||||
},
|
||||
stat: {
|
||||
fontSize: StyleConstants.Font.Size.S
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
@ -25,7 +25,11 @@ export const timelineFetch = async (
|
|||
direction: 'prev' | 'next'
|
||||
id: string
|
||||
}
|
||||
) => {
|
||||
): Promise<{
|
||||
toots: Mastodon.Status[]
|
||||
pointer?: number
|
||||
pinnedLength?: number
|
||||
}> => {
|
||||
let res
|
||||
|
||||
if (pagination && pagination.id) {
|
||||
|
@ -47,7 +51,7 @@ export const timelineFetch = async (
|
|||
url: 'timelines/home',
|
||||
params
|
||||
})
|
||||
return Promise.resolve({ toots: res.body, pointer: null })
|
||||
return Promise.resolve({ toots: res.body })
|
||||
|
||||
case 'Local':
|
||||
params.local = 'true'
|
||||
|
@ -57,7 +61,7 @@ export const timelineFetch = async (
|
|||
url: 'timelines/public',
|
||||
params
|
||||
})
|
||||
return Promise.resolve({ toots: res.body, pointer: null })
|
||||
return Promise.resolve({ toots: res.body })
|
||||
|
||||
case 'LocalPublic':
|
||||
res = await client({
|
||||
|
@ -66,7 +70,7 @@ export const timelineFetch = async (
|
|||
url: 'timelines/public',
|
||||
params
|
||||
})
|
||||
return Promise.resolve({ toots: res.body, pointer: null })
|
||||
return Promise.resolve({ toots: res.body })
|
||||
|
||||
case 'RemotePublic':
|
||||
res = await client({
|
||||
|
@ -75,7 +79,7 @@ export const timelineFetch = async (
|
|||
url: 'timelines/public',
|
||||
params
|
||||
})
|
||||
return Promise.resolve({ toots: res.body, pointer: null })
|
||||
return Promise.resolve({ toots: res.body })
|
||||
|
||||
case 'Notifications':
|
||||
res = await client({
|
||||
|
@ -84,7 +88,7 @@ export const timelineFetch = async (
|
|||
url: 'notifications',
|
||||
params
|
||||
})
|
||||
return Promise.resolve({ toots: res.body, pointer: null })
|
||||
return Promise.resolve({ toots: res.body })
|
||||
|
||||
case 'Account_Default':
|
||||
res = await client({
|
||||
|
@ -95,6 +99,7 @@ export const timelineFetch = async (
|
|||
pinned: 'true'
|
||||
}
|
||||
})
|
||||
const pinnedLength = res.body.length
|
||||
let toots: Mastodon.Status[] = res.body
|
||||
res = await client({
|
||||
method: 'get',
|
||||
|
@ -105,7 +110,7 @@ export const timelineFetch = async (
|
|||
}
|
||||
})
|
||||
toots = uniqBy([...toots, ...res.body], 'id')
|
||||
return Promise.resolve({ toots: toots, pointer: null })
|
||||
return Promise.resolve({ toots: toots, pinnedLength })
|
||||
|
||||
case 'Account_All':
|
||||
res = await client({
|
||||
|
@ -114,7 +119,7 @@ export const timelineFetch = async (
|
|||
url: `accounts/${account}/statuses`,
|
||||
params
|
||||
})
|
||||
return Promise.resolve({ toots: res.body, pointer: null })
|
||||
return Promise.resolve({ toots: res.body })
|
||||
|
||||
case 'Account_Media':
|
||||
res = await client({
|
||||
|
@ -125,7 +130,7 @@ export const timelineFetch = async (
|
|||
only_media: 'true'
|
||||
}
|
||||
})
|
||||
return Promise.resolve({ toots: res.body, pointer: null })
|
||||
return Promise.resolve({ toots: res.body })
|
||||
|
||||
case 'Hashtag':
|
||||
res = await client({
|
||||
|
@ -134,7 +139,7 @@ export const timelineFetch = async (
|
|||
url: `timelines/tag/${hashtag}`,
|
||||
params
|
||||
})
|
||||
return Promise.resolve({ toots: res.body, pointer: null })
|
||||
return Promise.resolve({ toots: res.body })
|
||||
|
||||
case 'Conversations':
|
||||
res = await client({
|
||||
|
@ -149,7 +154,7 @@ export const timelineFetch = async (
|
|||
(b: Mastodon.Conversation) => b.id !== pagination.id
|
||||
)
|
||||
}
|
||||
return Promise.resolve({ toots: res.body, pointer: null })
|
||||
return Promise.resolve({ toots: res.body })
|
||||
|
||||
case 'Bookmarks':
|
||||
res = await client({
|
||||
|
@ -158,7 +163,7 @@ export const timelineFetch = async (
|
|||
url: `bookmarks`,
|
||||
params
|
||||
})
|
||||
return Promise.resolve({ toots: res.body, pointer: null })
|
||||
return Promise.resolve({ toots: res.body })
|
||||
|
||||
case 'Favourites':
|
||||
res = await client({
|
||||
|
@ -167,7 +172,7 @@ export const timelineFetch = async (
|
|||
url: `favourites`,
|
||||
params
|
||||
})
|
||||
return Promise.resolve({ toots: res.body, pointer: null })
|
||||
return Promise.resolve({ toots: res.body })
|
||||
|
||||
case 'List':
|
||||
res = await client({
|
||||
|
@ -176,7 +181,7 @@ export const timelineFetch = async (
|
|||
url: `timelines/list/${list}`,
|
||||
params
|
||||
})
|
||||
return Promise.resolve({ toots: res.body, pointer: null })
|
||||
return Promise.resolve({ toots: res.body })
|
||||
|
||||
case 'Toot':
|
||||
res = await client({
|
||||
|
@ -188,8 +193,5 @@ export const timelineFetch = async (
|
|||
toots: [...res.body.ancestors, toot, ...res.body.descendants],
|
||||
pointer: res.body.ancestors.length
|
||||
})
|
||||
|
||||
default:
|
||||
console.error('Page is not provided')
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue