mirror of
https://github.com/tooot-app/app
synced 2025-02-23 15:17:38 +01:00
Basic account page works
This commit is contained in:
parent
3f8e451099
commit
a6e33d8b0a
8
package-lock.json
generated
8
package-lock.json
generated
@ -6570,6 +6570,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"react-native-collapsible": {
|
||||
"version": "1.5.3",
|
||||
"resolved": "https://registry.npmjs.org/react-native-collapsible/-/react-native-collapsible-1.5.3.tgz",
|
||||
"integrity": "sha512-Ciuy3yF8wcHeLxXFtx9vxoyrq8l+9EJqLM/ADw54gVwzTQc1/e2ojhQbVFPlxhDA+Pba2qezkaKfv5ifxg8hoQ==",
|
||||
"requires": {
|
||||
"prop-types": "^15.6.2"
|
||||
}
|
||||
},
|
||||
"react-native-gesture-handler": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-1.7.0.tgz",
|
||||
|
@ -23,6 +23,7 @@
|
||||
"react": "16.13.1",
|
||||
"react-dom": "16.13.1",
|
||||
"react-native": "https://github.com/expo/react-native/archive/sdk-39.0.3.tar.gz",
|
||||
"react-native-collapsible": "^1.5.3",
|
||||
"react-native-gesture-handler": "~1.7.0",
|
||||
"react-native-htmlview": "^0.16.0",
|
||||
"react-native-image-zoom-viewer": "^3.0.1",
|
||||
|
@ -14,9 +14,7 @@ export async function client (url, query, { body, ...customConfig } = {}) {
|
||||
}
|
||||
|
||||
const queryString = query
|
||||
? `?${Object.keys(query)
|
||||
.map(key => `${key}=${query[key]}`)
|
||||
.join('&')}`
|
||||
? `?${query.map(({ key, value }) => `${key}=${value}`).join('&')}`
|
||||
: ''
|
||||
|
||||
if (body) {
|
||||
|
119
src/components/ParseContent.jsx
Normal file
119
src/components/ParseContent.jsx
Normal file
@ -0,0 +1,119 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { StyleSheet, Text } from 'react-native'
|
||||
import HTMLView from 'react-native-htmlview'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
|
||||
import Emojis from 'src/components/TootTimeline/Emojis'
|
||||
|
||||
function renderNode ({ node, index, navigation, mentions, showFullLink }) {
|
||||
if (node.name == 'a') {
|
||||
const classes = node.attribs.class
|
||||
const href = node.attribs.href
|
||||
if (classes) {
|
||||
if (classes.includes('hashtag')) {
|
||||
return (
|
||||
<Text
|
||||
key={index}
|
||||
style={styles.a}
|
||||
onPress={() => {
|
||||
const tag = href.split(new RegExp(/\/tag\/(.*)|\/tags\/(.*)/))
|
||||
navigation.navigate('Hashtag', {
|
||||
hashtag: tag[1] || tag[2]
|
||||
})
|
||||
}}
|
||||
>
|
||||
{node.children[0].data}
|
||||
{node.children[1]?.children[0].data}
|
||||
</Text>
|
||||
)
|
||||
} else if (classes.includes('mention')) {
|
||||
return (
|
||||
<Text
|
||||
key={index}
|
||||
style={styles.a}
|
||||
onPress={() => {
|
||||
const username = href.split(new RegExp(/@(.*)/))
|
||||
const usernameIndex = mentions.findIndex(
|
||||
m => m.username === username[1]
|
||||
)
|
||||
navigation.navigate('Account', {
|
||||
id: mentions[usernameIndex].id
|
||||
})
|
||||
}}
|
||||
>
|
||||
{node.children[0].data}
|
||||
{node.children[1]?.children[0].data}
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
} else {
|
||||
const domain = href.split(new RegExp(/:\/\/(.*?)\//))
|
||||
return (
|
||||
<Text
|
||||
key={index}
|
||||
style={styles.a}
|
||||
onPress={() => {
|
||||
navigation.navigate('Webview', {
|
||||
uri: href,
|
||||
domain: domain[1]
|
||||
})
|
||||
}}
|
||||
>
|
||||
{showFullLink ? href : domain[1]}
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default function ParseContent ({
|
||||
content,
|
||||
emojis,
|
||||
emojiSize = 14,
|
||||
mentions,
|
||||
showFullLink = false
|
||||
}) {
|
||||
const navigation = useNavigation()
|
||||
|
||||
return (
|
||||
<HTMLView
|
||||
value={content}
|
||||
stylesheet={HTMLstyles}
|
||||
addLineBreaks={null}
|
||||
renderNode={(node, index) =>
|
||||
renderNode({ node, index, navigation, mentions, showFullLink })
|
||||
}
|
||||
TextComponent={({ children }) => (
|
||||
<Emojis content={children} emojis={emojis} dimension={emojiSize} />
|
||||
)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
a: {
|
||||
color: 'blue'
|
||||
}
|
||||
})
|
||||
|
||||
const HTMLstyles = StyleSheet.create({
|
||||
p: {
|
||||
marginBottom: 12
|
||||
}
|
||||
})
|
||||
|
||||
ParseContent.propTypes = {
|
||||
content: PropTypes.string.isRequired,
|
||||
emojis: Emojis.propTypes.emojis,
|
||||
emojiSize: PropTypes.number,
|
||||
mentions: PropTypes.arrayOf(
|
||||
PropTypes.exact({
|
||||
id: PropTypes.string.isRequired,
|
||||
username: PropTypes.string.isRequired,
|
||||
url: PropTypes.string.isRequired,
|
||||
acct: PropTypes.string.isRequired
|
||||
})
|
||||
),
|
||||
showFullLink: PropTypes.bool
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import React, { useState } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { StyleSheet, View } from 'react-native'
|
||||
import { Dimensions, StyleSheet, View } from 'react-native'
|
||||
|
||||
import Reblog from './TootTimeline/Reblog'
|
||||
import Avatar from './TootTimeline/Avatar'
|
||||
@ -11,8 +11,6 @@ import Actions from './TootTimeline/Actions'
|
||||
// Maybe break away notification types? https://docs.joinmastodon.org/entities/notification/
|
||||
|
||||
export default function TootTimeline ({ item, notification }) {
|
||||
const [viewWidth, setViewWidth] = useState()
|
||||
|
||||
let contentAggregated = {}
|
||||
let actualContent
|
||||
if (notification && item.status) {
|
||||
@ -41,11 +39,11 @@ export default function TootTimeline ({ item, notification }) {
|
||||
/>
|
||||
)}
|
||||
<View style={styles.toot}>
|
||||
<Avatar uri={item.reblog?.account.avatar || item.account.avatar} />
|
||||
<View
|
||||
style={styles.details}
|
||||
onLayout={e => setViewWidth(e.nativeEvent.layout.width)}
|
||||
>
|
||||
<Avatar
|
||||
uri={item.reblog?.account.avatar || item.account.avatar}
|
||||
id={item.reblog?.account.id || item.account.id}
|
||||
/>
|
||||
<View style={styles.details}>
|
||||
<Header
|
||||
name={
|
||||
(item.reblog?.account.display_name
|
||||
@ -60,7 +58,11 @@ export default function TootTimeline ({ item, notification }) {
|
||||
created_at={item.created_at}
|
||||
application={item.application || null}
|
||||
/>
|
||||
<Content {...contentAggregated} width={viewWidth} />
|
||||
<Content
|
||||
{...contentAggregated}
|
||||
style={{ flex: 1 }}
|
||||
width={Dimensions.get('window').width - 24 - 50 - 8}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<Actions />
|
||||
|
@ -1,9 +1,22 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Image, StyleSheet, View } from 'react-native'
|
||||
import { Image, Pressable, StyleSheet } from 'react-native'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
|
||||
export default function Avatar ({ uri }) {
|
||||
return <Image source={{ uri: uri }} style={styles.avatar} />
|
||||
export default function Avatar ({ uri, id }) {
|
||||
const navigation = useNavigation()
|
||||
return (
|
||||
<Pressable
|
||||
style={styles.avatar}
|
||||
onPress={() => {
|
||||
navigation.navigate('Account', {
|
||||
id: id
|
||||
})
|
||||
}}
|
||||
>
|
||||
<Image source={{ uri: uri }} style={styles.image} />
|
||||
</Pressable>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
@ -11,9 +24,14 @@ const styles = StyleSheet.create({
|
||||
width: 50,
|
||||
height: 50,
|
||||
marginRight: 8
|
||||
},
|
||||
image: {
|
||||
width: '100%',
|
||||
height: '100%'
|
||||
}
|
||||
})
|
||||
|
||||
Avatar.propTypes = {
|
||||
uri: PropTypes.string.isRequired
|
||||
uri: PropTypes.string.isRequired,
|
||||
id: PropTypes.string.isRequired
|
||||
}
|
||||
|
@ -6,75 +6,13 @@ import {
|
||||
Modal,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableHighlight,
|
||||
Pressable,
|
||||
View
|
||||
} from 'react-native'
|
||||
import HTMLView from 'react-native-htmlview'
|
||||
import Collapsible from 'react-native-collapsible'
|
||||
import ImageViewer from 'react-native-image-zoom-viewer'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
|
||||
import Emojis from './Emojis'
|
||||
|
||||
function renderNode (navigation, node, index, mentions) {
|
||||
if (node.name == 'a') {
|
||||
const classes = node.attribs.class
|
||||
const href = node.attribs.href
|
||||
if (classes) {
|
||||
if (classes.includes('hashtag')) {
|
||||
return (
|
||||
<Text
|
||||
key={index}
|
||||
style={styles.a}
|
||||
onPress={() => {
|
||||
const tag = href.split(new RegExp(/\/tag\/(.*)|\/tags\/(.*)/))
|
||||
navigation.navigate('Hashtag', {
|
||||
hashtag: tag[1] || tag[2]
|
||||
})
|
||||
}}
|
||||
>
|
||||
{node.children[0].data}
|
||||
{node.children[1]?.children[0].data}
|
||||
</Text>
|
||||
)
|
||||
} else if (classes.includes('mention')) {
|
||||
return (
|
||||
<Text
|
||||
key={index}
|
||||
style={styles.a}
|
||||
onPress={() => {
|
||||
const username = href.split(new RegExp(/@(.*)/))
|
||||
const usernameIndex = mentions.findIndex(
|
||||
m => m.username === username[1]
|
||||
)
|
||||
navigation.navigate('Account', {
|
||||
id: mentions[usernameIndex].id
|
||||
})
|
||||
}}
|
||||
>
|
||||
{node.children[0].data}
|
||||
{node.children[1]?.children[0].data}
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
} else {
|
||||
const domain = href.split(new RegExp(/:\/\/(.*?)\//))
|
||||
return (
|
||||
<Text
|
||||
key={index}
|
||||
style={styles.a}
|
||||
onPress={() => {
|
||||
navigation.navigate('Webview', {
|
||||
uri: href,
|
||||
domain: domain[1]
|
||||
})
|
||||
}}
|
||||
>
|
||||
{domain[1]}
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
import ParseContent from 'src/components/ParseContent'
|
||||
|
||||
function Media ({ media_attachments, sensitive, width }) {
|
||||
const [mediaSensitive, setMediaSensitive] = useState(sensitive)
|
||||
@ -90,16 +28,6 @@ function Media ({ media_attachments, sensitive, width }) {
|
||||
|
||||
let images = []
|
||||
if (width) {
|
||||
const calWidth = i => {
|
||||
if (media_attachments.length === 1) {
|
||||
return { flexGrow: 1, aspectRatio: 16 / 9 }
|
||||
} else if (media_attachments.length === 3 && i === 2) {
|
||||
return { flexGrow: 1, aspectRatio: 16 / 9 }
|
||||
} else {
|
||||
return { flexBasis: width / 2 - 4, aspectRatio: 16 / 9 }
|
||||
}
|
||||
}
|
||||
|
||||
media_attachments = media_attachments.map((m, i) => {
|
||||
switch (m.type) {
|
||||
case 'unknown':
|
||||
@ -111,9 +39,9 @@ function Media ({ media_attachments, sensitive, width }) {
|
||||
height: m.meta.original.height
|
||||
})
|
||||
return (
|
||||
<TouchableHighlight
|
||||
<Pressable
|
||||
key={i}
|
||||
style={calWidth(i)}
|
||||
style={{ flexGrow: 1, height: width / 5, margin: 4 }}
|
||||
onPress={() => {
|
||||
setImageModalIndex(i)
|
||||
setImageModalVisible(true)
|
||||
@ -122,9 +50,9 @@ function Media ({ media_attachments, sensitive, width }) {
|
||||
<Image
|
||||
source={{ uri: m.preview_url }}
|
||||
style={styles.image}
|
||||
blurRadius={mediaSensitive ? 50 : 0}
|
||||
blurRadius={mediaSensitive ? width / 5 : 0}
|
||||
/>
|
||||
</TouchableHighlight>
|
||||
</Pressable>
|
||||
)
|
||||
}
|
||||
})
|
||||
@ -181,54 +109,79 @@ export default function Content ({
|
||||
mentions,
|
||||
sensitive,
|
||||
spoiler_text,
|
||||
tags,
|
||||
width
|
||||
}) {
|
||||
const navigation = useNavigation()
|
||||
const [spoilerCollapsed, setSpoilerCollapsed] = useState(true)
|
||||
|
||||
let fullContent = []
|
||||
if (content) {
|
||||
fullContent.push(
|
||||
<HTMLView
|
||||
key='content'
|
||||
value={content}
|
||||
renderNode={(node, index) =>
|
||||
renderNode(navigation, node, index, mentions)
|
||||
}
|
||||
TextComponent={({ children }) => (
|
||||
<Emojis content={children} emojis={emojis} dimension={14} />
|
||||
)}
|
||||
return (
|
||||
<>
|
||||
{content &&
|
||||
(spoiler_text ? (
|
||||
<>
|
||||
<Text>
|
||||
{spoiler_text}{' '}
|
||||
<Text onPress={() => setSpoilerCollapsed(!spoilerCollapsed)}>
|
||||
点击展开
|
||||
</Text>
|
||||
</Text>
|
||||
<Collapsible collapsed={spoilerCollapsed}>
|
||||
<ParseContent
|
||||
content={content}
|
||||
emojis={emojis}
|
||||
emojiSize={14}
|
||||
mentions={mentions}
|
||||
/>
|
||||
)
|
||||
}
|
||||
if (media_attachments) {
|
||||
fullContent.push(
|
||||
</Collapsible>
|
||||
</>
|
||||
) : (
|
||||
<ParseContent
|
||||
content={content}
|
||||
emojis={emojis}
|
||||
emojiSize={14}
|
||||
mentions={mentions}
|
||||
/>
|
||||
))}
|
||||
{media_attachments.length > 0 && (
|
||||
<View
|
||||
style={{
|
||||
width: width + 8,
|
||||
height: width / 2,
|
||||
marginTop: 4,
|
||||
marginLeft: -4
|
||||
}}
|
||||
>
|
||||
<Media
|
||||
key='media'
|
||||
media_attachments={media_attachments}
|
||||
sensitive={sensitive}
|
||||
width={width}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
return fullContent
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
media: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between'
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
flexWrap: 'wrap',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'stretch',
|
||||
alignContent: 'stretch'
|
||||
},
|
||||
image: {
|
||||
width: '100%',
|
||||
height: '100%'
|
||||
},
|
||||
a: {
|
||||
color: 'blue'
|
||||
}
|
||||
})
|
||||
|
||||
Content.propTypes = {
|
||||
content: PropTypes.string
|
||||
content: ParseContent.propTypes.content,
|
||||
emojis: ParseContent.propTypes.emojis,
|
||||
// media_attachments
|
||||
mentions: ParseContent.propTypes.mentions,
|
||||
sensitive: PropTypes.bool.isRequired,
|
||||
spoiler_text: PropTypes.string,
|
||||
width: PropTypes.number.isRequired
|
||||
}
|
||||
|
@ -2,17 +2,16 @@ 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_]+):/)
|
||||
const regexEmoji = new RegExp(/(:.*?:)/g)
|
||||
|
||||
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 emojiShortcode = str.split(regexEmoji)[1]
|
||||
const emojiIndex = emojis.findIndex(emoji => {
|
||||
return emoji.shortcode === emojiShortcode
|
||||
return emojiShortcode === `:${emoji.shortcode}:`
|
||||
})
|
||||
return (
|
||||
<Image
|
||||
|
@ -14,11 +14,12 @@ export default function Header ({
|
||||
}) {
|
||||
const [since, setSince] = useState(relativeTime(created_at))
|
||||
|
||||
// causing full re-render
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
setSince(relativeTime(created_at))
|
||||
}, 1000)
|
||||
})
|
||||
}, [since])
|
||||
|
||||
return (
|
||||
<View>
|
||||
|
@ -1,11 +1,89 @@
|
||||
import React, { useEffect } from 'react'
|
||||
import { SafeAreaView, ScrollView, Text } from 'react-native'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import {
|
||||
Dimensions,
|
||||
Image,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
Text,
|
||||
View
|
||||
} from 'react-native'
|
||||
import HTMLView from 'react-native-htmlview'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import { useFocusEffect } from '@react-navigation/native'
|
||||
import { Feather } from '@expo/vector-icons'
|
||||
|
||||
import { fetch, getState, reset } from 'src/stacks/common/accountSlice'
|
||||
import * as accountSlice from 'src/stacks/common/accountSlice'
|
||||
import * as relationshipsSlice from 'src/stacks/common/relationshipsSlice'
|
||||
|
||||
// Show remote hashtag? Only when private, show local version?
|
||||
import ParseContent from 'src/components/ParseContent'
|
||||
|
||||
// Moved account example: https://m.cmx.im/web/accounts/27812
|
||||
|
||||
function Header ({ uri, size }) {
|
||||
if (uri) {
|
||||
return (
|
||||
<Image
|
||||
source={{ uri: uri }}
|
||||
style={styles.header(size ? size.height / size.width : 1 / 2)}
|
||||
/>
|
||||
)
|
||||
} else {
|
||||
return <View style={styles.header(1 / 3)} />
|
||||
}
|
||||
}
|
||||
|
||||
function Information ({ account, emojis }) {
|
||||
return (
|
||||
<View style={styles.information}>
|
||||
{/* <Text>Moved or not: {account.moved}</Text> */}
|
||||
<Image source={{ uri: account.avatar }} style={styles.avatar} />
|
||||
|
||||
<Text style={styles.display_name}>
|
||||
{account.display_name || account.username}
|
||||
{account.bot && (
|
||||
<Feather name='hard-drive' style={styles.display_name} />
|
||||
)}
|
||||
</Text>
|
||||
<Text style={styles.account}>
|
||||
{account.acct}
|
||||
{account.locked && <Feather name='lock' />}
|
||||
</Text>
|
||||
|
||||
{account.fields &&
|
||||
account.fields.map((field, index) => (
|
||||
<View key={index} style={{ flex: 1, flexDirection: 'row' }}>
|
||||
<Text style={{ width: '30%', alignSelf: 'center' }}>
|
||||
<ParseContent content={field.name} emojis={emojis} showFullLink />{' '}
|
||||
{field.verified_at && <Feather name='check-circle' />}
|
||||
</Text>
|
||||
<Text style={{ width: '70%' }}>
|
||||
<ParseContent
|
||||
content={field.value}
|
||||
emojis={emojis}
|
||||
showFullLink
|
||||
/>
|
||||
</Text>
|
||||
</View>
|
||||
))}
|
||||
|
||||
<Text>
|
||||
<ParseContent content={account.note} emojis={emojis} />
|
||||
</Text>
|
||||
<Text>
|
||||
加入时间{' '}
|
||||
{new Date(account.created_at).toLocaleDateString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
})}
|
||||
</Text>
|
||||
|
||||
<Text>Toots: {account.statuses_count}</Text>
|
||||
<Text>Followers: {account.followers_count}</Text>
|
||||
<Text>Following: {account.following_count}</Text>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
export default function Account ({
|
||||
route: {
|
||||
@ -13,29 +91,74 @@ export default function Account ({
|
||||
}
|
||||
}) {
|
||||
const dispatch = useDispatch()
|
||||
const state = useSelector(getState)
|
||||
const accountState = useSelector(accountSlice.retrive)
|
||||
// const stateRelationships = useSelector(relationshipsState)
|
||||
const [loaded, setLoaded] = useState(false)
|
||||
const [headerImageSize, setHeaderImageSize] = useState()
|
||||
|
||||
useEffect(() => {
|
||||
if (state.status === 'idle') {
|
||||
dispatch(fetch({ id }))
|
||||
if (accountState.status === 'idle') {
|
||||
dispatch(accountSlice.fetch({ id }))
|
||||
}
|
||||
}, [state, dispatch])
|
||||
|
||||
if (accountState.account.header) {
|
||||
Image.getSize(accountState.account.header, (width, height) => {
|
||||
setHeaderImageSize({ width, height })
|
||||
setLoaded(true)
|
||||
})
|
||||
} else {
|
||||
setLoaded(true)
|
||||
}
|
||||
}, [accountState, dispatch])
|
||||
|
||||
useFocusEffect(
|
||||
React.useCallback(() => {
|
||||
// Do something when the screen is focused
|
||||
|
||||
return () => {
|
||||
dispatch(reset())
|
||||
dispatch(accountSlice.reset())
|
||||
}
|
||||
}, [])
|
||||
)
|
||||
|
||||
return (
|
||||
<SafeAreaView>
|
||||
// add emoji support
|
||||
return loaded ? (
|
||||
<ScrollView>
|
||||
<Text>{state.account.acct}</Text>
|
||||
<Header
|
||||
uri={accountState.account.header}
|
||||
size={
|
||||
headerImageSize && {
|
||||
width: headerImageSize.width,
|
||||
height: headerImageSize.height
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Information
|
||||
account={accountState.account}
|
||||
emojis={accountState.emojis}
|
||||
/>
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
) : (
|
||||
<></>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
header: ratio => ({
|
||||
width: '100%',
|
||||
height: Dimensions.get('window').width * ratio,
|
||||
backgroundColor: 'gray'
|
||||
}),
|
||||
information: { marginTop: -30, paddingLeft: 12, paddingRight: 12 },
|
||||
avatar: {
|
||||
width: 90,
|
||||
height: 90
|
||||
},
|
||||
display_name: {
|
||||
fontSize: 18,
|
||||
fontWeight: 'bold',
|
||||
marginTop: 12
|
||||
},
|
||||
account: {
|
||||
marginTop: 4
|
||||
}
|
||||
})
|
||||
|
@ -33,7 +33,12 @@ export default function Timeline ({ page, hashtag, list }) {
|
||||
<TootTimeline key={item.key} item={item} />
|
||||
)}
|
||||
onRefresh={() =>
|
||||
dispatch(fetch({ page, query: { since_id: state.toots[0].id } }))
|
||||
dispatch(
|
||||
fetch({
|
||||
page,
|
||||
query: [{ key: 'since_id', value: state.toots[0].id }]
|
||||
})
|
||||
)
|
||||
}
|
||||
refreshing={state.status === 'loading'}
|
||||
onEndReached={() => {
|
||||
@ -41,9 +46,12 @@ export default function Timeline ({ page, hashtag, list }) {
|
||||
dispatch(
|
||||
fetch({
|
||||
page,
|
||||
query: {
|
||||
max_id: state.toots[state.toots.length - 1].id
|
||||
query: [
|
||||
{
|
||||
key: 'max_id',
|
||||
value: state.toots[state.toots.length - 1].id
|
||||
}
|
||||
]
|
||||
})
|
||||
)
|
||||
setTimelineReady(true)
|
||||
|
@ -74,9 +74,11 @@ export default function TimelinesCombined ({ name, content }) {
|
||||
<Stack.Screen
|
||||
name='Account'
|
||||
component={Account}
|
||||
options={({ route }) => ({
|
||||
title: `${route.params.id}`
|
||||
})}
|
||||
options={{
|
||||
headerTranslucent: true,
|
||||
headerStyle: { backgroundColor: 'rgba(255, 255, 255, 0)' },
|
||||
headerCenter: () => {}
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='Hashtag'
|
||||
|
@ -16,18 +16,13 @@ const accountInitState = {
|
||||
status: 'idle'
|
||||
}
|
||||
|
||||
export const getState = state => state.account
|
||||
export const retrive = state => state.account
|
||||
|
||||
export const accountSlice = createSlice({
|
||||
name: 'account',
|
||||
initialState: {
|
||||
account: {},
|
||||
status: 'idle'
|
||||
},
|
||||
initialState: accountInitState,
|
||||
reducers: {
|
||||
reset: state => {
|
||||
state.account = accountInitState
|
||||
}
|
||||
reset: () => accountInitState
|
||||
},
|
||||
extraReducers: {
|
||||
[fetch.pending]: state => {
|
||||
|
0
src/stacks/common/interfaceSlice.js
Normal file
0
src/stacks/common/interfaceSlice.js
Normal file
60
src/stacks/common/relationshipsSlice.js
Normal file
60
src/stacks/common/relationshipsSlice.js
Normal file
@ -0,0 +1,60 @@
|
||||
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'
|
||||
|
||||
import { client } from 'src/api/client'
|
||||
|
||||
export const fetch = createAsyncThunk(
|
||||
'relationships/fetch',
|
||||
async ({ ids }, { getState }) => {
|
||||
if (!ids.length) console.error('Relationships empty')
|
||||
const instanceLocal = getState().instanceInfo.local + '/api/v1/'
|
||||
const query = ids.map(id => ({
|
||||
key: 'id[]',
|
||||
value: id
|
||||
}))
|
||||
const header = {
|
||||
headers: {
|
||||
Authorization: `Bearer ${getState().instanceInfo.localToken}`
|
||||
}
|
||||
}
|
||||
|
||||
return await client.get(
|
||||
`${instanceLocal}accounts/relationships`,
|
||||
query,
|
||||
header
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
const relationshipsInitState = {
|
||||
relationships: [],
|
||||
status: 'idle'
|
||||
}
|
||||
|
||||
export const retrive = state => state.relationships
|
||||
|
||||
export const relationshipSlice = createSlice({
|
||||
name: 'relationships',
|
||||
initialState: {
|
||||
relationships: [],
|
||||
status: 'idle'
|
||||
},
|
||||
reducers: {
|
||||
reset: () => relationshipsInitState
|
||||
},
|
||||
extraReducers: {
|
||||
[fetch.pending]: state => {
|
||||
state.status = 'loading'
|
||||
},
|
||||
[fetch.fulfilled]: (state, action) => {
|
||||
state.status = 'succeeded'
|
||||
state.relationships = action.payload
|
||||
},
|
||||
[fetch.rejected]: (state, action) => {
|
||||
state.status = 'failed'
|
||||
console.error(action.error.message)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export const { reset } = relationshipSlice.actions
|
||||
export default relationshipSlice.reducer
|
@ -3,6 +3,7 @@ import { configureStore } from '@reduxjs/toolkit'
|
||||
import instanceInfoSlice from 'src/stacks/common/instanceInfoSlice'
|
||||
import timelineSlice from 'src/stacks/common/timelineSlice'
|
||||
import accountSlice from 'src/stacks/common/accountSlice'
|
||||
import relationshipsSlice from 'src/stacks/common/relationshipsSlice'
|
||||
|
||||
// get site information from local storage and pass to reducers
|
||||
const preloadedState = {
|
||||
@ -16,7 +17,8 @@ const preloadedState = {
|
||||
const reducer = {
|
||||
instanceInfo: instanceInfoSlice,
|
||||
timelines: timelineSlice,
|
||||
account: accountSlice
|
||||
account: accountSlice,
|
||||
relationships: relationshipsSlice
|
||||
}
|
||||
|
||||
export default configureStore({
|
||||
|
@ -13,7 +13,7 @@ import { client } from 'src/api/client'
|
||||
|
||||
export const fetch = createAsyncThunk(
|
||||
'timeline/fetch',
|
||||
async ({ page, query, hashtag, list }, { getState }) => {
|
||||
async ({ page, query = [], hashtag, list }, { getState }) => {
|
||||
const instanceLocal = getState().instanceInfo.local + '/api/v1/'
|
||||
const instanceRemote = getState().instanceInfo.remote + '/api/v1/'
|
||||
const header = {
|
||||
@ -26,7 +26,7 @@ export const fetch = createAsyncThunk(
|
||||
case 'Following':
|
||||
return await client.get(`${instanceLocal}timelines/home`, query, header)
|
||||
case 'Local':
|
||||
query ? (query.local = true) : (query = { local: 'true' })
|
||||
query.push({ key: 'local', value: 'true' })
|
||||
return await client.get(
|
||||
`${instanceLocal}timelines/public`,
|
||||
query,
|
||||
|
Loading…
x
Reference in New Issue
Block a user