Highlighted toot

This commit is contained in:
Zhiyuan Zheng 2020-12-12 22:19:18 +01:00
parent dcb37e91c9
commit 70743ec82d
No known key found for this signature in database
GPG Key ID: 078A93AB607D85E0
9 changed files with 163 additions and 82 deletions

View File

@ -1,5 +1,5 @@
import React, { useCallback, useState } from 'react'
import { Pressable, StyleSheet, Text } from 'react-native'
import { Pressable, Text } from 'react-native'
import HTMLView from 'react-native-htmlview'
import { useNavigation } from '@react-navigation/native'
@ -22,7 +22,7 @@ const renderNode = ({
theme: any
node: any
index: number
size: number
size: 'M' | 'L'
navigation: any
mentions?: Mastodon.Mention[]
showFullLink: boolean
@ -35,7 +35,10 @@ const renderNode = ({
return (
<Text
key={index}
style={{ color: theme.link, fontSize: size }}
style={{
color: theme.link,
fontSize: StyleConstants.Font.Size[size]
}}
onPress={() => {
const tag = href.split(new RegExp(/\/tag\/(.*)|\/tags\/(.*)/))
navigation.push('Screen-Shared-Hashtag', {
@ -51,7 +54,10 @@ const renderNode = ({
return (
<Text
key={index}
style={{ color: theme.link, fontSize: size }}
style={{
color: theme.link,
fontSize: StyleConstants.Font.Size[size]
}}
onPress={() => {
const username = href.split(new RegExp(/@(.*)/))
const usernameIndex = mentions.findIndex(
@ -72,7 +78,10 @@ const renderNode = ({
return (
<Text
key={index}
style={{ color: theme.link, fontSize: size }}
style={{
color: theme.link,
fontSize: StyleConstants.Font.Size[size]
}}
onPress={() => {
navigation.navigate('Screen-Shared-Webview', {
uri: href,
@ -80,7 +89,11 @@ const renderNode = ({
})
}}
>
<Feather name='external-link' size={size} color={theme.link} />{' '}
<Feather
name='external-link'
size={StyleConstants.Font.Size[size]}
color={theme.link}
/>{' '}
{showFullLink ? href : domain[1]}
</Text>
)
@ -90,7 +103,7 @@ const renderNode = ({
export interface Props {
content: string
size: number
size: 'M' | 'L'
emojis?: Mastodon.Emoji[]
mentions?: Mastodon.Mention[]
showFullLink?: boolean
@ -124,7 +137,11 @@ const ParseContent: React.FC<Props> = ({
const textComponent = useCallback(
({ children }) =>
emojis && children ? (
<Emojis content={children.toString()} emojis={emojis} size={size} />
<Emojis
content={children.toString()}
emojis={emojis}
size={StyleConstants.Font.Size[size]}
/>
) : (
<Text>{children}</Text>
),
@ -143,7 +160,7 @@ const ParseContent: React.FC<Props> = ({
numberOfLines={
totalLines && totalLines > numberOfLines ? shownLines : totalLines
}
style={styles.root}
style={{ lineHeight: StyleConstants.Font.LineHeight[size] }}
onTextLayout={({ nativeEvent }) => {
if (!textLoaded) {
setTextLoaded(true)
@ -200,10 +217,4 @@ const ParseContent: React.FC<Props> = ({
)
}
const styles = StyleSheet.create({
root: {
lineHeight: StyleConstants.Font.LineHeight.M
}
})
export default ParseContent

View File

@ -1,5 +1,5 @@
import React, { useCallback, useEffect, useRef } from 'react'
import { ActivityIndicator, AppState, FlatList, StyleSheet } from 'react-native'
import React, { useCallback, useEffect, useMemo, useRef } from 'react'
import { AppState, FlatList, StyleSheet } from 'react-native'
import { setFocusHandler, useInfiniteQuery } from 'react-query'
import TimelineNotifications from 'src/components/Timelines/Timeline/Notifications'
@ -75,10 +75,33 @@ const Timeline: React.FC<Props> = ({
case 'Notifications':
return <TimelineNotifications notification={item} queryKey={queryKey} />
default:
return <TimelineDefault item={item} queryKey={queryKey} />
return (
<TimelineDefault
item={item}
queryKey={queryKey}
{...(toot && toot.id === item.id && { highlighted: true })}
/>
)
}
}, [])
const flItemSeparatorComponent = useCallback(() => <TimelineSeparator />, [])
const flItemSeparatorComponent = useCallback(
({ leadingItem }) => (
<TimelineSeparator
{...(toot && toot.id === leadingItem.id && { highlighted: true })}
/>
),
[]
)
const flItemEmptyComponent = useMemo(
() => (
<TimelineEmpty
isLoading={isLoading}
isError={isError}
refetch={refetch}
/>
),
[isLoading, isError]
)
const flOnRefresh = useCallback(
() =>
!disableRefresh &&
@ -102,11 +125,12 @@ const Timeline: React.FC<Props> = ({
[flattenData]
)
const flFooter = useCallback(() => {
if (isFetchingMore) {
return <ActivityIndicator />
} else {
return <TimelineEnd />
}
return <TimelineEnd isFetchingMore={isFetchingMore} />
// if (isFetchingMore) {
// return <ActivityIndicator />
// } else {
// return <TimelineEnd />
// }
}, [isFetchingMore])
const onScrollToIndexFailed = useCallback(error => {
const offset = error.averageItemLength * error.index
@ -127,18 +151,12 @@ const Timeline: React.FC<Props> = ({
renderItem={flRenderItem}
onEndReached={flOnEndReach}
keyExtractor={flKeyExtrator}
ListFooterComponent={flFooter}
scrollEnabled={scrollEnabled} // For timeline in Account view
ListFooterComponent={flFooter}
ListEmptyComponent={flItemEmptyComponent}
ItemSeparatorComponent={flItemSeparatorComponent}
onEndReachedThreshold={!disableRefresh ? 0.75 : null}
refreshing={!disableRefresh && isLoading && flattenData.length > 0}
ListEmptyComponent={
<TimelineEmpty
isLoading={isLoading}
isError={isError}
refetch={refetch}
/>
}
{...(toot && isSuccess && { onScrollToIndexFailed })}
/>
)

View File

@ -16,18 +16,25 @@ import { StyleConstants } from 'src/utils/styles/constants'
export interface Props {
item: Mastodon.Status
queryKey: App.QueryKey
highlighted?: boolean
}
// When the poll is long
const TimelineDefault: React.FC<Props> = ({ item, queryKey }) => {
const TimelineDefault: React.FC<Props> = ({
item,
queryKey,
highlighted = false
}) => {
const navigation = useNavigation()
let actualStatus = item.reblog ? item.reblog : item
const contentWidth =
Dimensions.get('window').width -
StyleConstants.Spacing.Global.PagePadding * 2 - // Global page padding on both sides
StyleConstants.Avatar.S - // Avatar width
StyleConstants.Spacing.S // Avatar margin to the right
const contentWidth = highlighted
? Dimensions.get('window').width -
StyleConstants.Spacing.Global.PagePadding * 2 // Global page padding on both sides
: Dimensions.get('window').width -
StyleConstants.Spacing.Global.PagePadding * 2 - // Global page padding on both sides
StyleConstants.Avatar.S - // Avatar width
StyleConstants.Spacing.S // Avatar margin to the right
const tootOnPress = useCallback(
() =>
@ -38,9 +45,15 @@ const TimelineDefault: React.FC<Props> = ({ item, queryKey }) => {
)
const tootChildren = useMemo(
() => (
<>
<View
style={{
paddingLeft: highlighted
? 0
: StyleConstants.Avatar.S + StyleConstants.Spacing.S
}}
>
{actualStatus.content.length > 0 && (
<TimelineContent status={actualStatus} />
<TimelineContent status={actualStatus} highlighted={highlighted} />
)}
{actualStatus.poll && (
<TimelinePoll queryKey={queryKey} status={actualStatus} />
@ -49,7 +62,7 @@ const TimelineDefault: React.FC<Props> = ({ item, queryKey }) => {
<TimelineAttachment status={actualStatus} width={contentWidth} />
)}
{actualStatus.card && <TimelineCard card={actualStatus.card} />}
</>
</View>
),
[actualStatus.poll?.voted]
)
@ -59,13 +72,19 @@ const TimelineDefault: React.FC<Props> = ({ item, queryKey }) => {
{item.reblog && (
<TimelineActioned action='reblog' account={item.account} />
)}
<View style={styles.status}>
<View style={styles.header}>
<TimelineAvatar account={actualStatus.account} />
<View style={styles.details}>
<TimelineHeaderDefault queryKey={queryKey} status={actualStatus} />
<Pressable onPress={tootOnPress} children={tootChildren} />
<TimelineActions queryKey={queryKey} status={actualStatus} />
</View>
<TimelineHeaderDefault queryKey={queryKey} status={actualStatus} />
</View>
<Pressable onPress={tootOnPress} children={tootChildren} />
<View
style={{
paddingLeft: highlighted
? 0
: StyleConstants.Avatar.S + StyleConstants.Spacing.S
}}
>
<TimelineActions queryKey={queryKey} status={actualStatus} />
</View>
</View>
)
@ -73,17 +92,17 @@ const TimelineDefault: React.FC<Props> = ({ item, queryKey }) => {
const styles = StyleSheet.create({
statusView: {
flex: 1,
flexDirection: 'column',
padding: StyleConstants.Spacing.Global.PagePadding,
paddingBottom: StyleConstants.Spacing.M
},
status: {
header: {
flex: 1,
flexDirection: 'row'
width: '100%',
flexDirection: 'row',
marginBottom: StyleConstants.Spacing.S
},
details: {
flex: 1
content: {
paddingLeft: StyleConstants.Avatar.S + StyleConstants.Spacing.S
}
})

View File

@ -4,20 +4,34 @@ import { StyleSheet, View } from 'react-native'
import { useTheme } from 'src/utils/styles/ThemeManager'
import { StyleConstants } from 'src/utils/styles/constants'
const TimelineSeparator = () => {
export interface Props {
highlighted?: boolean
}
const TimelineSeparator: React.FC<Props> = ({ highlighted = false }) => {
const { theme } = useTheme()
return <View style={[styles.base, { borderTopColor: theme.separator }]} />
return (
<View
style={[
styles.base,
{
borderTopColor: theme.separator,
marginLeft: highlighted
? StyleConstants.Spacing.Global.PagePadding
: StyleConstants.Spacing.Global.PagePadding +
StyleConstants.Avatar.S +
StyleConstants.Spacing.S
}
]}
/>
)
}
const styles = StyleSheet.create({
base: {
borderTopWidth: 1,
marginLeft:
StyleConstants.Spacing.M +
StyleConstants.Avatar.S +
StyleConstants.Spacing.S,
marginRight: StyleConstants.Spacing.M
marginRight: StyleConstants.Spacing.Global.PagePadding
}
})

View File

@ -25,7 +25,7 @@ const TimelineAvatar: React.FC<Props> = ({ account }) => {
const styles = StyleSheet.create({
avatar: {
width: StyleConstants.Avatar.S,
flexBasis: StyleConstants.Avatar.S,
height: StyleConstants.Avatar.S,
marginRight: StyleConstants.Spacing.S
},

View File

@ -11,9 +11,14 @@ import { LinearGradient } from 'expo-linear-gradient'
export interface Props {
status: Mastodon.Status
numberOfLines?: number
highlighted?: boolean
}
const TimelineContent: React.FC<Props> = ({ status, numberOfLines }) => {
const TimelineContent: React.FC<Props> = ({
status,
numberOfLines,
highlighted = false
}) => {
const { theme } = useTheme()
const [spoilerCollapsed, setSpoilerCollapsed] = useState(true)
const lineHeight = 28
@ -24,13 +29,13 @@ const TimelineContent: React.FC<Props> = ({ status, numberOfLines }) => {
<>
<ParseContent
content={status.spoiler_text}
size={StyleConstants.Font.Size.M}
size={highlighted ? 'L' : 'M'}
emojis={status.emojis}
/>
<Collapsible collapsed={spoilerCollapsed} collapsedHeight={20}>
<ParseContent
content={status.content}
size={StyleConstants.Font.Size.M}
size={highlighted ? 'L' : 'M'}
emojis={status.emojis}
mentions={status.mentions}
{...(numberOfLines && { numberOfLines: numberOfLines })}
@ -68,7 +73,7 @@ const TimelineContent: React.FC<Props> = ({ status, numberOfLines }) => {
) : (
<ParseContent
content={status.content}
size={StyleConstants.Font.Size.M}
size={highlighted ? 'L' : 'M'}
emojis={status.emojis}
mentions={status.mentions}
{...(numberOfLines && { numberOfLines: numberOfLines })}

View File

@ -1,23 +1,31 @@
import { Feather } from '@expo/vector-icons'
import React from 'react'
import { StyleSheet, Text, View } from 'react-native'
import { ActivityIndicator, StyleSheet, Text, View } from 'react-native'
import { StyleConstants } from 'src/utils/styles/constants'
import { useTheme } from 'src/utils/styles/ThemeManager'
const TimelineEnd: React.FC = () => {
export interface Props {
isFetchingMore: false | 'previous' | 'next' | undefined
}
const TimelineEnd: React.FC<Props> = ({ isFetchingMore }) => {
const { theme } = useTheme()
return (
<View style={styles.base}>
<Text style={[styles.text, { color: theme.secondary }]}>
{' '}
<Feather
name='coffee'
size={StyleConstants.Font.Size.S}
color={theme.secondary}
/>{' '}
</Text>
{isFetchingMore ? (
<ActivityIndicator />
) : (
<Text style={[styles.text, { color: theme.secondary }]}>
{' '}
<Feather
name='coffee'
size={StyleConstants.Font.Size.S}
color={theme.secondary}
/>{' '}
</Text>
)}
</View>
)
}

View File

@ -60,7 +60,7 @@ const TimelineHeaderDefault: React.FC<Props> = ({ queryKey, status }) => {
)
return (
<View>
<View style={styles.base}>
<View style={styles.nameAndAction}>
<View style={styles.name}>
{emojis?.length ? (
@ -156,13 +156,17 @@ const TimelineHeaderDefault: React.FC<Props> = ({ queryKey, status }) => {
}
const styles = StyleSheet.create({
base: {
flex: 1
},
nameAndAction: {
width: '100%',
flex: 1,
flexBasis: '100%',
flexDirection: 'row',
justifyContent: 'space-between'
alignItems: 'flex-start'
},
name: {
flexBasis: '90%',
flexBasis: '85%',
flexDirection: 'row'
},
nameWithoutEmoji: {
@ -170,7 +174,9 @@ const styles = StyleSheet.create({
fontWeight: StyleConstants.Font.Weight.Bold
},
action: {
alignItems: 'flex-end'
flex: 1,
flexDirection: 'row',
justifyContent: 'center'
},
account: {
flexShrink: 1,

View File

@ -7,7 +7,7 @@ export const StyleConstants = {
M: 16,
L: 18
},
LineHeight: { M: 20 },
LineHeight: { M: 20, L: 24 },
Weight: {
Bold: '600' as '600'
}