1
0
mirror of https://github.com/tooot-app/app synced 2025-06-05 22:19:13 +02:00

Working emojis in reblog and names

This commit is contained in:
Zhiyuan Zheng
2020-10-25 01:35:41 +02:00
parent 2bc6e3277c
commit 996cf0c1e7
11 changed files with 289 additions and 59 deletions

View File

@ -3,6 +3,7 @@ module.exports = function (api) {
return { return {
presets: ['babel-preset-expo'], presets: ['babel-preset-expo'],
plugins: [ plugins: [
['@babel/plugin-proposal-optional-chaining'],
[ [
'module-resolver', 'module-resolver',
{ {

View File

@ -34,6 +34,7 @@
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "~7.9.0", "@babel/core": "~7.9.0",
"@babel/plugin-proposal-optional-chaining": "^7.12.1",
"babel-plugin-module-resolver": "^4.0.0" "babel-plugin-module-resolver": "^4.0.0"
}, },
"private": true "private": true

View File

@ -1,52 +1,60 @@
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import React from 'react' import React from 'react'
import { Image, StyleSheet, Text, View } from 'react-native' import { StyleSheet, View } from 'react-native'
import HTML from 'react-native-render-html'
import relativeTime from 'src/utils/relativeTime' import Reblog from './TootTimeline/Reblog'
import Avatar from './TootTimeline/Avatar'
import Header from './TootTimeline/Header'
import Content from './TootTimeline/Content'
import Actions from './TootTimeline/Actions'
export default function TootTimeline ({ item, notification }) { export default function TootTimeline ({ item, notification }) {
return ( return (
<View style={styles.tootTimeline}> <View style={styles.tootTimeline}>
<View style={styles.header}> {item.reblog && (
<Image <Reblog
source={{ name={item.account.display_name || item.account.username}
uri: item.reblog ? item.reblog.account.avatar : item.account.avatar emojis={item.account.emojis}
}}
style={styles.avatar}
/> />
<View> )}
<View style={styles.name}> <View style={styles.toot}>
<Text> <Avatar uri={item.reblog?.account.avatar || item.account.avatar} />
{item.reblog <View style={{flexGrow: 1}}>
? item.reblog.account.display_name <Header
: item.account.display_name} name={
</Text> (item.reblog?.account.display_name
<Text> ? item.reblog?.account.display_name
{item.reblog ? item.reblog.account.acct : item.account.acct} : item.reblog?.account.username) ||
</Text> (item.account.display_name
</View> ? item.account.display_name
<View> : item.account.username)
<Text>{relativeTime(item.created_at)}</Text> }
{item.application && item.application.name !== 'Web' && ( emojis={item.reblog?.account.emojis || item.account.emojis}
<Text onPress={() => Linking.openURL(item.application.website)}> account={item.reblog?.account.acct || item.account.acct}
{item.application.name} created_at={item.created_at}
</Text> application={item.application || null}
)} />
</View> <Content
content={notification ? item.status.content : item.content}
/>
</View> </View>
</View> </View>
{notification ? ( <Actions />
<HTML html={item.status.content} />
) : item.content ? (
<HTML html={item.content} />
) : (
<></>
)}
</View> </View>
) )
} }
const styles = StyleSheet.create({
tootTimeline: {
flex: 1,
flexDirection: 'column',
padding: 12
},
toot: {
flexDirection: 'row'
}
})
TootTimeline.propTypes = { TootTimeline.propTypes = {
item: PropTypes.shape({ item: PropTypes.shape({
account: PropTypes.shape({ account: PropTypes.shape({
@ -63,20 +71,3 @@ TootTimeline.propTypes = {
}).isRequired, }).isRequired,
notification: PropTypes.bool notification: PropTypes.bool
} }
const styles = StyleSheet.create({
tootTimeline: {
flex: 1,
padding: 15
},
header: {
flexDirection: 'row'
},
avatar: {
width: 40,
height: 40
},
name: {
flexDirection: 'row'
}
})

View File

@ -0,0 +1,16 @@
import React from 'react'
import PropTypes from 'prop-types'
import { StyleSheet } from 'react-native'
export default function Actions () {
return <></>
}
const styles = StyleSheet.create({
width: 50,
height: 50
})
// Actions.propTypes = {
// uri: PropTypes.string
// }

View File

@ -0,0 +1,19 @@
import React from 'react'
import PropTypes from 'prop-types'
import { Image, StyleSheet } from 'react-native'
export default function Avatar ({ uri }) {
return <Image source={{ uri: uri }} style={styles.avatar} />
}
const styles = StyleSheet.create({
avatar: {
width: 50,
height: 50,
marginRight: 8
}
})
Avatar.propTypes = {
uri: PropTypes.string.isRequired
}

View File

@ -0,0 +1,31 @@
import React, { useState } from 'react'
import PropTypes from 'prop-types'
import { Dimensions, StyleSheet, View } from 'react-native'
import HTML from 'react-native-render-html'
// !! Need to solve dimension issue
export default function Content ({ content }) {
const [viewWidth, setViewWidth] = useState()
return (
content && (
<View
style={{ width: '100%' }}
onLayout={e => setViewWidth(e.nativeEvent.layout.width)}
>
{viewWidth && (
<HTML html={content} containerStyle={{ width: viewWidth }} />
)}
</View>
)
)
}
const styles = StyleSheet.create({
width: 50,
height: 50
})
Content.propTypes = {
content: PropTypes.string
}

View File

@ -0,0 +1,53 @@
import React from 'react'
import PropTypes from 'prop-types'
import { Image, Text } from 'react-native'
const regexEmoji = new RegExp(/(:[a-z0-9_]+:)/g)
const regexEmojiSelect = new RegExp(/:([a-z0-9_]+):/)
export default function Emojis ({ content, emojis, dimension }) {
const hasEmojis = content.match(regexEmoji)
return hasEmojis ? (
content.split(regexEmoji).map((str, i) => {
if (str.match(regexEmoji)) {
const emojiShortcode = str.split(regexEmojiSelect)[1]
const emojiIndex = emojis.findIndex(emoji => {
return emoji.shortcode === emojiShortcode
})
return (
<Image
key={i}
source={{ uri: emojis[emojiIndex].url }}
style={{ width: dimension, height: dimension }}
/>
)
} else {
return (
<Text
key={i}
style={{ fontSize: dimension, lineHeight: dimension + 1 }}
>
{str}
</Text>
)
}
})
) : (
<Text style={{ fontSize: dimension, lineHeight: dimension + 1 }}>
{content}
</Text>
)
}
Emojis.propTypes = {
content: PropTypes.string.isRequired,
emojis: PropTypes.arrayOf(
PropTypes.exact({
shortcode: PropTypes.string.isRequired,
url: PropTypes.string.isRequired,
static_url: PropTypes.string.isRequired,
visible_in_picker: PropTypes.bool.isRequired,
category: PropTypes.string
})
)
}

View File

@ -0,0 +1,86 @@
import React, { useEffect, useState } from 'react'
import PropTypes from 'prop-types'
import { StyleSheet, Text, View } from 'react-native'
import Emojis from './Emojis'
import relativeTime from 'src/utils/relativeTime'
export default function Header ({
name,
emojis,
account,
created_at,
application
}) {
const [since, setSince] = useState(relativeTime(created_at))
useEffect(() => {
const timer = setTimeout(() => {
setSince(relativeTime(created_at))
}, 1000)
})
return (
<View>
<View style={styles.names}>
<View style={styles.name}>
<Emojis content={name} emojis={emojis} dimension={14} />
</View>
<Text style={styles.account}>@{account}</Text>
</View>
<View style={styles.meta}>
<View>
<Text style={styles.created_at}>{since}</Text>
</View>
{application && application.name !== 'Web' && (
<View>
<Text
onPress={() => Linking.openURL(application.website)}
style={styles.application}
>
{application.name}
</Text>
</View>
)}
</View>
</View>
)
}
const styles = StyleSheet.create({
names: {
flexDirection: 'row',
marginBottom: 8
},
name: {
flexDirection: 'row',
marginRight: 8
},
account: {
fontSize: 12,
lineHeight: 14
},
meta: {
flexDirection: 'row'
},
created_at: {
fontSize: 12,
lineHeight: 12,
marginRight: 8
},
application: {
fontSize: 12,
lineHeight: 11
}
})
Header.propTypes = {
name: PropTypes.string.isRequired,
emojis: Emojis.propTypes.emojis,
account: PropTypes.string.isRequired,
created_at: PropTypes.string.isRequired,
application: PropTypes.exact({
name: PropTypes.string.isRequired,
website: PropTypes.string
})
}

View File

@ -0,0 +1,36 @@
import React from 'react'
import PropTypes from 'prop-types'
import { StyleSheet, Text, View } from 'react-native'
import { Feather } from '@expo/vector-icons'
import Emojis from './Emojis'
export default function Reblog ({ name, emojis }) {
return (
<View style={styles.reblog}>
<Feather name='repeat' size={12} color='black' style={styles.icon} />
<View style={styles.name}>
<Emojis content={name} emojis={emojis} dimension={12} />
</View>
</View>
)
}
const styles = StyleSheet.create({
reblog: {
flexDirection: 'row',
marginBottom: 8
},
icon: {
marginLeft: 50 - 12,
marginRight: 8
},
name: {
flexDirection: 'row'
}
})
Reblog.propTypes = {
name: PropTypes.string.isRequired,
emojis: Emojis.propTypes.emojis
}

View File

@ -21,7 +21,6 @@ const Default = ({ dispatch, toots, status, timeline }) => {
dispatch(fetch({ ...timeline, id: toots[toots.length - 1].id })) dispatch(fetch({ ...timeline, id: toots[toots.length - 1].id }))
} }
onEndReachedThreshold={0.5} onEndReachedThreshold={0.5}
style={{ height: '100%', width: '100%' }}
/> />
{status === 'loading' && <ActivityIndicator />} {status === 'loading' && <ActivityIndicator />}
</> </>
@ -42,12 +41,9 @@ const Notifications = ({ dispatch, toots, status, timeline }) => {
} }
refreshing={status === 'loading'} refreshing={status === 'loading'}
onEndReached={() => onEndReached={() =>
dispatch( dispatch(fetch({ ...timeline, id: toots[toots.length - 1].id }))
fetch({ ...timeline, id: toots[toots.length - 1].id })
)
} }
onEndReachedThreshold={0.5} onEndReachedThreshold={0.5}
style={{ height: '100%', width: '100%' }}
/> />
{status === 'loading' && <ActivityIndicator />} {status === 'loading' && <ActivityIndicator />}
</> </>

View File

@ -6,8 +6,8 @@ import timelineSlice from 'src/stacks/common/timelineSlice'
// get site information from local storage and pass to reducers // get site information from local storage and pass to reducers
const preloadedState = { const preloadedState = {
instanceInfo: { instanceInfo: {
current: 'm.cmx.im', current: 'social.xmflsct.com',
currentToken: 'Cxx19XX2VNHnPy_dr_HCHMh4HvwHEvYwWrrU3r3BNzQ', currentToken: 'qjzJ0IjvZ1apsn0_wBkGcdjKgX7Dao9KEPhGwggPwAo',
remote: 'mastodon.social' remote: 'mastodon.social'
} }
} }