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