mirror of
https://github.com/tooot-app/app
synced 2025-02-18 04:40:57 +01:00
Updates
This commit is contained in:
parent
5473fcb770
commit
6aed76ac65
@ -1,5 +1,5 @@
|
||||
import { Feather } from '@expo/vector-icons'
|
||||
import React, { useLayoutEffect, useMemo } from 'react'
|
||||
import React, { useEffect, useMemo } from 'react'
|
||||
import {
|
||||
Pressable,
|
||||
StyleProp,
|
||||
@ -46,7 +46,7 @@ const Button: React.FC<Props> = ({
|
||||
}) => {
|
||||
const { theme } = useTheme()
|
||||
|
||||
useLayoutEffect(() => layoutAnimation(), [content, loading, disabled])
|
||||
useEffect(() => layoutAnimation(), [content, loading, disabled])
|
||||
|
||||
const loadingSpinkit = useMemo(
|
||||
() => (
|
||||
|
4
src/components/Parse.tsx
Normal file
4
src/components/Parse.tsx
Normal file
@ -0,0 +1,4 @@
|
||||
import ParseEmojis from './Parse/Emojis'
|
||||
import ParseHTML from './Parse/HTML'
|
||||
|
||||
export { ParseEmojis, ParseHTML }
|
70
src/components/Parse/Emojis.tsx
Normal file
70
src/components/Parse/Emojis.tsx
Normal file
@ -0,0 +1,70 @@
|
||||
import React from 'react'
|
||||
import { Image, StyleSheet, Text } from 'react-native'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
|
||||
const regexEmoji = new RegExp(/(:[A-Za-z0-9_]+:)/)
|
||||
|
||||
export interface Props {
|
||||
content: string
|
||||
emojis?: Mastodon.Emoji[]
|
||||
size?: 'S' | 'M' | 'L'
|
||||
fontBold?: boolean
|
||||
}
|
||||
|
||||
const ParseEmojis: React.FC<Props> = ({
|
||||
content,
|
||||
emojis,
|
||||
size = 'M',
|
||||
fontBold = false
|
||||
}) => {
|
||||
const { theme } = useTheme()
|
||||
const styles = StyleSheet.create({
|
||||
text: {
|
||||
color: theme.primary,
|
||||
...StyleConstants.FontStyle[size],
|
||||
...(fontBold && { fontWeight: StyleConstants.Font.Weight.Bold })
|
||||
},
|
||||
image: {
|
||||
width: StyleConstants.Font.Size[size],
|
||||
height: StyleConstants.Font.Size[size]
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<Text style={styles.text}>
|
||||
{emojis ? (
|
||||
content
|
||||
.split(regexEmoji)
|
||||
.filter(f => f)
|
||||
.map((str, i) => {
|
||||
if (str.match(regexEmoji)) {
|
||||
const emojiShortcode = str.split(regexEmoji)[1]
|
||||
const emojiIndex = emojis.findIndex(emoji => {
|
||||
return emojiShortcode === `:${emoji.shortcode}:`
|
||||
})
|
||||
return emojiIndex === -1 ? (
|
||||
<Text key={i}>{emojiShortcode}</Text>
|
||||
) : (
|
||||
<Text key={i}>
|
||||
{/* When emoji starts a paragraph, lineHeight will break */}
|
||||
{i === 0 ? <Text> </Text> : null}
|
||||
<Image
|
||||
resizeMode='contain'
|
||||
source={{ uri: emojis[emojiIndex].url }}
|
||||
style={[styles.image]}
|
||||
/>
|
||||
</Text>
|
||||
)
|
||||
} else {
|
||||
return <Text key={i}>{str}</Text>
|
||||
}
|
||||
})
|
||||
) : (
|
||||
<Text>{content}</Text>
|
||||
)}
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(ParseEmojis, () => true)
|
@ -1,14 +1,14 @@
|
||||
import openLink from '@components/openLink'
|
||||
import { ParseEmojis } from '@components/Parse'
|
||||
import { Feather } from '@expo/vector-icons'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import layoutAnimation from '@utils/styles/layoutAnimation'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import { LinearGradient } from 'expo-linear-gradient'
|
||||
import React, { useCallback, useMemo, useState } from 'react'
|
||||
import { Pressable, Text, View } from 'react-native'
|
||||
import HTMLView from 'react-native-htmlview'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import Emojis from '@components/Timelines/Timeline/Shared/Emojis'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import { Feather } from '@expo/vector-icons'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import openLink from '@root/utils/openLink'
|
||||
import layoutAnimation from '@root/utils/styles/layoutAnimation'
|
||||
|
||||
// Prevent going to the same hashtag multiple times
|
||||
const renderNode = ({
|
||||
@ -126,7 +126,7 @@ export interface Props {
|
||||
expandHint?: string
|
||||
}
|
||||
|
||||
const ParseContent: React.FC<Props> = ({
|
||||
const ParseHTML: React.FC<Props> = ({
|
||||
content,
|
||||
size = 'M',
|
||||
emojis,
|
||||
@ -155,11 +155,7 @@ const ParseContent: React.FC<Props> = ({
|
||||
)
|
||||
const textComponent = useCallback(({ children }) => {
|
||||
if (children) {
|
||||
return emojis ? (
|
||||
<Emojis content={children.toString()} emojis={emojis} size={size} />
|
||||
) : (
|
||||
<Text style={{ ...StyleConstants.FontStyle[size] }}>{children}</Text>
|
||||
)
|
||||
return <ParseEmojis content={children.toString()} emojis={emojis} />
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
@ -170,7 +166,9 @@ const ParseContent: React.FC<Props> = ({
|
||||
|
||||
const [heightOriginal, setHeightOriginal] = useState<number>()
|
||||
const [heightTruncated, setHeightTruncated] = useState<number>()
|
||||
const [allowExpand, setAllowExpand] = useState(false)
|
||||
const [allowExpand, setAllowExpand] = useState(
|
||||
numberOfLines === 0 ? true : undefined
|
||||
)
|
||||
const [showAllText, setShowAllText] = useState(false)
|
||||
|
||||
const calNumberOfLines = useMemo(() => {
|
||||
@ -189,27 +187,36 @@ const ParseContent: React.FC<Props> = ({
|
||||
}
|
||||
}, [heightOriginal, heightTruncated, allowExpand, showAllText])
|
||||
|
||||
const onLayout = useCallback(
|
||||
({ nativeEvent }) => {
|
||||
if (!heightOriginal) {
|
||||
setHeightOriginal(nativeEvent.layout.height)
|
||||
} else {
|
||||
if (!heightTruncated) {
|
||||
setHeightTruncated(nativeEvent.layout.height)
|
||||
} else {
|
||||
if (heightOriginal > heightTruncated) {
|
||||
setAllowExpand(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
[heightOriginal, heightTruncated]
|
||||
)
|
||||
|
||||
return (
|
||||
<View>
|
||||
<Text
|
||||
style={{ color: theme.primary, overflow: 'hidden' }}
|
||||
style={{
|
||||
...StyleConstants.FontStyle[size],
|
||||
color: theme.primary,
|
||||
overflow: 'hidden'
|
||||
}}
|
||||
children={children}
|
||||
numberOfLines={calNumberOfLines}
|
||||
onLayout={({ nativeEvent }) => {
|
||||
if (!heightOriginal) {
|
||||
setHeightOriginal(nativeEvent.layout.height)
|
||||
} else {
|
||||
if (!heightTruncated) {
|
||||
setHeightTruncated(nativeEvent.layout.height)
|
||||
} else {
|
||||
if (heightOriginal > heightTruncated) {
|
||||
setAllowExpand(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
onLayout={allowExpand === undefined ? onLayout : undefined}
|
||||
/>
|
||||
{allowExpand && (
|
||||
{allowExpand ? (
|
||||
<Pressable
|
||||
onPress={() => {
|
||||
layoutAnimation()
|
||||
@ -239,7 +246,7 @@ const ParseContent: React.FC<Props> = ({
|
||||
</Text>
|
||||
</LinearGradient>
|
||||
</Pressable>
|
||||
)}
|
||||
) : null}
|
||||
</View>
|
||||
)
|
||||
},
|
||||
@ -256,4 +263,4 @@ const ParseContent: React.FC<Props> = ({
|
||||
)
|
||||
}
|
||||
|
||||
export default ParseContent
|
||||
export default ParseHTML
|
@ -34,19 +34,22 @@ const Timelines: React.FC<Props> = ({ name, content }) => {
|
||||
.filter(p => (localRegistered ? true : p.page === 'RemotePublic'))
|
||||
.map(p => ({ key: p.page }))
|
||||
|
||||
const renderScene = ({
|
||||
route
|
||||
}: {
|
||||
route: {
|
||||
key: App.Pages
|
||||
}
|
||||
}) => {
|
||||
return (
|
||||
(localRegistered || route.key === 'RemotePublic') && (
|
||||
<Timeline page={route.key} />
|
||||
const renderScene = useCallback(
|
||||
({
|
||||
route
|
||||
}: {
|
||||
route: {
|
||||
key: App.Pages
|
||||
}
|
||||
}) => {
|
||||
return (
|
||||
(localRegistered || route.key === 'RemotePublic') && (
|
||||
<Timeline page={route.key} />
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
[localRegistered]
|
||||
)
|
||||
|
||||
const screenComponent = useCallback(
|
||||
() => (
|
||||
|
@ -29,6 +29,7 @@ export interface Props {
|
||||
toot?: Mastodon.Status
|
||||
account?: string
|
||||
disableRefresh?: boolean
|
||||
disableInfinity?: boolean
|
||||
}
|
||||
|
||||
const Timeline: React.FC<Props> = ({
|
||||
@ -37,7 +38,8 @@ const Timeline: React.FC<Props> = ({
|
||||
list,
|
||||
toot,
|
||||
account,
|
||||
disableRefresh = false
|
||||
disableRefresh = false,
|
||||
disableInfinity = false
|
||||
}) => {
|
||||
const queryKey: QueryKey.Timeline = [
|
||||
page,
|
||||
@ -152,11 +154,11 @@ const Timeline: React.FC<Props> = ({
|
||||
[status]
|
||||
)
|
||||
const onEndReached = useCallback(
|
||||
() => !disableRefresh && !isFetchingNextPage && fetchNextPage(),
|
||||
() => !disableInfinity && !isFetchingNextPage && fetchNextPage(),
|
||||
[isFetchingNextPage]
|
||||
)
|
||||
const ListFooterComponent = useCallback(
|
||||
() => <TimelineEnd hasNextPage={!disableRefresh ? hasNextPage : false} />,
|
||||
() => <TimelineEnd hasNextPage={!disableInfinity ? hasNextPage : false} />,
|
||||
[hasNextPage]
|
||||
)
|
||||
const refreshControl = useMemo(
|
||||
@ -197,6 +199,10 @@ const Timeline: React.FC<Props> = ({
|
||||
{...(!disableRefresh && { refreshControl })}
|
||||
ItemSeparatorComponent={ItemSeparatorComponent}
|
||||
{...(toot && isSuccess && { onScrollToIndexFailed })}
|
||||
maintainVisibleContentPosition={{
|
||||
minIndexForVisible: 0,
|
||||
autoscrollToTopThreshold: 2
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@ -207,4 +213,6 @@ const styles = StyleSheet.create({
|
||||
}
|
||||
})
|
||||
|
||||
// Timeline.whyDidYouRender = true
|
||||
|
||||
export default Timeline
|
||||
|
@ -111,7 +111,7 @@ const TimelineDefault: React.FC<Props> = ({
|
||||
const styles = StyleSheet.create({
|
||||
statusView: {
|
||||
padding: StyleConstants.Spacing.Global.PagePadding,
|
||||
paddingBottom: StyleConstants.Spacing.M
|
||||
paddingBottom: StyleConstants.Spacing.S
|
||||
},
|
||||
header: {
|
||||
flex: 1,
|
||||
|
@ -1,14 +1,14 @@
|
||||
import React from 'react'
|
||||
import { StyleSheet, Text, View } from 'react-native'
|
||||
import { Feather } from '@expo/vector-icons'
|
||||
|
||||
import Emojis from '@components/Timelines/Timeline/Shared/Emojis'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import React, { useCallback, useMemo } from 'react'
|
||||
import { Pressable, StyleSheet, View } from 'react-native'
|
||||
import { ParseEmojis } from '@root/components/Parse'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
|
||||
export interface Props {
|
||||
account: Mastodon.Account
|
||||
action: 'favourite' | 'follow' | 'mention' | 'poll' | 'reblog' | 'pinned'
|
||||
action: 'favourite' | 'follow' | 'poll' | 'reblog' | 'pinned' | 'mention'
|
||||
notification?: boolean
|
||||
}
|
||||
|
||||
@ -18,96 +18,106 @@ const TimelineActioned: React.FC<Props> = ({
|
||||
notification = false
|
||||
}) => {
|
||||
const { theme } = useTheme()
|
||||
const navigation = useNavigation()
|
||||
const name = account.display_name || account.username
|
||||
const iconColor = theme.primary
|
||||
|
||||
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
|
||||
name='heart'
|
||||
size={StyleConstants.Font.Size.S}
|
||||
color={iconColor}
|
||||
style={styles.icon}
|
||||
/>
|
||||
)
|
||||
content = `${name} 喜欢了你的嘟嘟`
|
||||
break
|
||||
case 'follow':
|
||||
icon = (
|
||||
<Feather
|
||||
name='user-plus'
|
||||
size={StyleConstants.Font.Size.S}
|
||||
color={iconColor}
|
||||
style={styles.icon}
|
||||
/>
|
||||
)
|
||||
content = `${name} 开始关注你`
|
||||
break
|
||||
case 'poll':
|
||||
icon = (
|
||||
<Feather
|
||||
name='bar-chart-2'
|
||||
size={StyleConstants.Font.Size.S}
|
||||
color={iconColor}
|
||||
style={styles.icon}
|
||||
/>
|
||||
)
|
||||
content = `你参与的投票已结束`
|
||||
break
|
||||
case 'reblog':
|
||||
icon = (
|
||||
<Feather
|
||||
name='repeat'
|
||||
size={StyleConstants.Font.Size.S}
|
||||
color={iconColor}
|
||||
style={styles.icon}
|
||||
/>
|
||||
)
|
||||
content = `${name} 转嘟了${notification ? '你的嘟嘟' : ''}`
|
||||
break
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.actioned}>
|
||||
{icon}
|
||||
{content && (
|
||||
<View style={styles.content}>
|
||||
{account.emojis ? (
|
||||
<Emojis content={content} emojis={account.emojis} size='S' />
|
||||
) : (
|
||||
<Text>{content}</Text>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
const content = (content: string) => (
|
||||
<ParseEmojis content={content} emojis={account.emojis} size='S' />
|
||||
)
|
||||
|
||||
const onPress = useCallback(() => {
|
||||
navigation.push('Screen-Shared-Account', { account })
|
||||
}, [])
|
||||
|
||||
const children = useMemo(() => {
|
||||
switch (action) {
|
||||
case 'pinned':
|
||||
return (
|
||||
<>
|
||||
<Feather
|
||||
name='anchor'
|
||||
size={StyleConstants.Font.Size.S}
|
||||
color={iconColor}
|
||||
style={styles.icon}
|
||||
/>
|
||||
{content('置顶')}
|
||||
</>
|
||||
)
|
||||
break
|
||||
case 'favourite':
|
||||
return (
|
||||
<>
|
||||
<Feather
|
||||
name='heart'
|
||||
size={StyleConstants.Font.Size.S}
|
||||
color={iconColor}
|
||||
style={styles.icon}
|
||||
/>
|
||||
<Pressable onPress={onPress}>
|
||||
{content(`${name} 喜欢了你的嘟嘟`)}
|
||||
</Pressable>
|
||||
</>
|
||||
)
|
||||
break
|
||||
case 'follow':
|
||||
return (
|
||||
<>
|
||||
<Feather
|
||||
name='user-plus'
|
||||
size={StyleConstants.Font.Size.S}
|
||||
color={iconColor}
|
||||
style={styles.icon}
|
||||
/>
|
||||
<Pressable onPress={onPress}>
|
||||
{content(`${name} 开始关注你`)}
|
||||
</Pressable>
|
||||
</>
|
||||
)
|
||||
break
|
||||
case 'poll':
|
||||
return (
|
||||
<>
|
||||
<Feather
|
||||
name='bar-chart-2'
|
||||
size={StyleConstants.Font.Size.S}
|
||||
color={iconColor}
|
||||
style={styles.icon}
|
||||
/>
|
||||
{content('你参与的投票已结束')}
|
||||
</>
|
||||
)
|
||||
break
|
||||
case 'reblog':
|
||||
return (
|
||||
<>
|
||||
<Feather
|
||||
name='repeat'
|
||||
size={StyleConstants.Font.Size.S}
|
||||
color={iconColor}
|
||||
style={styles.icon}
|
||||
/>
|
||||
<Pressable onPress={onPress}>
|
||||
{content(`${name} 转嘟了${notification ? '你的嘟嘟' : ''}`)}
|
||||
</Pressable>
|
||||
</>
|
||||
)
|
||||
break
|
||||
}
|
||||
}, [])
|
||||
|
||||
return <View style={styles.actioned} children={children} />
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
actioned: {
|
||||
flexDirection: 'row',
|
||||
marginBottom: StyleConstants.Spacing.S
|
||||
marginBottom: StyleConstants.Spacing.S,
|
||||
paddingLeft: StyleConstants.Avatar.M - StyleConstants.Font.Size.S,
|
||||
paddingRight: StyleConstants.Spacing.Global.PagePadding
|
||||
},
|
||||
icon: {
|
||||
marginLeft: StyleConstants.Avatar.M - StyleConstants.Font.Size.S,
|
||||
marginRight: StyleConstants.Spacing.S
|
||||
},
|
||||
content: {
|
||||
flexDirection: 'row'
|
||||
paddingRight: StyleConstants.Spacing.S
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -1,16 +1,15 @@
|
||||
import client from '@api/client'
|
||||
import haptics from '@components/haptics'
|
||||
import { TimelineData } from '@components/Timelines/Timeline'
|
||||
import { toast } from '@components/toast'
|
||||
import { Feather } from '@expo/vector-icons'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import { findIndex } from 'lodash'
|
||||
import React, { useCallback, useMemo } from 'react'
|
||||
import { ActionSheetIOS, Pressable, StyleSheet, Text, View } from 'react-native'
|
||||
import { useMutation, useQueryClient } from 'react-query'
|
||||
import { Feather } from '@expo/vector-icons'
|
||||
|
||||
import client from '@api/client'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import { toast } from '@components/toast'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { findIndex } from 'lodash'
|
||||
import { TimelineData } from '../../Timeline'
|
||||
import haptics from '@root/components/haptics'
|
||||
|
||||
const fireMutation = async ({
|
||||
id,
|
||||
@ -35,10 +34,8 @@ const fireMutation = async ({
|
||||
}) // bug in response from Mastodon
|
||||
|
||||
if (!res.body[stateKey] === prevState) {
|
||||
toast({ type: 'success', content: '功能成功' })
|
||||
return Promise.resolve(res.body)
|
||||
} else {
|
||||
toast({ type: 'error', content: '功能错误' })
|
||||
return Promise.reject()
|
||||
}
|
||||
break
|
||||
@ -64,6 +61,7 @@ const TimelineActions: React.FC<Props> = ({ queryKey, status, reblog }) => {
|
||||
queryClient.cancelQueries(queryKey)
|
||||
const oldData = queryClient.getQueryData(queryKey)
|
||||
|
||||
haptics('Success')
|
||||
switch (type) {
|
||||
case 'favourite':
|
||||
case 'reblog':
|
||||
@ -111,7 +109,6 @@ const TimelineActions: React.FC<Props> = ({ queryKey, status, reblog }) => {
|
||||
|
||||
return old
|
||||
})
|
||||
haptics('Success')
|
||||
break
|
||||
}
|
||||
|
||||
@ -175,8 +172,8 @@ const TimelineActions: React.FC<Props> = ({ queryKey, status, reblog }) => {
|
||||
'com.apple.UIKit.activity.OpenInIBooks'
|
||||
]
|
||||
},
|
||||
() => haptics('Success'),
|
||||
() => haptics('Error')
|
||||
() => haptics('Error'),
|
||||
() => haptics('Success')
|
||||
),
|
||||
[]
|
||||
)
|
||||
@ -294,12 +291,13 @@ const styles = StyleSheet.create({
|
||||
width: '100%',
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
marginTop: StyleConstants.Spacing.M
|
||||
marginTop: StyleConstants.Spacing.S
|
||||
},
|
||||
action: {
|
||||
width: '20%',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center'
|
||||
justifyContent: 'center',
|
||||
paddingVertical: StyleConstants.Spacing.S
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import Button from '@components/Button'
|
||||
import openLink from '@root/utils/openLink'
|
||||
import { StyleConstants } from '@root/utils/styles/constants'
|
||||
import { useTheme } from '@root/utils/styles/ThemeManager'
|
||||
import openLink from '@components/openLink'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import { Surface } from 'gl-react-expo'
|
||||
import { Blurhash } from 'gl-react-blurhash'
|
||||
import React from 'react'
|
||||
@ -38,7 +38,7 @@ const AttachmentUnsupported: React.FC<Props> = ({
|
||||
{ color: attachment.blurhash ? theme.background : theme.primary }
|
||||
]}
|
||||
>
|
||||
文件不支持
|
||||
文件读取错误
|
||||
</Text>
|
||||
{attachment.remote_url ? (
|
||||
<Button
|
||||
|
@ -1,8 +1,8 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react'
|
||||
import { Image, Pressable, StyleSheet, Text, View } from 'react-native'
|
||||
import openLink from '@components/openLink'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import openLink from '@root/utils/openLink'
|
||||
import { Surface } from 'gl-react-expo'
|
||||
import { Blurhash } from 'gl-react-blurhash'
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { ParseHTML } from '@components/Parse'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import React from 'react'
|
||||
import { View } from 'react-native'
|
||||
import ParseContent from '@components/ParseContent'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
|
||||
export interface Props {
|
||||
status: Mastodon.Status
|
||||
@ -18,28 +18,28 @@ const TimelineContent: React.FC<Props> = ({
|
||||
<>
|
||||
{status.spoiler_text ? (
|
||||
<>
|
||||
<ParseContent
|
||||
content={status.spoiler_text}
|
||||
size={highlighted ? 'L' : 'M'}
|
||||
emojis={status.emojis}
|
||||
mentions={status.mentions}
|
||||
tags={status.tags}
|
||||
numberOfLines={999}
|
||||
/>
|
||||
<View style={{ marginTop: StyleConstants.Font.Size.M }}>
|
||||
<ParseContent
|
||||
content={status.content}
|
||||
<View style={{ marginBottom: StyleConstants.Font.Size.M }}>
|
||||
<ParseHTML
|
||||
content={status.spoiler_text}
|
||||
size={highlighted ? 'L' : 'M'}
|
||||
emojis={status.emojis}
|
||||
mentions={status.mentions}
|
||||
tags={status.tags}
|
||||
numberOfLines={1}
|
||||
expandHint='隐藏内容'
|
||||
numberOfLines={999}
|
||||
/>
|
||||
</View>
|
||||
<ParseHTML
|
||||
content={status.content}
|
||||
size={highlighted ? 'L' : 'M'}
|
||||
emojis={status.emojis}
|
||||
mentions={status.mentions}
|
||||
tags={status.tags}
|
||||
numberOfLines={0}
|
||||
expandHint='隐藏内容'
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<ParseContent
|
||||
<ParseHTML
|
||||
content={status.content}
|
||||
size={highlighted ? 'L' : 'M'}
|
||||
emojis={status.emojis}
|
||||
|
@ -1,72 +0,0 @@
|
||||
import React from 'react'
|
||||
import { Image, StyleSheet, Text } from 'react-native'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
|
||||
const regexEmoji = new RegExp(/(:[A-Za-z0-9_]+:)/)
|
||||
|
||||
export interface Props {
|
||||
content: string
|
||||
emojis: Mastodon.Emoji[]
|
||||
size?: 'S' | 'M' | 'L'
|
||||
fontBold?: boolean
|
||||
numberOfLines?: number
|
||||
}
|
||||
|
||||
const Emojis: React.FC<Props> = ({
|
||||
content,
|
||||
emojis,
|
||||
size = 'M',
|
||||
fontBold = false,
|
||||
numberOfLines
|
||||
}) => {
|
||||
const { theme } = useTheme()
|
||||
const styles = StyleSheet.create({
|
||||
text: {
|
||||
color: theme.primary,
|
||||
...StyleConstants.FontStyle[size],
|
||||
...(fontBold && { fontWeight: StyleConstants.Font.Weight.Bold })
|
||||
},
|
||||
image: {
|
||||
width: StyleConstants.Font.Size[size],
|
||||
height: StyleConstants.Font.Size[size],
|
||||
marginBottom: -2 // hacking
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<Text numberOfLines={numberOfLines || undefined}>
|
||||
{content.split(regexEmoji).map((str, i) => {
|
||||
if (str.match(regexEmoji)) {
|
||||
const emojiShortcode = str.split(regexEmoji)[1]
|
||||
const emojiIndex = emojis.findIndex(emoji => {
|
||||
return emojiShortcode === `:${emoji.shortcode}:`
|
||||
})
|
||||
return emojiIndex === -1 ? (
|
||||
<Text key={i} style={styles.text}>
|
||||
{emojiShortcode}
|
||||
</Text>
|
||||
) : (
|
||||
<Image
|
||||
key={i}
|
||||
resizeMode='contain'
|
||||
source={{ uri: emojis[emojiIndex].url }}
|
||||
style={[styles.image]}
|
||||
/>
|
||||
)
|
||||
} else {
|
||||
return str ? (
|
||||
<Text key={i} style={styles.text}>
|
||||
{str}
|
||||
</Text>
|
||||
) : (
|
||||
undefined
|
||||
)
|
||||
}
|
||||
})}
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
|
||||
// export default React.memo(Emojis, () => true)
|
||||
export default Emojis
|
@ -1,15 +1,14 @@
|
||||
import client from '@api/client'
|
||||
import haptics from '@components/haptics'
|
||||
import { ParseEmojis } from '@components/Parse'
|
||||
import relativeTime from '@components/relativeTime'
|
||||
import { toast } from '@components/toast'
|
||||
import { Feather } from '@expo/vector-icons'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useCallback, useMemo } from 'react'
|
||||
import { Pressable, StyleSheet, Text, View } from 'react-native'
|
||||
import { useMutation, useQueryClient } from 'react-query'
|
||||
import client from '@api/client'
|
||||
import { toast } from '@components/toast'
|
||||
|
||||
import relativeTime from '@utils/relativeTime'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import Emojis from '@components/Timelines/Timeline/Shared/Emojis'
|
||||
import haptics from '@root/components/haptics'
|
||||
|
||||
export interface Props {
|
||||
queryKey: QueryKey.Timeline
|
||||
@ -43,6 +42,7 @@ const HeaderConversation: React.FC<Props> = ({ queryKey, conversation }) => {
|
||||
queryClient.cancelQueries(queryKey)
|
||||
const oldData = queryClient.getQueryData(queryKey)
|
||||
|
||||
haptics('Success')
|
||||
queryClient.setQueryData(queryKey, (old: any) =>
|
||||
old.pages.map((paging: any) => ({
|
||||
toots: paging.toots.filter(
|
||||
@ -51,7 +51,6 @@ const HeaderConversation: React.FC<Props> = ({ queryKey, conversation }) => {
|
||||
pointer: paging.pointer
|
||||
}))
|
||||
)
|
||||
haptics('Success')
|
||||
|
||||
return oldData
|
||||
},
|
||||
@ -80,26 +79,17 @@ const HeaderConversation: React.FC<Props> = ({ queryKey, conversation }) => {
|
||||
return (
|
||||
<View style={styles.base}>
|
||||
<View style={styles.nameAndDate}>
|
||||
<View style={styles.name}>
|
||||
{conversation.accounts[0].emojis ? (
|
||||
<Emojis
|
||||
<View style={styles.namdAndAccount}>
|
||||
<Text numberOfLines={1}>
|
||||
<ParseEmojis
|
||||
content={
|
||||
conversation.accounts[0].display_name ||
|
||||
conversation.accounts[0].username
|
||||
}
|
||||
emojis={conversation.accounts[0].emojis}
|
||||
size='M'
|
||||
fontBold={true}
|
||||
fontBold
|
||||
/>
|
||||
) : (
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
style={[styles.nameWithoutEmoji, { color: theme.primary }]}
|
||||
>
|
||||
{conversation.accounts[0].display_name ||
|
||||
conversation.accounts[0].username}
|
||||
</Text>
|
||||
)}
|
||||
</Text>
|
||||
<Text
|
||||
style={[styles.account, { color: theme.secondary }]}
|
||||
numberOfLines={1}
|
||||
@ -136,7 +126,7 @@ const styles = StyleSheet.create({
|
||||
nameAndDate: {
|
||||
width: '80%'
|
||||
},
|
||||
name: {
|
||||
namdAndAccount: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center'
|
||||
},
|
||||
@ -144,10 +134,6 @@ const styles = StyleSheet.create({
|
||||
flexShrink: 1,
|
||||
marginLeft: StyleConstants.Spacing.XS
|
||||
},
|
||||
nameWithoutEmoji: {
|
||||
...StyleConstants.FontStyle.M,
|
||||
fontWeight: StyleConstants.Font.Weight.Bold
|
||||
},
|
||||
meta: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
|
@ -1,17 +1,17 @@
|
||||
import BottomSheet from '@components/BottomSheet'
|
||||
import openLink from '@components/openLink'
|
||||
import { ParseEmojis } from '@components/Parse'
|
||||
import relativeTime from '@components/relativeTime'
|
||||
import { Feather } from '@expo/vector-icons'
|
||||
import { getLocalUrl } from '@utils/slices/instancesSlice'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import HeaderDefaultActionsAccount from '@components/Timelines/Timeline/Shared/HeaderDefault/ActionsAccount'
|
||||
import HeaderDefaultActionsDomain from '@components/Timelines/Timeline/Shared/HeaderDefault/ActionsDomain'
|
||||
import HeaderDefaultActionsStatus from '@components/Timelines/Timeline/Shared/HeaderDefault/ActionsStatus'
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { Pressable, StyleSheet, Text, View } from 'react-native'
|
||||
import { Feather } from '@expo/vector-icons'
|
||||
import Emojis from '@components/Timelines/Timeline/Shared/Emojis'
|
||||
import relativeTime from '@utils/relativeTime'
|
||||
import { getLocalUrl } from '@utils/slices/instancesSlice'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import BottomSheet from '@components/BottomSheet'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import HeaderDefaultActionsAccount from '@components/Timelines/Timeline/Shared/HeaderDefault/ActionsAccount'
|
||||
import HeaderDefaultActionsStatus from '@components/Timelines/Timeline/Shared/HeaderDefault/ActionsStatus'
|
||||
import HeaderDefaultActionsDomain from '@components/Timelines/Timeline/Shared/HeaderDefault/ActionsDomain'
|
||||
import openLink from '@root/utils/openLink'
|
||||
|
||||
export interface Props {
|
||||
queryKey?: QueryKey.Timeline
|
||||
@ -69,21 +69,9 @@ const TimelineHeaderDefault: React.FC<Props> = ({
|
||||
<View style={styles.base}>
|
||||
<View style={queryKey ? { flexBasis: '80%' } : { flexBasis: '100%' }}>
|
||||
<View style={styles.nameAndAccount}>
|
||||
{emojis?.length ? (
|
||||
<Emojis
|
||||
content={name}
|
||||
emojis={emojis}
|
||||
size='M'
|
||||
fontBold={true}
|
||||
/>
|
||||
) : (
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
style={[styles.nameWithoutEmoji, { color: theme.primary }]}
|
||||
>
|
||||
{name}
|
||||
</Text>
|
||||
)}
|
||||
<Text numberOfLines={1}>
|
||||
<ParseEmojis content={name} emojis={emojis} fontBold />
|
||||
</Text>
|
||||
<Text
|
||||
style={[styles.account, { color: theme.secondary }]}
|
||||
numberOfLines={1}
|
||||
@ -163,7 +151,8 @@ const TimelineHeaderDefault: React.FC<Props> = ({
|
||||
const styles = StyleSheet.create({
|
||||
base: {
|
||||
flex: 1,
|
||||
flexDirection: 'row'
|
||||
flexDirection: 'row',
|
||||
alignItems: 'baseline'
|
||||
},
|
||||
nameAndMeta: {
|
||||
flexBasis: '80%'
|
||||
@ -172,10 +161,6 @@ const styles = StyleSheet.create({
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center'
|
||||
},
|
||||
nameWithoutEmoji: {
|
||||
...StyleConstants.FontStyle.M,
|
||||
fontWeight: StyleConstants.Font.Weight.Bold
|
||||
},
|
||||
account: {
|
||||
flex: 1,
|
||||
marginLeft: StyleConstants.Spacing.XS
|
||||
@ -199,7 +184,8 @@ const styles = StyleSheet.create({
|
||||
action: {
|
||||
flexBasis: '20%',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center'
|
||||
justifyContent: 'center',
|
||||
paddingBottom: StyleConstants.Spacing.S
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -1,17 +1,17 @@
|
||||
import client from '@api/client'
|
||||
import { Feather } from '@expo/vector-icons'
|
||||
import haptics from '@components/haptics'
|
||||
import openLink from '@components/openLink'
|
||||
import { ParseEmojis } from '@components/Parse'
|
||||
import relativeTime from '@components/relativeTime'
|
||||
import { toast } from '@components/toast'
|
||||
import { relationshipFetch } from '@utils/fetches/relationshipFetch'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { Pressable, StyleSheet, Text, View } from 'react-native'
|
||||
import { Chase } from 'react-native-animated-spinkit'
|
||||
import { useQuery } from 'react-query'
|
||||
import client from '@api/client'
|
||||
import { Feather } from '@expo/vector-icons'
|
||||
import Emojis from '@components/Timelines/Timeline/Shared/Emojis'
|
||||
import { toast } from '@components/toast'
|
||||
import openLink from '@root/utils/openLink'
|
||||
import relativeTime from '@utils/relativeTime'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { relationshipFetch } from '@utils/fetches/relationshipFetch'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import haptics from '@root/components/haptics'
|
||||
|
||||
export interface Props {
|
||||
notification: Mastodon.Notification
|
||||
@ -129,16 +129,9 @@ const TimelineHeaderNotification: React.FC<Props> = ({ notification }) => {
|
||||
<View style={styles.base}>
|
||||
<View style={styles.nameAndMeta}>
|
||||
<View style={styles.nameAndAccount}>
|
||||
{emojis?.length ? (
|
||||
<Emojis content={name} emojis={emojis} size='M' fontBold={true} />
|
||||
) : (
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
style={[styles.nameWithoutEmoji, { color: theme.primary }]}
|
||||
>
|
||||
{name}
|
||||
</Text>
|
||||
)}
|
||||
<Text numberOfLines={1}>
|
||||
<ParseEmojis content={name} emojis={emojis} fontBold />
|
||||
</Text>
|
||||
<Text
|
||||
style={[styles.account, { color: theme.secondary }]}
|
||||
numberOfLines={1}
|
||||
@ -194,10 +187,6 @@ const styles = StyleSheet.create({
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center'
|
||||
},
|
||||
nameWithoutEmoji: {
|
||||
...StyleConstants.FontStyle.M,
|
||||
fontWeight: StyleConstants.Font.Weight.Bold
|
||||
},
|
||||
account: {
|
||||
flexShrink: 1,
|
||||
marginLeft: StyleConstants.Spacing.XS
|
||||
|
@ -1,18 +1,16 @@
|
||||
import client from '@api/client'
|
||||
import Button from '@components/Button'
|
||||
import haptics from '@components/haptics'
|
||||
import relativeTime from '@components/relativeTime'
|
||||
import { TimelineData } from '@components/Timelines/Timeline'
|
||||
import { Feather } from '@expo/vector-icons'
|
||||
import { ParseEmojis } from '@root/components/Parse'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import { findIndex } from 'lodash'
|
||||
import React, { useCallback, useMemo, useState } from 'react'
|
||||
import { Pressable, StyleSheet, Text, View } from 'react-native'
|
||||
import { useMutation, useQueryClient } from 'react-query'
|
||||
import client from '@api/client'
|
||||
import Button from '@components/Button'
|
||||
import { toast } from '@components/toast'
|
||||
import relativeTime from '@utils/relativeTime'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
|
||||
import Emojis from './Emojis'
|
||||
import { TimelineData } from '../../Timeline'
|
||||
import { findIndex } from 'lodash'
|
||||
import haptics from '@root/components/haptics'
|
||||
|
||||
const fireMutation = async ({
|
||||
id,
|
||||
@ -39,11 +37,6 @@ const fireMutation = async ({
|
||||
if (res.body.id === id) {
|
||||
return Promise.resolve(res.body as Mastodon.Poll)
|
||||
} else {
|
||||
toast({
|
||||
type: 'error',
|
||||
content: '投票失败,请重试',
|
||||
autoHide: false
|
||||
})
|
||||
return Promise.reject()
|
||||
}
|
||||
}
|
||||
@ -61,7 +54,7 @@ const TimelinePoll: React.FC<Props> = ({
|
||||
reblog,
|
||||
sameAccount
|
||||
}) => {
|
||||
const { theme } = useTheme()
|
||||
const { mode, theme } = useTheme()
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
const [allOptions, setAllOptions] = useState(
|
||||
@ -98,10 +91,13 @@ const TimelinePoll: React.FC<Props> = ({
|
||||
})
|
||||
|
||||
haptics('Success')
|
||||
},
|
||||
onError: () => {
|
||||
haptics('Error')
|
||||
}
|
||||
})
|
||||
|
||||
const pollButton = () => {
|
||||
const pollButton = useMemo(() => {
|
||||
if (!poll.expired) {
|
||||
if (!sameAccount && !poll.voted) {
|
||||
return (
|
||||
@ -131,7 +127,7 @@ const TimelinePoll: React.FC<Props> = ({
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [poll.expired, poll.voted, allOptions, mutation.isLoading])
|
||||
|
||||
const pollExpiration = useMemo(() => {
|
||||
if (poll.expired) {
|
||||
@ -147,7 +143,7 @@ const TimelinePoll: React.FC<Props> = ({
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
}, [])
|
||||
}, [mode])
|
||||
|
||||
const isSelected = useCallback(
|
||||
(index: number): any =>
|
||||
@ -157,101 +153,93 @@ const TimelinePoll: React.FC<Props> = ({
|
||||
[allOptions]
|
||||
)
|
||||
|
||||
const pollBodyDisallow = useMemo(() => {
|
||||
return poll.options.map((option, index) => (
|
||||
<View key={index} style={styles.optionContainer}>
|
||||
<View style={styles.optionContent}>
|
||||
<Feather
|
||||
style={styles.optionSelection}
|
||||
name={
|
||||
`${poll.own_votes?.includes(index) ? 'check-' : ''}${
|
||||
poll.multiple ? 'square' : 'circle'
|
||||
}` as any
|
||||
}
|
||||
size={StyleConstants.Font.Size.M}
|
||||
color={
|
||||
poll.own_votes?.includes(index) ? theme.primary : theme.disabled
|
||||
}
|
||||
/>
|
||||
<Text style={styles.optionText}>
|
||||
<ParseEmojis content={option.title} emojis={poll.emojis} />
|
||||
</Text>
|
||||
<Text style={[styles.optionPercentage, { color: theme.primary }]}>
|
||||
{poll.votes_count
|
||||
? Math.round((option.votes_count / poll.voters_count) * 100)
|
||||
: 0}
|
||||
%
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<View
|
||||
style={[
|
||||
styles.background,
|
||||
{
|
||||
width: `${Math.round(
|
||||
(option.votes_count / poll.voters_count) * 100
|
||||
)}%`,
|
||||
backgroundColor: theme.disabled
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</View>
|
||||
))
|
||||
}, [mode, poll.options])
|
||||
const pollBodyAllow = useMemo(() => {
|
||||
return poll.options.map((option, index) => (
|
||||
<Pressable
|
||||
key={index}
|
||||
style={styles.optionContainer}
|
||||
onPress={() => {
|
||||
haptics('Light')
|
||||
if (poll.multiple) {
|
||||
setAllOptions(allOptions.map((o, i) => (i === index ? !o : o)))
|
||||
} else {
|
||||
{
|
||||
const otherOptions =
|
||||
allOptions[index] === false ? false : undefined
|
||||
setAllOptions(
|
||||
allOptions.map((o, i) =>
|
||||
i === index
|
||||
? !o
|
||||
: otherOptions !== undefined
|
||||
? otherOptions
|
||||
: o
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<View style={[styles.optionContent]}>
|
||||
<Feather
|
||||
style={styles.optionSelection}
|
||||
name={isSelected(index)}
|
||||
size={StyleConstants.Font.Size.L}
|
||||
color={theme.primary}
|
||||
/>
|
||||
<Text style={styles.optionText}>
|
||||
<ParseEmojis content={option.title} emojis={poll.emojis} />
|
||||
</Text>
|
||||
</View>
|
||||
</Pressable>
|
||||
))
|
||||
}, [mode, allOptions])
|
||||
|
||||
return (
|
||||
<View style={styles.base}>
|
||||
{poll.options.map((option, index) =>
|
||||
poll.voted ? (
|
||||
<View key={index} style={styles.poll}>
|
||||
<View style={styles.optionSelected}>
|
||||
<Feather
|
||||
style={styles.voted}
|
||||
name={
|
||||
`${poll.own_votes!.includes(index) ? 'check-' : ''}${
|
||||
poll.multiple ? 'square' : 'circle'
|
||||
}` as any
|
||||
}
|
||||
size={StyleConstants.Font.Size.M}
|
||||
color={
|
||||
poll.own_votes!.includes(index)
|
||||
? theme.primary
|
||||
: theme.disabled
|
||||
}
|
||||
/>
|
||||
<View style={styles.contentSelected}>
|
||||
<Emojis
|
||||
content={option.title}
|
||||
emojis={poll.emojis}
|
||||
size='M'
|
||||
numberOfLines={2}
|
||||
/>
|
||||
</View>
|
||||
<Text style={[styles.percentage, { color: theme.primary }]}>
|
||||
{poll.votes_count
|
||||
? Math.round((option.votes_count / poll.voters_count) * 100)
|
||||
: 0}
|
||||
%
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<View
|
||||
style={[
|
||||
styles.background,
|
||||
{
|
||||
width: `${Math.round(
|
||||
(option.votes_count / poll.voters_count) * 100
|
||||
)}%`,
|
||||
backgroundColor: theme.disabled
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</View>
|
||||
) : (
|
||||
<View key={index} style={styles.poll}>
|
||||
<Pressable
|
||||
style={[styles.optionUnselected]}
|
||||
onPress={() => {
|
||||
haptics('Light')
|
||||
if (poll.multiple) {
|
||||
setAllOptions(
|
||||
allOptions.map((o, i) => (i === index ? !o : o))
|
||||
)
|
||||
} else {
|
||||
{
|
||||
const otherOptions =
|
||||
allOptions[index] === false ? false : undefined
|
||||
setAllOptions(
|
||||
allOptions.map((o, i) =>
|
||||
i === index
|
||||
? !o
|
||||
: otherOptions !== undefined
|
||||
? otherOptions
|
||||
: o
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Feather
|
||||
style={styles.votedNot}
|
||||
name={isSelected(index)}
|
||||
size={StyleConstants.Font.Size.L}
|
||||
color={theme.primary}
|
||||
/>
|
||||
<View style={styles.contentUnselected}>
|
||||
<Emojis
|
||||
content={option.title}
|
||||
emojis={poll.emojis}
|
||||
size='M'
|
||||
numberOfLines={2}
|
||||
/>
|
||||
</View>
|
||||
</Pressable>
|
||||
</View>
|
||||
)
|
||||
)}
|
||||
{poll.expired || poll.voted ? pollBodyDisallow : pollBodyAllow}
|
||||
<View style={styles.meta}>
|
||||
{pollButton()}
|
||||
{pollButton}
|
||||
<Text style={[styles.votes, { color: theme.secondary }]}>
|
||||
已投{poll.voters_count || 0}人{' • '}
|
||||
</Text>
|
||||
@ -265,39 +253,24 @@ const styles = StyleSheet.create({
|
||||
base: {
|
||||
marginTop: StyleConstants.Spacing.M
|
||||
},
|
||||
poll: {
|
||||
optionContainer: {
|
||||
flex: 1,
|
||||
minHeight: StyleConstants.Font.LineHeight.M * 2,
|
||||
paddingVertical: StyleConstants.Spacing.XS
|
||||
paddingVertical: StyleConstants.Spacing.S
|
||||
},
|
||||
optionSelected: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
paddingRight: StyleConstants.Spacing.M
|
||||
},
|
||||
optionUnselected: {
|
||||
optionContent: {
|
||||
flex: 1,
|
||||
flexDirection: 'row'
|
||||
},
|
||||
contentSelected: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingRight: StyleConstants.Spacing.S
|
||||
optionText: {
|
||||
flex: 1
|
||||
},
|
||||
contentUnselected: {
|
||||
flexShrink: 1
|
||||
},
|
||||
voted: {
|
||||
optionSelection: {
|
||||
marginRight: StyleConstants.Spacing.S
|
||||
},
|
||||
votedNot: {
|
||||
paddingRight: StyleConstants.Spacing.S
|
||||
},
|
||||
percentage: {
|
||||
...StyleConstants.FontStyle.M
|
||||
optionPercentage: {
|
||||
...StyleConstants.FontStyle.M,
|
||||
alignSelf: 'center',
|
||||
marginLeft: StyleConstants.Spacing.S
|
||||
},
|
||||
background: {
|
||||
height: StyleConstants.Spacing.XS,
|
||||
@ -314,7 +287,7 @@ const styles = StyleSheet.create({
|
||||
marginTop: StyleConstants.Spacing.XS
|
||||
},
|
||||
button: {
|
||||
marginRight: StyleConstants.Spacing.M
|
||||
marginRight: StyleConstants.Spacing.S
|
||||
},
|
||||
votes: {
|
||||
...StyleConstants.FontStyle.S
|
||||
|
@ -2,8 +2,9 @@ import * as Analytics from 'expo-firebase-analytics'
|
||||
import * as Sentry from 'sentry-expo'
|
||||
|
||||
const analytics = (event: string, params?: { [key: string]: string }) => {
|
||||
Analytics.logEvent(event, params).catch(error =>
|
||||
Sentry.Native.captureException(error)
|
||||
Analytics.logEvent(event, params).catch(
|
||||
error => {}
|
||||
// Sentry.Native.captureException(error)
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { store } from '@root/store'
|
||||
import { getSettingsBrowser } from '@utils/slices/settingsSlice'
|
||||
import * as Linking from 'expo-linking'
|
||||
import * as WebBrowser from 'expo-web-browser'
|
||||
import { getSettingsBrowser } from './slices/settingsSlice'
|
||||
|
||||
const openLink = async (url: string) => {
|
||||
switch (getSettingsBrowser(store.getState())) {
|
@ -12,7 +12,6 @@ import Logout from '@screens/Me/Root/Logout'
|
||||
import { useScrollToTop } from '@react-navigation/native'
|
||||
import { AccountState } from '../Shared/Account'
|
||||
import AccountNav from '../Shared/Account/Nav'
|
||||
import layoutAnimation from '@root/utils/styles/layoutAnimation'
|
||||
|
||||
const ScreenMeRoot: React.FC = () => {
|
||||
const localRegistered = useSelector(getLocalUrl)
|
||||
|
@ -1,9 +1,9 @@
|
||||
import analytics from '@components/analytics'
|
||||
import Button from '@components/Button'
|
||||
import ParseContent from '@components/ParseContent'
|
||||
import haptics from '@components/haptics'
|
||||
import { ParseHTML } from '@components/Parse'
|
||||
import { Feather } from '@expo/vector-icons'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import analytics from '@root/components/analytics'
|
||||
import haptics from '@root/components/haptics'
|
||||
import { applicationFetch } from '@utils/fetches/applicationFetch'
|
||||
import { instanceFetch } from '@utils/fetches/instanceFetch'
|
||||
import { loginLocal } from '@utils/slices/instancesSlice'
|
||||
@ -144,7 +144,12 @@ const Login: React.FC = () => {
|
||||
height={StyleConstants.Font.Size.M}
|
||||
shimmerColors={theme.shimmer}
|
||||
>
|
||||
<ParseContent content={content!} size={'M'} numberOfLines={5} />
|
||||
<ParseHTML
|
||||
content={content!}
|
||||
size={'M'}
|
||||
numberOfLines={5}
|
||||
expandHint='介绍'
|
||||
/>
|
||||
</ShimmerPlaceholder>
|
||||
</View>
|
||||
)
|
||||
|
@ -13,7 +13,6 @@ import BottomSheet from '@root/components/BottomSheet'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { getLocalAccountId } from '@root/utils/slices/instancesSlice'
|
||||
import HeaderDefaultActionsAccount from '@root/components/Timelines/Timeline/Shared/HeaderDefault/ActionsAccount'
|
||||
import layoutAnimation from '@root/utils/styles/layoutAnimation'
|
||||
|
||||
// Moved account example: https://m.cmx.im/web/accounts/27812
|
||||
|
||||
@ -113,12 +112,12 @@ const ScreenSharedAccount: React.FC<Props> = ({
|
||||
/>
|
||||
) : null}
|
||||
<ScrollView
|
||||
bounces={false}
|
||||
scrollEventThrottle={16}
|
||||
showsVerticalScrollIndicator={false}
|
||||
onScroll={Animated.event(
|
||||
[{ nativeEvent: { contentOffset: { y: scrollY } } }],
|
||||
{ useNativeDriver: false }
|
||||
)}
|
||||
scrollEventThrottle={8}
|
||||
>
|
||||
<AccountHeader
|
||||
accountState={accountState}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import client from '@root/api/client'
|
||||
import Button from '@root/components/Button'
|
||||
import haptics from '@root/components/haptics'
|
||||
import { toast } from '@root/components/toast'
|
||||
import { relationshipFetch } from '@root/utils/fetches/relationshipFetch'
|
||||
import { StyleConstants } from '@root/utils/styles/constants'
|
||||
@ -55,8 +56,14 @@ const AccountInformationActions: React.FC<Props> = ({ account }) => {
|
||||
}, [account])
|
||||
const queryClient = useQueryClient()
|
||||
const mutation = useMutation(fireMutation, {
|
||||
onSuccess: data => queryClient.setQueryData(relationshipQueryKey, data),
|
||||
onError: () => toast({ type: 'error', content: '关注失败,请重试' })
|
||||
onSuccess: data => {
|
||||
haptics('Success')
|
||||
queryClient.setQueryData(relationshipQueryKey, data)
|
||||
},
|
||||
onError: () => {
|
||||
haptics('Error')
|
||||
toast({ type: 'error', content: '关注失败,请重试' })
|
||||
}
|
||||
})
|
||||
|
||||
const mainAction = useMemo(() => {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { ParseHTML } from '@components/Parse'
|
||||
import { Feather } from '@expo/vector-icons'
|
||||
import ParseContent from '@root/components/ParseContent'
|
||||
import { StyleConstants } from '@root/utils/styles/constants'
|
||||
import { useTheme } from '@root/utils/styles/ThemeManager'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React from 'react'
|
||||
import { StyleSheet, View } from 'react-native'
|
||||
|
||||
@ -20,7 +20,7 @@ const AccountInformationFields: React.FC<Props> = ({ account }) => {
|
||||
style={[styles.field, { borderBottomColor: theme.border }]}
|
||||
>
|
||||
<View style={[styles.fieldLeft, { borderRightColor: theme.border }]}>
|
||||
<ParseContent
|
||||
<ParseHTML
|
||||
content={field.name}
|
||||
size={'M'}
|
||||
emojis={account.emojis}
|
||||
@ -36,7 +36,7 @@ const AccountInformationFields: React.FC<Props> = ({ account }) => {
|
||||
) : null}
|
||||
</View>
|
||||
<View style={styles.fieldRight}>
|
||||
<ParseContent
|
||||
<ParseHTML
|
||||
content={field.value}
|
||||
size={'M'}
|
||||
emojis={account.emojis}
|
||||
|
@ -1,9 +1,9 @@
|
||||
import Emojis from '@root/components/Timelines/Timeline/Shared/Emojis'
|
||||
import { StyleConstants } from '@root/utils/styles/constants'
|
||||
import { useTheme } from '@root/utils/styles/ThemeManager'
|
||||
import { ParseEmojis } from '@components/Parse'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import { LinearGradient } from 'expo-linear-gradient'
|
||||
import React, { forwardRef } from 'react'
|
||||
import { StyleSheet, Text, View } from 'react-native'
|
||||
import { StyleSheet } from 'react-native'
|
||||
import ShimmerPlaceholder, {
|
||||
createShimmerPlaceholder
|
||||
} from 'react-native-shimmer-placeholder'
|
||||
@ -28,26 +28,14 @@ const AccountInformationName = forwardRef<ShimmerPlaceholder, Props>(
|
||||
style={styles.name}
|
||||
shimmerColors={theme.shimmer}
|
||||
>
|
||||
<View>
|
||||
{account?.emojis ? (
|
||||
<Emojis
|
||||
content={account?.display_name || account?.username}
|
||||
emojis={account.emojis}
|
||||
size='L'
|
||||
fontBold={true}
|
||||
/>
|
||||
) : (
|
||||
<Text
|
||||
style={{
|
||||
color: theme.primary,
|
||||
...StyleConstants.FontStyle.L,
|
||||
fontWeight: StyleConstants.Font.Weight.Bold
|
||||
}}
|
||||
>
|
||||
{account?.display_name || account?.username}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
{account ? (
|
||||
<ParseEmojis
|
||||
content={account.display_name || account.username}
|
||||
emojis={account.emojis}
|
||||
size='L'
|
||||
fontBold
|
||||
/>
|
||||
) : null}
|
||||
</ShimmerPlaceholder>
|
||||
)
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import ParseContent from '@root/components/ParseContent'
|
||||
import { StyleConstants } from '@root/utils/styles/constants'
|
||||
import { ParseHTML } from '@components/Parse'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import React from 'react'
|
||||
import { StyleSheet, View } from 'react-native'
|
||||
|
||||
@ -10,11 +10,7 @@ export interface Props {
|
||||
const AccountInformationNotes: React.FC<Props> = ({ account }) => {
|
||||
return (
|
||||
<View style={styles.note}>
|
||||
<ParseContent
|
||||
content={account.note!}
|
||||
size={'M'}
|
||||
emojis={account.emojis}
|
||||
/>
|
||||
<ParseHTML content={account.note!} size={'M'} emojis={account.emojis} />
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
import Emojis from '@root/components/Timelines/Timeline/Shared/Emojis'
|
||||
import { StyleConstants } from '@root/utils/styles/constants'
|
||||
import { useTheme } from '@root/utils/styles/ThemeManager'
|
||||
import { ParseEmojis } from '@components/Parse'
|
||||
import { AccountState } from '@screens/Shared/Account'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React from 'react'
|
||||
import { Animated, Dimensions, StyleSheet, Text, View } from 'react-native'
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
||||
import { AccountState } from '../Account'
|
||||
|
||||
export interface Props {
|
||||
accountState: AccountState
|
||||
@ -59,24 +59,15 @@ const AccountNav: React.FC<Props> = ({ accountState, scrollY, account }) => {
|
||||
}
|
||||
]}
|
||||
>
|
||||
{account?.emojis ? (
|
||||
<Emojis
|
||||
content={account?.display_name || account?.username}
|
||||
emojis={account.emojis}
|
||||
size='M'
|
||||
fontBold={true}
|
||||
/>
|
||||
) : (
|
||||
<Text
|
||||
style={{
|
||||
color: theme.primary,
|
||||
...StyleConstants.FontStyle.M,
|
||||
fontWeight: StyleConstants.Font.Weight.Bold
|
||||
}}
|
||||
>
|
||||
{account?.display_name || account?.username}
|
||||
{account ? (
|
||||
<Text numberOfLines={1}>
|
||||
<ParseEmojis
|
||||
content={account.display_name || account.username}
|
||||
emojis={account.emojis}
|
||||
fontBold
|
||||
/>
|
||||
</Text>
|
||||
)}
|
||||
) : null}
|
||||
</Animated.View>
|
||||
</View>
|
||||
</Animated.View>
|
||||
|
@ -19,6 +19,9 @@ const AccountToots: React.FC<Props> = ({
|
||||
accountDispatch,
|
||||
id
|
||||
}) => {
|
||||
const headerHeight = useSafeAreaInsets().top + 44
|
||||
const footerHeight = useSafeAreaInsets().bottom + useBottomTabBarHeight()
|
||||
|
||||
const routes: { key: App.Pages }[] = [
|
||||
{ key: 'Account_Default' },
|
||||
{ key: 'Account_All' },
|
||||
@ -37,20 +40,11 @@ const AccountToots: React.FC<Props> = ({
|
||||
},
|
||||
[]
|
||||
)
|
||||
const headerHeight = useSafeAreaInsets().top + 44
|
||||
const footerHeight = useSafeAreaInsets().bottom + useBottomTabBarHeight()
|
||||
|
||||
return (
|
||||
<TabView
|
||||
lazy
|
||||
swipeEnabled
|
||||
style={[
|
||||
styles.base,
|
||||
{
|
||||
height:
|
||||
Dimensions.get('window').height - headerHeight - footerHeight - 33
|
||||
}
|
||||
]}
|
||||
renderScene={renderScene}
|
||||
renderTabBar={() => null}
|
||||
initialLayout={{ width: Dimensions.get('window').width }}
|
||||
@ -58,6 +52,16 @@ const AccountToots: React.FC<Props> = ({
|
||||
onIndexChange={index =>
|
||||
accountDispatch({ type: 'segmentedIndex', payload: index })
|
||||
}
|
||||
style={[
|
||||
styles.base,
|
||||
{
|
||||
height:
|
||||
Dimensions.get('window').height -
|
||||
headerHeight -
|
||||
footerHeight -
|
||||
(33 + StyleConstants.Spacing.Global.PagePadding * 2)
|
||||
}
|
||||
]}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@ -68,4 +72,4 @@ const styles = StyleSheet.create({
|
||||
}
|
||||
})
|
||||
|
||||
export default AccountToots
|
||||
export default React.memo(AccountToots, () => true)
|
||||
|
@ -1,12 +1,12 @@
|
||||
import client from '@api/client'
|
||||
import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs'
|
||||
import client from '@root/api/client'
|
||||
import Button from '@root/components/Button'
|
||||
import haptics from '@root/components/haptics'
|
||||
import ParseContent from '@root/components/ParseContent'
|
||||
import { announcementFetch } from '@root/utils/fetches/announcementsFetch'
|
||||
import relativeTime from '@root/utils/relativeTime'
|
||||
import { StyleConstants } from '@root/utils/styles/constants'
|
||||
import { useTheme } from '@root/utils/styles/ThemeManager'
|
||||
import Button from '@components/Button'
|
||||
import haptics from '@components/haptics'
|
||||
import { ParseHTML } from '@components/Parse'
|
||||
import relativeTime from '@components/relativeTime'
|
||||
import { announcementFetch } from '@utils/fetches/announcementsFetch'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import {
|
||||
Dimensions,
|
||||
@ -103,7 +103,7 @@ const ScreenSharedAnnouncements: React.FC = ({
|
||||
发布于{relativeTime(item.published_at)}
|
||||
</Text>
|
||||
<ScrollView style={styles.scrollView} showsVerticalScrollIndicator>
|
||||
<ParseContent
|
||||
<ParseHTML
|
||||
content={item.content}
|
||||
size='M'
|
||||
emojis={item.emojis}
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { HeaderLeft, HeaderRight } from '@components/Header'
|
||||
import haptics from '@root/components/haptics'
|
||||
import { toast } from '@root/components/toast'
|
||||
import { store } from '@root/store'
|
||||
import layoutAnimation from '@root/utils/styles/layoutAnimation'
|
||||
import formatText from '@screens/Shared/Compose/formatText'
|
||||
@ -27,7 +26,6 @@ import { SafeAreaView } from 'react-native-safe-area-context'
|
||||
import { createNativeStackNavigator } from 'react-native-screens/native-stack'
|
||||
import { useQueryClient } from 'react-query'
|
||||
import ComposeEditAttachment from './Compose/EditAttachment'
|
||||
import ComposeEditAttachmentRoot from './Compose/EditAttachment/Root'
|
||||
import composeInitialState from './Compose/utils/initialState'
|
||||
import composeParseState from './Compose/utils/parseState'
|
||||
import composeSend from './Compose/utils/post'
|
||||
@ -190,7 +188,6 @@ const Compose: React.FC<Props> = ({ route: { params }, navigation }) => {
|
||||
haptics('Success')
|
||||
queryClient.invalidateQueries(['Following'])
|
||||
navigation.goBack()
|
||||
toast({ type: 'success', content: '发布成功' })
|
||||
})
|
||||
.catch(() => {
|
||||
haptics('Error')
|
||||
|
@ -6,7 +6,13 @@ import addAttachment from '@screens/Shared/Compose/addAttachment'
|
||||
import { ExtendedAttachment } from '@screens/Shared/Compose/utils/types'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useCallback, useContext, useMemo } from 'react'
|
||||
import React, {
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef
|
||||
} from 'react'
|
||||
import {
|
||||
FlatList,
|
||||
Image,
|
||||
@ -26,6 +32,9 @@ const ComposeAttachments: React.FC = () => {
|
||||
const { theme } = useTheme()
|
||||
const navigation = useNavigation()
|
||||
|
||||
const flatListRef = useRef<FlatList>(null)
|
||||
let prevOffsets = useRef<number[]>()
|
||||
|
||||
const sensitiveOnPress = useCallback(
|
||||
() =>
|
||||
composeDispatch({
|
||||
@ -35,35 +44,75 @@ const ComposeAttachments: React.FC = () => {
|
||||
[composeState.attachments.sensitive]
|
||||
)
|
||||
|
||||
const calculateWidth = useCallback(item => {
|
||||
if (item.local) {
|
||||
return (item.local.width / item.local.height) * DEFAULT_HEIGHT
|
||||
} else {
|
||||
if (item.remote) {
|
||||
if (item.remote.meta.original.aspect) {
|
||||
return item.remote.meta.original.aspect * DEFAULT_HEIGHT
|
||||
} else if (
|
||||
item.remote.meta.original.width &&
|
||||
item.remote.meta.original.height
|
||||
) {
|
||||
return (
|
||||
(item.remote.meta.original.width /
|
||||
item.remote.meta.original.height) *
|
||||
DEFAULT_HEIGHT
|
||||
)
|
||||
} else {
|
||||
return DEFAULT_HEIGHT
|
||||
}
|
||||
} else {
|
||||
return DEFAULT_HEIGHT
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
const snapToOffsets = useMemo(() => {
|
||||
const attachmentsOffsets = composeState.attachments.uploads.map(
|
||||
(_, index) => {
|
||||
let currentOffset = 0
|
||||
Array.from(Array(index).keys()).map(
|
||||
i =>
|
||||
(currentOffset =
|
||||
currentOffset +
|
||||
calculateWidth(composeState.attachments.uploads[i]) +
|
||||
StyleConstants.Spacing.Global.PagePadding)
|
||||
)
|
||||
return currentOffset
|
||||
}
|
||||
)
|
||||
return attachmentsOffsets.length < 4
|
||||
? [
|
||||
...attachmentsOffsets,
|
||||
attachmentsOffsets.reduce((a, b) => a + b, 0) +
|
||||
DEFAULT_HEIGHT +
|
||||
StyleConstants.Spacing.Global.PagePadding
|
||||
]
|
||||
: attachmentsOffsets
|
||||
}, [composeState.attachments.uploads.length])
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
snapToOffsets.length >
|
||||
(prevOffsets.current ? prevOffsets.current?.length : 0)
|
||||
) {
|
||||
flatListRef.current?.scrollToOffset({
|
||||
offset:
|
||||
snapToOffsets[snapToOffsets.length - 2] +
|
||||
snapToOffsets[snapToOffsets.length - 1]
|
||||
})
|
||||
}
|
||||
prevOffsets.current = snapToOffsets
|
||||
}, [snapToOffsets, prevOffsets])
|
||||
|
||||
const renderAttachment = useCallback(
|
||||
({ item, index }: { item: ExtendedAttachment; index: number }) => {
|
||||
let calculatedWidth: number
|
||||
if (item.local) {
|
||||
calculatedWidth =
|
||||
(item.local.width / item.local.height) * DEFAULT_HEIGHT
|
||||
} else {
|
||||
if (item.remote) {
|
||||
if (item.remote.meta.original.aspect) {
|
||||
calculatedWidth = item.remote.meta.original.aspect * DEFAULT_HEIGHT
|
||||
} else if (
|
||||
item.remote.meta.original.width &&
|
||||
item.remote.meta.original.height
|
||||
) {
|
||||
calculatedWidth =
|
||||
(item.remote.meta.original.width /
|
||||
item.remote.meta.original.height) *
|
||||
DEFAULT_HEIGHT
|
||||
} else {
|
||||
calculatedWidth = DEFAULT_HEIGHT
|
||||
}
|
||||
} else {
|
||||
calculatedWidth = DEFAULT_HEIGHT
|
||||
}
|
||||
}
|
||||
return (
|
||||
<View
|
||||
key={index}
|
||||
style={[styles.container, { width: calculatedWidth }]}
|
||||
style={[styles.container, { width: calculateWidth(item) }]}
|
||||
>
|
||||
<Image
|
||||
style={styles.image}
|
||||
@ -172,7 +221,6 @@ const ComposeAttachments: React.FC = () => {
|
||||
),
|
||||
[]
|
||||
)
|
||||
|
||||
return (
|
||||
<View style={styles.base}>
|
||||
<Pressable style={styles.sensitive} onPress={sensitiveOnPress}>
|
||||
@ -187,14 +235,19 @@ const ComposeAttachments: React.FC = () => {
|
||||
</Pressable>
|
||||
<FlatList
|
||||
horizontal
|
||||
keyExtractor={item => item.local!.uri || item.remote!.url}
|
||||
data={composeState.attachments.uploads}
|
||||
ref={flatListRef}
|
||||
decelerationRate={0}
|
||||
pagingEnabled={false}
|
||||
snapToAlignment='center'
|
||||
renderItem={renderAttachment}
|
||||
snapToOffsets={snapToOffsets}
|
||||
keyboardShouldPersistTaps='handled'
|
||||
showsHorizontalScrollIndicator={false}
|
||||
data={composeState.attachments.uploads}
|
||||
keyExtractor={item => item.local!.uri || item.remote!.url}
|
||||
ListFooterComponent={
|
||||
composeState.attachments.uploads.length < 4 ? listFooter : null
|
||||
}
|
||||
showsHorizontalScrollIndicator={false}
|
||||
keyboardShouldPersistTaps='handled'
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
|
@ -1,5 +1,5 @@
|
||||
import Emojis from '@components/Timelines/Timeline/Shared/Emojis'
|
||||
import haptics from '@root/components/haptics'
|
||||
import haptics from '@components/haptics'
|
||||
import { ParseEmojis } from '@components/Parse'
|
||||
import { ComposeContext } from '@screens/Shared/Compose'
|
||||
import ComposeActions from '@screens/Shared/Compose/Actions'
|
||||
import ComposeRootFooter from '@screens/Shared/Compose/Root/Footer'
|
||||
@ -9,7 +9,6 @@ import { emojisFetch } from '@utils/fetches/emojisFetch'
|
||||
import { searchFetch } from '@utils/fetches/searchFetch'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import * as Permissions from 'expo-permissions'
|
||||
import { forEach, groupBy, sortBy } from 'lodash'
|
||||
import React, {
|
||||
Dispatch,
|
||||
@ -66,18 +65,20 @@ const ListItem = React.memo(
|
||||
<View style={[styles.account, { borderBottomColor: theme.border }]}>
|
||||
<Image source={{ uri: item.avatar }} style={styles.accountAvatar} />
|
||||
<View>
|
||||
<Text style={[styles.accountName, { color: theme.primary }]}>
|
||||
{item.emojis?.length ? (
|
||||
<Emojis
|
||||
content={item.display_name || item.username}
|
||||
emojis={item.emojis}
|
||||
size='S'
|
||||
/>
|
||||
) : (
|
||||
item.display_name || item.username
|
||||
)}
|
||||
<Text
|
||||
style={[styles.accountName, { color: theme.primary }]}
|
||||
numberOfLines={1}
|
||||
>
|
||||
<ParseEmojis
|
||||
content={item.display_name || item.username}
|
||||
emojis={item.emojis}
|
||||
size='S'
|
||||
/>
|
||||
</Text>
|
||||
<Text style={[styles.accountAccount, { color: theme.primary }]}>
|
||||
<Text
|
||||
style={[styles.accountAccount, { color: theme.primary }]}
|
||||
numberOfLines={1}
|
||||
>
|
||||
@{item.acct}
|
||||
</Text>
|
||||
</View>
|
||||
|
@ -94,8 +94,12 @@ const formatText = ({
|
||||
contentLength = contentLength + 23
|
||||
break
|
||||
case 'accounts':
|
||||
contentLength =
|
||||
contentLength + main.split(new RegExp('(@.*)@?'))[1].length
|
||||
if (main.match(/@/g)!.length > 1) {
|
||||
contentLength =
|
||||
contentLength + main.split(new RegExp('(@.*?)@'))[1].length
|
||||
} else {
|
||||
contentLength = contentLength + main.length
|
||||
}
|
||||
break
|
||||
case 'hashtags':
|
||||
contentLength = contentLength + main.length
|
||||
|
@ -16,6 +16,9 @@ const composeParseState = ({
|
||||
case 'edit':
|
||||
return {
|
||||
...composeInitialState,
|
||||
...(incomingStatus.spoiler_text && {
|
||||
spoiler: { ...composeInitialState.spoiler, active: true }
|
||||
}),
|
||||
...(incomingStatus.poll && {
|
||||
poll: {
|
||||
active: true,
|
||||
|
@ -109,8 +109,8 @@ const ScreenSharedImagesViewer: React.FC<Props> = ({
|
||||
{
|
||||
url: imageUrls[currentIndex].url
|
||||
},
|
||||
() => haptics('Success'),
|
||||
() => haptics('Error')
|
||||
() => haptics('Error'),
|
||||
() => haptics('Success')
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
@ -1,11 +1,10 @@
|
||||
import { HeaderRight } from '@components/Header'
|
||||
import { ParseEmojis, ParseHTML } from '@components/Parse'
|
||||
import { Feather } from '@expo/vector-icons'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { HeaderRight } from '@root/components/Header'
|
||||
import ParseContent from '@root/components/ParseContent'
|
||||
import Emojis from '@root/components/Timelines/Timeline/Shared/Emojis'
|
||||
import { searchFetch } from '@root/utils/fetches/searchFetch'
|
||||
import { StyleConstants } from '@root/utils/styles/constants'
|
||||
import { useTheme } from '@root/utils/styles/ThemeManager'
|
||||
import { searchFetch } from '@utils/fetches/searchFetch'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import { debounce } from 'lodash'
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import {
|
||||
@ -173,21 +172,16 @@ const ScreenSharedSearch: React.FC = () => {
|
||||
style={styles.itemAccountAvatar}
|
||||
/>
|
||||
<View>
|
||||
{item.emojis?.length ? (
|
||||
<Emojis
|
||||
<Text numberOfLines={1}>
|
||||
<ParseEmojis
|
||||
content={item.display_name || item.username}
|
||||
emojis={item.emojis}
|
||||
size='S'
|
||||
fontBold={true}
|
||||
fontBold
|
||||
/>
|
||||
) : (
|
||||
<Text
|
||||
style={[styles.nameWithoutEmoji, { color: theme.primary }]}
|
||||
>
|
||||
{item.display_name || item.username}
|
||||
</Text>
|
||||
)}
|
||||
</Text>
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
style={[styles.itemAccountAcct, { color: theme.secondary }]}
|
||||
>
|
||||
@{item.acct}
|
||||
@ -229,28 +223,23 @@ const ScreenSharedSearch: React.FC = () => {
|
||||
style={styles.itemAccountAvatar}
|
||||
/>
|
||||
<View>
|
||||
{item.account.emojis?.length ? (
|
||||
<Emojis
|
||||
<Text numberOfLines={1}>
|
||||
<ParseEmojis
|
||||
content={item.account.display_name || item.account.username}
|
||||
emojis={item.account.emojis}
|
||||
size='S'
|
||||
fontBold={true}
|
||||
fontBold
|
||||
/>
|
||||
) : (
|
||||
<Text
|
||||
style={[styles.nameWithoutEmoji, { color: theme.primary }]}
|
||||
>
|
||||
{item.account.display_name || item.account.username}
|
||||
</Text>
|
||||
)}
|
||||
</Text>
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
style={[styles.itemAccountAcct, { color: theme.secondary }]}
|
||||
>
|
||||
@{item.account.acct}
|
||||
</Text>
|
||||
{item.content && (
|
||||
<View style={styles.itemStatus}>
|
||||
<ParseContent
|
||||
<ParseHTML
|
||||
content={item.content}
|
||||
size='M'
|
||||
emojis={item.emojis}
|
||||
@ -405,10 +394,6 @@ const styles = StyleSheet.create({
|
||||
borderRadius: 6,
|
||||
marginRight: StyleConstants.Spacing.S
|
||||
},
|
||||
nameWithoutEmoji: {
|
||||
...StyleConstants.FontStyle.S,
|
||||
fontWeight: StyleConstants.Font.Weight.Bold
|
||||
},
|
||||
itemAccountAcct: { marginTop: StyleConstants.Spacing.XS },
|
||||
itemHashtag: {
|
||||
...StyleConstants.FontStyle.M
|
||||
|
@ -15,7 +15,7 @@ const ScreenSharedToot: React.FC<Props> = ({
|
||||
params: { toot }
|
||||
}
|
||||
}) => {
|
||||
return <Timeline page='Toot' toot={toot} disableRefresh />
|
||||
return <Timeline page='Toot' toot={toot} disableRefresh disableInfinity />
|
||||
}
|
||||
|
||||
export default ScreenSharedToot
|
||||
|
Loading…
x
Reference in New Issue
Block a user