mirror of https://github.com/tooot-app/app
React-Query fully working on existing components
This commit is contained in:
parent
d1f32524ba
commit
631636db15
16
App.tsx
16
App.tsx
|
@ -1,6 +1,20 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import { QueryCache, ReactQueryCacheProvider, setConsole } from 'react-query'
|
||||||
|
|
||||||
import { Index } from 'src/Index'
|
import { Index } from 'src/Index'
|
||||||
|
|
||||||
const App: React.FC = () => <Index />
|
const queryCache = new QueryCache()
|
||||||
|
|
||||||
|
setConsole({
|
||||||
|
log: console.log,
|
||||||
|
warn: console.warn,
|
||||||
|
error: console.warn
|
||||||
|
})
|
||||||
|
|
||||||
|
const App: React.FC = () => (
|
||||||
|
<ReactQueryCacheProvider queryCache={queryCache}>
|
||||||
|
<Index />
|
||||||
|
</ReactQueryCacheProvider>
|
||||||
|
)
|
||||||
|
|
||||||
export default App
|
export default App
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -38,6 +38,7 @@
|
||||||
"react-native-web": "~0.13.7",
|
"react-native-web": "~0.13.7",
|
||||||
"react-native-webview": "10.7.0",
|
"react-native-webview": "10.7.0",
|
||||||
"react-navigation": "^4.4.3",
|
"react-navigation": "^4.4.3",
|
||||||
|
"react-query": "^2.26.1",
|
||||||
"react-redux": "^7.2.1"
|
"react-redux": "^7.2.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { StyleSheet, Text } from 'react-native'
|
||||||
import HTMLView, { HTMLViewNode } from 'react-native-htmlview'
|
import HTMLView, { HTMLViewNode } from 'react-native-htmlview'
|
||||||
import { useNavigation } from '@react-navigation/native'
|
import { useNavigation } from '@react-navigation/native'
|
||||||
|
|
||||||
import Emojis from 'src/components/Toot/Emojis'
|
import Emojis from 'src/components/Status/Emojis'
|
||||||
|
|
||||||
const renderNode = ({
|
const renderNode = ({
|
||||||
node,
|
node,
|
||||||
|
@ -14,7 +14,7 @@ const renderNode = ({
|
||||||
}: {
|
}: {
|
||||||
node: HTMLViewNode
|
node: HTMLViewNode
|
||||||
index: number
|
index: number
|
||||||
navigation: object
|
navigation: any
|
||||||
mentions?: mastodon.Mention[]
|
mentions?: mastodon.Mention[]
|
||||||
showFullLink: boolean
|
showFullLink: boolean
|
||||||
}) => {
|
}) => {
|
||||||
|
|
|
@ -42,7 +42,6 @@ const Actions: React.FC<Props> = ({
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setSuccessMessage(undefined)
|
setSuccessMessage(undefined)
|
||||||
console.log('ajwieorjawioejri')
|
|
||||||
}, 2000)
|
}, 2000)
|
||||||
return () => {}
|
return () => {}
|
||||||
}, [successMessage])
|
}, [successMessage])
|
|
@ -20,9 +20,7 @@ const Emojis: React.FC<Props> = ({ content, emojis, dimension }) => {
|
||||||
return emojiShortcode === `:${emoji.shortcode}:`
|
return emojiShortcode === `:${emoji.shortcode}:`
|
||||||
})
|
})
|
||||||
return emojiIndex === -1 ? (
|
return emojiIndex === -1 ? (
|
||||||
<Text key={i} style={{ color: 'red' }}>
|
<Text key={i}>{emojiShortcode}</Text>
|
||||||
Something wrong with emoji!
|
|
||||||
</Text>
|
|
||||||
) : (
|
) : (
|
||||||
<Image
|
<Image
|
||||||
key={i}
|
key={i}
|
|
@ -8,7 +8,7 @@ export interface Props {
|
||||||
const Success: React.FC<Props> = ({ message }) => {
|
const Success: React.FC<Props> = ({ message }) => {
|
||||||
const fadeAnim = useRef(new Animated.Value(0)).current
|
const fadeAnim = useRef(new Animated.Value(0)).current
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log(message)
|
// console.log(message)
|
||||||
if (message !== undefined) {
|
if (message !== undefined) {
|
||||||
fadeIn()
|
fadeIn()
|
||||||
} else {
|
} else {
|
|
@ -2,7 +2,7 @@ import { Dispatch } from '@reduxjs/toolkit'
|
||||||
import { Alert } from 'react-native'
|
import { Alert } from 'react-native'
|
||||||
|
|
||||||
import client from 'src/api/client'
|
import client from 'src/api/client'
|
||||||
import { updateStatus } from 'src/stacks/common/timelineSlice'
|
// import { updateStatus } from 'src/stacks/common/timelineSlice'
|
||||||
|
|
||||||
const action = async ({
|
const action = async ({
|
||||||
dispatch,
|
dispatch,
|
||||||
|
@ -35,7 +35,7 @@ const action = async ({
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!res.body[stateKey] === statePrev) {
|
if (!res.body[stateKey] === statePrev) {
|
||||||
dispatch(updateStatus(res.body))
|
// dispatch(updateStatus(res.body))
|
||||||
} else {
|
} else {
|
||||||
Alert.alert(alert.title, alert.message, [
|
Alert.alert(alert.title, alert.message, [
|
||||||
{ text: 'OK', onPress: () => console.log('OK Pressed') }
|
{ text: 'OK', onPress: () => console.log('OK Pressed') }
|
|
@ -0,0 +1,107 @@
|
||||||
|
import React, { useMemo } from 'react'
|
||||||
|
import { Dimensions, Pressable, StyleSheet, View } from 'react-native'
|
||||||
|
import { useNavigation } from '@react-navigation/native'
|
||||||
|
|
||||||
|
import Actioned from './Status/Actioned'
|
||||||
|
import Avatar from './Status/Avatar'
|
||||||
|
import Header from './Status/Header'
|
||||||
|
import Content from './Status/Content'
|
||||||
|
import Poll from './Status/Poll'
|
||||||
|
import Attachment from './Status/Attachment'
|
||||||
|
import Card from './Status/Card'
|
||||||
|
import Actions from './Status/Actions'
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
status: mastodon.Notification
|
||||||
|
}
|
||||||
|
|
||||||
|
const TootNotification: React.FC<Props> = ({ status }) => {
|
||||||
|
const navigation = useNavigation()
|
||||||
|
const actualAccount = status.status ? status.status.account : status.account
|
||||||
|
|
||||||
|
const statusView = useMemo(() => {
|
||||||
|
return (
|
||||||
|
<View style={styles.statusView}>
|
||||||
|
<Actioned
|
||||||
|
action={status.type}
|
||||||
|
name={status.account.display_name || status.account.username}
|
||||||
|
emojis={status.account.emojis}
|
||||||
|
notification
|
||||||
|
/>
|
||||||
|
|
||||||
|
<View style={styles.status}>
|
||||||
|
<Avatar uri={actualAccount.avatar} id={actualAccount.id} />
|
||||||
|
<View style={styles.details}>
|
||||||
|
<Header
|
||||||
|
name={actualAccount.display_name || actualAccount.username}
|
||||||
|
emojis={actualAccount.emojis}
|
||||||
|
account={actualAccount.acct}
|
||||||
|
created_at={status.created_at}
|
||||||
|
/>
|
||||||
|
<Pressable
|
||||||
|
onPress={() => navigation.navigate('Toot', { toot: status.id })}
|
||||||
|
>
|
||||||
|
{status.status ? (
|
||||||
|
<>
|
||||||
|
{status.status.content && (
|
||||||
|
<Content
|
||||||
|
content={status.status.content}
|
||||||
|
emojis={status.status.emojis}
|
||||||
|
mentions={status.status.mentions}
|
||||||
|
spoiler_text={status.status.spoiler_text}
|
||||||
|
// tags={status.status.tags}
|
||||||
|
// style={{ flex: 1 }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{status.status.poll && <Poll poll={status.status.poll} />}
|
||||||
|
{status.status.media_attachments.length > 0 && (
|
||||||
|
<Attachment
|
||||||
|
media_attachments={status.status.media_attachments}
|
||||||
|
sensitive={status.status.sensitive}
|
||||||
|
width={Dimensions.get('window').width - 24 - 50 - 8}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{status.status.card && <Card card={status.status.card} />}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
</Pressable>
|
||||||
|
{status.status && (
|
||||||
|
<Actions
|
||||||
|
id={status.status.id}
|
||||||
|
url={status.status.url}
|
||||||
|
replies_count={status.status.replies_count}
|
||||||
|
reblogs_count={status.status.reblogs_count}
|
||||||
|
reblogged={status.status.reblogged}
|
||||||
|
favourites_count={status.status.favourites_count}
|
||||||
|
favourited={status.status.favourited}
|
||||||
|
bookmarked={status.status.bookmarked}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}, [status])
|
||||||
|
|
||||||
|
return statusView
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
statusView: {
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: 'column',
|
||||||
|
padding: 12
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: 'row'
|
||||||
|
},
|
||||||
|
details: {
|
||||||
|
flex: 1,
|
||||||
|
flexGrow: 1
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default TootNotification
|
|
@ -2,35 +2,35 @@ import React, { useMemo } from 'react'
|
||||||
import { Dimensions, Pressable, StyleSheet, View } from 'react-native'
|
import { Dimensions, Pressable, StyleSheet, View } from 'react-native'
|
||||||
import { useNavigation } from '@react-navigation/native'
|
import { useNavigation } from '@react-navigation/native'
|
||||||
|
|
||||||
import Actioned from './Toot/Actioned'
|
import Actioned from './Status/Actioned'
|
||||||
import Avatar from './Toot/Avatar'
|
import Avatar from './Status/Avatar'
|
||||||
import Header from './Toot/Header'
|
import Header from './Status/Header'
|
||||||
import Content from './Toot/Content'
|
import Content from './Status/Content'
|
||||||
import Poll from './Toot/Poll'
|
import Poll from './Status/Poll'
|
||||||
import Attachment from './Toot/Attachment'
|
import Attachment from './Status/Attachment'
|
||||||
import Card from './Toot/Card'
|
import Card from './Status/Card'
|
||||||
import Actions from './Toot/Actions'
|
import Actions from './Status/Actions'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
toot: mastodon.Status
|
status: mastodon.Status
|
||||||
}
|
}
|
||||||
|
|
||||||
const TootTimeline: React.FC<Props> = ({ toot }) => {
|
const StatusInTimeline: React.FC<Props> = ({ status }) => {
|
||||||
const navigation = useNavigation()
|
const navigation = useNavigation()
|
||||||
|
|
||||||
let actualContent = toot.reblog ? toot.reblog : toot
|
let actualContent = status.reblog ? status.reblog : status
|
||||||
|
|
||||||
const tootView = useMemo(() => {
|
const statusView = useMemo(() => {
|
||||||
return (
|
return (
|
||||||
<View style={styles.tootTimeline}>
|
<View style={styles.statusView}>
|
||||||
{toot.reblog && (
|
{status.reblog && (
|
||||||
<Actioned
|
<Actioned
|
||||||
action='reblog'
|
action='reblog'
|
||||||
name={toot.account.display_name || toot.account.username}
|
name={status.account.display_name || status.account.username}
|
||||||
emojis={toot.account.emojis}
|
emojis={status.account.emojis}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<View style={styles.toot}>
|
<View style={styles.status}>
|
||||||
<Avatar
|
<Avatar
|
||||||
uri={actualContent.account.avatar}
|
uri={actualContent.account.avatar}
|
||||||
id={actualContent.account.id}
|
id={actualContent.account.id}
|
||||||
|
@ -43,8 +43,8 @@ const TootTimeline: React.FC<Props> = ({ toot }) => {
|
||||||
}
|
}
|
||||||
emojis={actualContent.account.emojis}
|
emojis={actualContent.account.emojis}
|
||||||
account={actualContent.account.acct}
|
account={actualContent.account.acct}
|
||||||
created_at={toot.created_at}
|
created_at={status.created_at}
|
||||||
application={toot.application}
|
application={status.application}
|
||||||
/>
|
/>
|
||||||
{/* Can pass toot info to next page to speed up performance */}
|
{/* Can pass toot info to next page to speed up performance */}
|
||||||
<Pressable
|
<Pressable
|
||||||
|
@ -88,18 +88,18 @@ const TootTimeline: React.FC<Props> = ({ toot }) => {
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}, [toot])
|
}, [status])
|
||||||
|
|
||||||
return tootView
|
return statusView
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
tootTimeline: {
|
statusView: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
padding: 12
|
padding: 12
|
||||||
},
|
},
|
||||||
toot: {
|
status: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
flexDirection: 'row'
|
flexDirection: 'row'
|
||||||
},
|
},
|
||||||
|
@ -109,4 +109,4 @@ const styles = StyleSheet.create({
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
export default TootTimeline
|
export default StatusInTimeline
|
|
@ -1,106 +0,0 @@
|
||||||
import React, { useMemo } from 'react'
|
|
||||||
import { Dimensions, Pressable, StyleSheet, Text, View } from 'react-native'
|
|
||||||
import { useNavigation } from '@react-navigation/native'
|
|
||||||
|
|
||||||
import Actioned from './Toot/Actioned'
|
|
||||||
import Avatar from './Toot/Avatar'
|
|
||||||
import Header from './Toot/Header'
|
|
||||||
import Content from './Toot/Content'
|
|
||||||
import Poll from './Toot/Poll'
|
|
||||||
import Attachment from './Toot/Attachment'
|
|
||||||
import Card from './Toot/Card'
|
|
||||||
import Actions from './Toot/Actions'
|
|
||||||
|
|
||||||
export interface Props {
|
|
||||||
toot: mastodon.Notification
|
|
||||||
}
|
|
||||||
|
|
||||||
const TootNotification: React.FC<Props> = ({ toot }) => {
|
|
||||||
const navigation = useNavigation()
|
|
||||||
const actualAccount = toot.status ? toot.status.account : toot.account
|
|
||||||
|
|
||||||
const tootView = useMemo(() => {
|
|
||||||
return (
|
|
||||||
<View style={styles.tootNotification}>
|
|
||||||
<Actioned
|
|
||||||
action={toot.type}
|
|
||||||
name={toot.account.display_name || toot.account.username}
|
|
||||||
emojis={toot.account.emojis}
|
|
||||||
notification
|
|
||||||
/>
|
|
||||||
|
|
||||||
<View style={styles.toot}>
|
|
||||||
<Avatar uri={actualAccount.avatar} id={actualAccount.id} />
|
|
||||||
<View style={styles.details}>
|
|
||||||
<Header
|
|
||||||
name={actualAccount.display_name || actualAccount.username}
|
|
||||||
emojis={actualAccount.emojis}
|
|
||||||
account={actualAccount.acct}
|
|
||||||
created_at={toot.created_at}
|
|
||||||
/>
|
|
||||||
<Pressable
|
|
||||||
onPress={() => navigation.navigate('Toot', { toot: toot.id })}
|
|
||||||
>
|
|
||||||
{toot.status ? (
|
|
||||||
<>
|
|
||||||
{toot.status.content && (
|
|
||||||
<Content
|
|
||||||
content={toot.status.content}
|
|
||||||
emojis={toot.status.emojis}
|
|
||||||
mentions={toot.status.mentions}
|
|
||||||
spoiler_text={toot.status.spoiler_text}
|
|
||||||
// tags={toot.status.tags}
|
|
||||||
// style={{ flex: 1 }}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{toot.status.poll && <Poll poll={toot.status.poll} />}
|
|
||||||
{toot.status.media_attachments.length > 0 && (
|
|
||||||
<Attachment
|
|
||||||
media_attachments={toot.status.media_attachments}
|
|
||||||
sensitive={toot.status.sensitive}
|
|
||||||
width={Dimensions.get('window').width - 24 - 50 - 8}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{toot.status.card && <Card card={toot.status.card} />}
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<></>
|
|
||||||
)}
|
|
||||||
</Pressable>
|
|
||||||
{toot.status && (
|
|
||||||
<Actions
|
|
||||||
id={toot.status.id}
|
|
||||||
replies_count={toot.status.replies_count}
|
|
||||||
reblogs_count={toot.status.reblogs_count}
|
|
||||||
reblogged={toot.status.reblogged}
|
|
||||||
favourites_count={toot.status.favourites_count}
|
|
||||||
favourited={toot.status.favourited}
|
|
||||||
bookmarked={toot.status.bookmarked}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}, [toot])
|
|
||||||
|
|
||||||
return tootView
|
|
||||||
}
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
tootNotification: {
|
|
||||||
flex: 1,
|
|
||||||
flexDirection: 'column',
|
|
||||||
padding: 12
|
|
||||||
},
|
|
||||||
toot: {
|
|
||||||
flex: 1,
|
|
||||||
flexDirection: 'row'
|
|
||||||
},
|
|
||||||
details: {
|
|
||||||
flex: 1,
|
|
||||||
flexGrow: 1
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
export default TootNotification
|
|
|
@ -9,16 +9,14 @@ import {
|
||||||
View
|
View
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
import SegmentedControl from '@react-native-community/segmented-control'
|
import SegmentedControl from '@react-native-community/segmented-control'
|
||||||
import { useDispatch, useSelector } from 'react-redux'
|
|
||||||
import { useFocusEffect } from '@react-navigation/native'
|
|
||||||
import { Feather } from '@expo/vector-icons'
|
import { Feather } from '@expo/vector-icons'
|
||||||
|
|
||||||
import * as accountSlice from 'src/stacks/common/accountSlice'
|
// import * as relationshipsSlice from 'src/stacks/common/relationshipsSlice'
|
||||||
import * as relationshipsSlice from 'src/stacks/common/relationshipsSlice'
|
|
||||||
import * as timelineSlice from 'src/stacks/common/timelineSlice'
|
|
||||||
|
|
||||||
import ParseContent from 'src/components/ParseContent'
|
import ParseContent from 'src/components/ParseContent'
|
||||||
import Timeline from 'src/stacks/common/Timeline'
|
import Timeline from 'src/stacks/common/Timeline'
|
||||||
|
import { useQuery } from 'react-query'
|
||||||
|
import { accountFetch } from '../common/accountFetch'
|
||||||
|
|
||||||
// Moved account example: https://m.cmx.im/web/accounts/27812
|
// Moved account example: https://m.cmx.im/web/accounts/27812
|
||||||
|
|
||||||
|
@ -33,11 +31,25 @@ const Header = ({
|
||||||
return (
|
return (
|
||||||
<Image
|
<Image
|
||||||
source={{ uri: uri }}
|
source={{ uri: uri }}
|
||||||
style={styles.header(size ? size.height / size.width : 1 / 2)}
|
style={[
|
||||||
|
styles.header,
|
||||||
|
{
|
||||||
|
height:
|
||||||
|
Dimensions.get('window').width *
|
||||||
|
(size ? size.height / size.width : 1 / 2)
|
||||||
|
}
|
||||||
|
]}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
return <View style={styles.header(1 / 3)} />
|
return (
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
styles.header,
|
||||||
|
{ height: Dimensions.get('window').width * (1 / 3) }
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,7 +114,7 @@ const Toots = ({ account }: { account: string }) => {
|
||||||
const [segmentManuallyTriggered, setSegmentManuallyTriggered] = useState(
|
const [segmentManuallyTriggered, setSegmentManuallyTriggered] = useState(
|
||||||
false
|
false
|
||||||
)
|
)
|
||||||
const horizontalPaging = useRef()
|
const horizontalPaging = useRef<any>()
|
||||||
|
|
||||||
const pages = ['Account_Default', 'Account_All', 'Account_Media']
|
const pages = ['Account_Default', 'Account_All', 'Account_Media']
|
||||||
|
|
||||||
|
@ -186,55 +198,46 @@ const Account: React.FC<Props> = ({
|
||||||
params: { id }
|
params: { id }
|
||||||
}
|
}
|
||||||
}) => {
|
}) => {
|
||||||
const dispatch = useDispatch()
|
const { isLoading, isFetchingMore, isError, isSuccess, data } = useQuery(
|
||||||
const accountState = useSelector(state => state.account)
|
['Account', { id }],
|
||||||
|
accountFetch
|
||||||
|
)
|
||||||
|
|
||||||
// const stateRelationships = useSelector(relationshipsState)
|
// const stateRelationships = useSelector(relationshipsState)
|
||||||
const [loaded, setLoaded] = useState(false)
|
const [loaded, setLoaded] = useState(false)
|
||||||
const [headerImageSize, setHeaderImageSize] = useState()
|
interface isHeaderImageSize {
|
||||||
|
width: number
|
||||||
|
height: number
|
||||||
|
}
|
||||||
|
const [headerImageSize, setHeaderImageSize] = useState<
|
||||||
|
isHeaderImageSize | undefined
|
||||||
|
>(undefined)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (accountState.status === 'idle') {
|
if (data.header) {
|
||||||
dispatch(accountSlice.fetch({ id }))
|
Image.getSize(data.header, (width, height) => {
|
||||||
}
|
|
||||||
if (accountState.account.header) {
|
|
||||||
Image.getSize(accountState.account.header, (width, height) => {
|
|
||||||
setHeaderImageSize({ width, height })
|
setHeaderImageSize({ width, height })
|
||||||
setLoaded(true)
|
setLoaded(true)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
setLoaded(true)
|
setLoaded(true)
|
||||||
}
|
}
|
||||||
}, [accountState, dispatch])
|
}, [data])
|
||||||
|
|
||||||
useFocusEffect(
|
|
||||||
React.useCallback(() => {
|
|
||||||
// Do something when the screen is focused
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
dispatch(accountSlice.reset())
|
|
||||||
dispatch(timelineSlice.reset('Account_Default'))
|
|
||||||
dispatch(timelineSlice.reset('Account_All'))
|
|
||||||
dispatch(timelineSlice.reset('Account_Media'))
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
)
|
|
||||||
// add emoji support
|
// add emoji support
|
||||||
return loaded ? (
|
return isSuccess ? (
|
||||||
<View>
|
<View>
|
||||||
<Header
|
{headerImageSize && (
|
||||||
uri={accountState.account.header}
|
<Header
|
||||||
size={
|
uri={data.header}
|
||||||
headerImageSize && {
|
size={{
|
||||||
width: headerImageSize.width,
|
width: headerImageSize.width,
|
||||||
height: headerImageSize.height
|
height: headerImageSize.height
|
||||||
}
|
}}
|
||||||
}
|
/>
|
||||||
/>
|
)}
|
||||||
<Information
|
<Information account={data} emojis={data.emojis} />
|
||||||
account={accountState.account}
|
<Toots account={id} />
|
||||||
emojis={accountState.account.emojis}
|
|
||||||
/>
|
|
||||||
{accountState.account.id && <Toots account={accountState.account.id} />}
|
|
||||||
</View>
|
</View>
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
|
@ -242,11 +245,10 @@ const Account: React.FC<Props> = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
header: ratio => ({
|
header: {
|
||||||
width: '100%',
|
width: '100%',
|
||||||
height: Dimensions.get('window').width * ratio,
|
|
||||||
backgroundColor: 'gray'
|
backgroundColor: 'gray'
|
||||||
}),
|
},
|
||||||
information: { marginTop: -30, paddingLeft: 12, paddingRight: 12 },
|
information: { marginTop: -30, paddingLeft: 12, paddingRight: 12 },
|
||||||
avatar: {
|
avatar: {
|
||||||
width: 90,
|
width: 90,
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { useDispatch } from 'react-redux'
|
|
||||||
import { useFocusEffect } from '@react-navigation/native'
|
|
||||||
|
|
||||||
import Timeline from 'src/stacks/common/Timeline'
|
import Timeline from 'src/stacks/common/Timeline'
|
||||||
import { reset } from 'src/stacks/common/timelineSlice'
|
|
||||||
|
|
||||||
// Show remote hashtag? Only when private, show local version?
|
// Show remote hashtag? Only when private, show local version?
|
||||||
|
|
||||||
|
@ -20,18 +17,6 @@ const Hashtag: React.FC<Props> = ({
|
||||||
params: { hashtag }
|
params: { hashtag }
|
||||||
}
|
}
|
||||||
}) => {
|
}) => {
|
||||||
const dispatch = useDispatch()
|
|
||||||
|
|
||||||
useFocusEffect(
|
|
||||||
React.useCallback(() => {
|
|
||||||
// Do something when the screen is focused
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
dispatch(reset('Hashtag'))
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
)
|
|
||||||
|
|
||||||
return <Timeline page='Hashtag' hashtag={hashtag} />
|
return <Timeline page='Hashtag' hashtag={hashtag} />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { useDispatch } from 'react-redux'
|
|
||||||
import { useFocusEffect } from '@react-navigation/native'
|
|
||||||
|
|
||||||
import Timeline from 'src/stacks/common/Timeline'
|
import Timeline from 'src/stacks/common/Timeline'
|
||||||
import { reset } from 'src/stacks/common/timelineSlice'
|
|
||||||
|
|
||||||
// Show remote hashtag? Only when private, show local version?
|
// Show remote hashtag? Only when private, show local version?
|
||||||
|
|
||||||
|
@ -20,18 +17,6 @@ const Toot: React.FC<Props> = ({
|
||||||
params: { toot }
|
params: { toot }
|
||||||
}
|
}
|
||||||
}) => {
|
}) => {
|
||||||
const dispatch = useDispatch()
|
|
||||||
|
|
||||||
useFocusEffect(
|
|
||||||
React.useCallback(() => {
|
|
||||||
// Do something when the screen is focused
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
dispatch(reset('Toot'))
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
)
|
|
||||||
|
|
||||||
return <Timeline page='Toot' toot={toot} disableRefresh />
|
return <Timeline page='Toot' toot={toot} disableRefresh />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,11 +5,7 @@ import Hashtag from 'src/stacks/Shared/Hashtag'
|
||||||
import Toot from 'src/stacks/Shared/Toot'
|
import Toot from 'src/stacks/Shared/Toot'
|
||||||
import Webview from 'src/stacks/Shared/Webview'
|
import Webview from 'src/stacks/Shared/Webview'
|
||||||
|
|
||||||
export interface Props {
|
const sharedScreens = (Stack: any) => {
|
||||||
Stack: any
|
|
||||||
}
|
|
||||||
|
|
||||||
const sharedScreens = Stack => {
|
|
||||||
return [
|
return [
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
key='Account'
|
key='Account'
|
||||||
|
|
|
@ -1,84 +1,93 @@
|
||||||
import React, { useEffect, useState } from 'react'
|
import React from 'react'
|
||||||
import { ActivityIndicator, FlatList, Text, View } from 'react-native'
|
import { ActivityIndicator, AppState, FlatList, Text, View } from 'react-native'
|
||||||
import { useSelector, useDispatch } from 'react-redux'
|
import { setFocusHandler, useInfiniteQuery } from 'react-query'
|
||||||
|
|
||||||
import TootNotification from 'src/components/TootNotification'
|
import StatusInNotifications from 'src/components/StatusInNotifications'
|
||||||
import TootTimeline from 'src/components/TootTimeline'
|
import StatusInTimeline from 'src/components/StatusInTimeline'
|
||||||
import { RootState } from 'src/stacks/common/store'
|
import { timelineFetch } from './timelineFetch'
|
||||||
import { fetch } from './timelineSlice'
|
|
||||||
|
|
||||||
// Opening nesting hashtag pages
|
// Opening nesting hashtag pages
|
||||||
|
|
||||||
const Timeline: React.FC<{
|
export interface Props {
|
||||||
page: store.TimelinePage
|
page: store.TimelinePage
|
||||||
hashtag?: string
|
hashtag?: string
|
||||||
list?: string
|
list?: string
|
||||||
toot?: string
|
toot?: string
|
||||||
account?: string
|
account?: string
|
||||||
disableRefresh?: boolean
|
disableRefresh?: boolean
|
||||||
}> = ({ page, hashtag, list, toot, account, disableRefresh = false }) => {
|
}
|
||||||
const dispatch = useDispatch()
|
|
||||||
const state = useSelector((state: RootState) => state.timelines[page])
|
|
||||||
const [timelineReady, setTimelineReady] = useState(false)
|
|
||||||
|
|
||||||
useEffect(() => {
|
const Timeline: React.FC<Props> = ({
|
||||||
let mounted = true
|
page,
|
||||||
if (state.status === 'idle' && mounted) {
|
hashtag,
|
||||||
dispatch(fetch({ page, hashtag, list, toot, account }))
|
list,
|
||||||
setTimelineReady(true)
|
toot,
|
||||||
|
account,
|
||||||
|
disableRefresh = false
|
||||||
|
}) => {
|
||||||
|
setFocusHandler(handleFocus => {
|
||||||
|
const handleAppStateChange = (appState: string) => {
|
||||||
|
if (appState === 'active') {
|
||||||
|
handleFocus()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return () => {
|
AppState.addEventListener('change', handleAppStateChange)
|
||||||
mounted = false
|
return () => AppState.removeEventListener('change', handleAppStateChange)
|
||||||
}
|
})
|
||||||
}, [state, dispatch])
|
|
||||||
|
const {
|
||||||
|
isLoading,
|
||||||
|
isFetchingMore,
|
||||||
|
isError,
|
||||||
|
isSuccess,
|
||||||
|
data,
|
||||||
|
fetchMore
|
||||||
|
} = useInfiniteQuery(
|
||||||
|
[page, { page, hashtag, list, toot, account }],
|
||||||
|
timelineFetch
|
||||||
|
)
|
||||||
|
const flattenData = data ? data.flatMap(d => [...d?.toots]) : []
|
||||||
|
|
||||||
let content
|
let content
|
||||||
if (state.status === 'failed') {
|
if (!isSuccess) {
|
||||||
|
content = <ActivityIndicator />
|
||||||
|
} else if (isError) {
|
||||||
content = <Text>Error message</Text>
|
content = <Text>Error message</Text>
|
||||||
} else {
|
} else {
|
||||||
content = (
|
content = (
|
||||||
<>
|
<>
|
||||||
<FlatList
|
<FlatList
|
||||||
style={{ minHeight: '100%' }}
|
style={{ minHeight: '100%' }}
|
||||||
data={state.toots}
|
data={flattenData}
|
||||||
keyExtractor={({ id }) => id}
|
keyExtractor={({ id }) => id}
|
||||||
renderItem={({ item, index, separators }) =>
|
renderItem={({ item, index, separators }) =>
|
||||||
page === 'Notifications' ? (
|
page === 'Notifications' ? (
|
||||||
<TootNotification key={index} toot={item} />
|
<StatusInNotifications key={index} status={item} />
|
||||||
) : (
|
) : (
|
||||||
<TootTimeline key={index} toot={item} />
|
<StatusInTimeline key={index} status={item} />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
// {...(state.pointer && { initialScrollIndex: state.pointer })}
|
// {...(state.pointer && { initialScrollIndex: state.pointer })}
|
||||||
{...(!disableRefresh && {
|
{...(!disableRefresh && {
|
||||||
onRefresh: () =>
|
onRefresh: () =>
|
||||||
dispatch(
|
fetchMore(
|
||||||
fetch({
|
{
|
||||||
page,
|
direction: 'prev',
|
||||||
hashtag,
|
id: flattenData[0].id
|
||||||
list,
|
},
|
||||||
paginationDirection: 'prev'
|
{ previous: true }
|
||||||
})
|
|
||||||
),
|
),
|
||||||
refreshing: state.status === 'loading',
|
refreshing: isLoading,
|
||||||
onEndReached: () => {
|
onEndReached: () => {
|
||||||
if (!timelineReady) {
|
fetchMore({
|
||||||
dispatch(
|
direction: 'next',
|
||||||
fetch({
|
id: flattenData[flattenData.length - 1].id
|
||||||
page,
|
})
|
||||||
hashtag,
|
|
||||||
list,
|
|
||||||
paginationDirection: 'next'
|
|
||||||
})
|
|
||||||
)
|
|
||||||
setTimelineReady(true)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
onEndReachedThreshold: 0.5
|
onEndReachedThreshold: 0.5
|
||||||
})}
|
})}
|
||||||
onMomentumScrollBegin={() => setTimelineReady(false)}
|
|
||||||
/>
|
/>
|
||||||
{state.status === 'loading' && <ActivityIndicator />}
|
{isFetchingMore && <ActivityIndicator />}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
import client from 'src/api/client'
|
||||||
|
|
||||||
|
export const accountFetch = async (key: string, { id }: { id: string }) => {
|
||||||
|
const res = await client({
|
||||||
|
method: 'get',
|
||||||
|
instance: 'local',
|
||||||
|
endpoint: `accounts/${id}`
|
||||||
|
})
|
||||||
|
return Promise.resolve(res.body)
|
||||||
|
}
|
|
@ -1,46 +0,0 @@
|
||||||
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'
|
|
||||||
|
|
||||||
import client from 'src/api/client'
|
|
||||||
|
|
||||||
export const fetch = createAsyncThunk(
|
|
||||||
'account/fetch',
|
|
||||||
async ({ id }: { id: string }) => {
|
|
||||||
const res = await client({
|
|
||||||
method: 'get',
|
|
||||||
instance: 'local',
|
|
||||||
endpoint: `accounts/${id}`
|
|
||||||
})
|
|
||||||
return res.body
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const accountInitState = {
|
|
||||||
account: {},
|
|
||||||
status: 'idle'
|
|
||||||
}
|
|
||||||
|
|
||||||
export const accountSlice = createSlice({
|
|
||||||
name: 'account',
|
|
||||||
initialState: accountInitState,
|
|
||||||
reducers: {
|
|
||||||
reset: () => accountInitState
|
|
||||||
},
|
|
||||||
extraReducers: builder => {
|
|
||||||
builder.addCase(fetch.pending, state => {
|
|
||||||
state.status = 'loading'
|
|
||||||
})
|
|
||||||
|
|
||||||
builder.addCase(fetch.fulfilled, (state, action) => {
|
|
||||||
state.status = 'succeeded'
|
|
||||||
state.account = action.payload
|
|
||||||
})
|
|
||||||
|
|
||||||
builder.addCase(fetch.rejected, (state, action) => {
|
|
||||||
state.status = 'failed'
|
|
||||||
console.error(action.error.message)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
export const { reset } = accountSlice.actions
|
|
||||||
export default accountSlice.reducer
|
|
|
@ -1,58 +0,0 @@
|
||||||
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 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
|
|
|
@ -1,9 +1,6 @@
|
||||||
import { configureStore } from '@reduxjs/toolkit'
|
import { configureStore } from '@reduxjs/toolkit'
|
||||||
|
|
||||||
import instanceInfoSlice from 'src/stacks/common/instanceInfoSlice'
|
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
|
// get site information from local storage and pass to reducers
|
||||||
const preloadedState = {
|
const preloadedState = {
|
||||||
|
@ -15,10 +12,7 @@ const preloadedState = {
|
||||||
}
|
}
|
||||||
|
|
||||||
const reducer = {
|
const reducer = {
|
||||||
instanceInfo: instanceInfoSlice,
|
instanceInfo: instanceInfoSlice
|
||||||
timelines: timelineSlice,
|
|
||||||
account: accountSlice,
|
|
||||||
// relationships: relationshipsSlice
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const store = configureStore({
|
const store = configureStore({
|
||||||
|
|
|
@ -0,0 +1,167 @@
|
||||||
|
import client from 'src/api/client'
|
||||||
|
|
||||||
|
export const timelineFetch = async (
|
||||||
|
key: string,
|
||||||
|
{
|
||||||
|
page,
|
||||||
|
query = {},
|
||||||
|
account,
|
||||||
|
hashtag,
|
||||||
|
list,
|
||||||
|
toot
|
||||||
|
}: {
|
||||||
|
page: string
|
||||||
|
query?: {
|
||||||
|
[key: string]: string | number | boolean
|
||||||
|
}
|
||||||
|
account?: string
|
||||||
|
hashtag?: string
|
||||||
|
list?: string
|
||||||
|
toot?: string
|
||||||
|
},
|
||||||
|
pagination: {
|
||||||
|
direction: 'prev' | 'next'
|
||||||
|
id: string
|
||||||
|
}
|
||||||
|
) => {
|
||||||
|
let res
|
||||||
|
|
||||||
|
if (pagination && pagination.id) {
|
||||||
|
switch (pagination.direction) {
|
||||||
|
case 'prev':
|
||||||
|
query.min_id = pagination.id
|
||||||
|
break
|
||||||
|
case 'next':
|
||||||
|
query.max_id = pagination.id
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (page) {
|
||||||
|
case 'Following':
|
||||||
|
res = await client({
|
||||||
|
method: 'get',
|
||||||
|
instance: 'local',
|
||||||
|
endpoint: 'timelines/home',
|
||||||
|
query
|
||||||
|
})
|
||||||
|
return Promise.resolve({ toots: res.body })
|
||||||
|
|
||||||
|
case 'Local':
|
||||||
|
query.local = 'true'
|
||||||
|
res = await client({
|
||||||
|
method: 'get',
|
||||||
|
instance: 'local',
|
||||||
|
endpoint: 'timelines/public',
|
||||||
|
query
|
||||||
|
})
|
||||||
|
return Promise.resolve({ toots: res.body })
|
||||||
|
|
||||||
|
case 'LocalPublic':
|
||||||
|
res = await client({
|
||||||
|
method: 'get',
|
||||||
|
instance: 'local',
|
||||||
|
endpoint: 'timelines/public',
|
||||||
|
query
|
||||||
|
})
|
||||||
|
return Promise.resolve({ toots: res.body })
|
||||||
|
|
||||||
|
case 'RemotePublic':
|
||||||
|
res = await client({
|
||||||
|
method: 'get',
|
||||||
|
instance: 'remote',
|
||||||
|
endpoint: 'timelines/public',
|
||||||
|
query
|
||||||
|
})
|
||||||
|
return Promise.resolve({ toots: res.body })
|
||||||
|
|
||||||
|
case 'Notifications':
|
||||||
|
res = await client({
|
||||||
|
method: 'get',
|
||||||
|
instance: 'local',
|
||||||
|
endpoint: 'notifications',
|
||||||
|
query
|
||||||
|
})
|
||||||
|
return Promise.resolve({ toots: res.body })
|
||||||
|
|
||||||
|
case 'Account_Default':
|
||||||
|
res = await client({
|
||||||
|
method: 'get',
|
||||||
|
instance: 'local',
|
||||||
|
endpoint: `accounts/${account}/statuses`,
|
||||||
|
query: {
|
||||||
|
pinned: 'true'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const toots = res.body
|
||||||
|
res = await client({
|
||||||
|
method: 'get',
|
||||||
|
instance: 'local',
|
||||||
|
endpoint: `accounts/${account}/statuses`,
|
||||||
|
query: {
|
||||||
|
exclude_replies: 'true'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
toots.push(...res.body)
|
||||||
|
return Promise.resolve({ toots: toots })
|
||||||
|
|
||||||
|
case 'Account_All':
|
||||||
|
res = await client({
|
||||||
|
method: 'get',
|
||||||
|
instance: 'local',
|
||||||
|
endpoint: `accounts/${account}/statuses`,
|
||||||
|
query
|
||||||
|
})
|
||||||
|
return Promise.resolve({ toots: res.body })
|
||||||
|
|
||||||
|
case 'Account_Media':
|
||||||
|
res = await client({
|
||||||
|
method: 'get',
|
||||||
|
instance: 'local',
|
||||||
|
endpoint: `accounts/${account}/statuses`,
|
||||||
|
query: {
|
||||||
|
only_media: 'true'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return Promise.resolve({ toots: res.body })
|
||||||
|
|
||||||
|
case 'Hashtag':
|
||||||
|
res = await client({
|
||||||
|
method: 'get',
|
||||||
|
instance: 'local',
|
||||||
|
endpoint: `timelines/tag/${hashtag}`,
|
||||||
|
query
|
||||||
|
})
|
||||||
|
return Promise.resolve({ toots: res.body })
|
||||||
|
|
||||||
|
// case 'List':
|
||||||
|
// res = await client({
|
||||||
|
// method: 'get',
|
||||||
|
// instance: 'local',
|
||||||
|
// endpoint: `timelines/list/${list}`,
|
||||||
|
// query
|
||||||
|
// })
|
||||||
|
// return {
|
||||||
|
// toots: res.body
|
||||||
|
// }
|
||||||
|
|
||||||
|
case 'Toot':
|
||||||
|
const current = await client({
|
||||||
|
method: 'get',
|
||||||
|
instance: 'local',
|
||||||
|
endpoint: `statuses/${toot}`
|
||||||
|
})
|
||||||
|
const context = await client({
|
||||||
|
method: 'get',
|
||||||
|
instance: 'local',
|
||||||
|
endpoint: `statuses/${toot}/context`
|
||||||
|
})
|
||||||
|
return Promise.resolve({
|
||||||
|
toots: [...context.ancestors, current, ...context.descendants],
|
||||||
|
pointer: context.ancestors.length
|
||||||
|
})
|
||||||
|
|
||||||
|
default:
|
||||||
|
console.error('First time fetching timeline error')
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,270 +0,0 @@
|
||||||
import {
|
|
||||||
AnyAction,
|
|
||||||
createAsyncThunk,
|
|
||||||
createSlice,
|
|
||||||
PayloadAction
|
|
||||||
} from '@reduxjs/toolkit'
|
|
||||||
import store from 'src'
|
|
||||||
|
|
||||||
import client from 'src/api/client'
|
|
||||||
|
|
||||||
export const fetch = createAsyncThunk(
|
|
||||||
'timeline/fetch',
|
|
||||||
async (
|
|
||||||
{
|
|
||||||
page,
|
|
||||||
paginationDirection,
|
|
||||||
query = {},
|
|
||||||
account,
|
|
||||||
hashtag,
|
|
||||||
list,
|
|
||||||
toot
|
|
||||||
}: {
|
|
||||||
page: string
|
|
||||||
paginationDirection?: 'prev' | 'next'
|
|
||||||
query?: {
|
|
||||||
[key: string]: string | number | boolean
|
|
||||||
}
|
|
||||||
account?: string
|
|
||||||
hashtag?: string
|
|
||||||
list?: string
|
|
||||||
toot?: string
|
|
||||||
},
|
|
||||||
{ getState }
|
|
||||||
) => {
|
|
||||||
let res
|
|
||||||
|
|
||||||
if (paginationDirection) {
|
|
||||||
//@ts-ignore
|
|
||||||
const allToots = getState().timelines[page].toots
|
|
||||||
switch (paginationDirection) {
|
|
||||||
case 'prev':
|
|
||||||
query.min_id = allToots[0].id
|
|
||||||
break
|
|
||||||
case 'next':
|
|
||||||
query.max_id = allToots[allToots.length - 1].id
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (page) {
|
|
||||||
case 'Following':
|
|
||||||
res = await client({
|
|
||||||
method: 'get',
|
|
||||||
instance: 'local',
|
|
||||||
endpoint: 'timelines/home',
|
|
||||||
query
|
|
||||||
})
|
|
||||||
return {
|
|
||||||
toots: res.body
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'Local':
|
|
||||||
query.local = 'true'
|
|
||||||
res = await client({
|
|
||||||
method: 'get',
|
|
||||||
instance: 'local',
|
|
||||||
endpoint: 'timelines/public',
|
|
||||||
query
|
|
||||||
})
|
|
||||||
return {
|
|
||||||
toots: res.body
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'LocalPublic':
|
|
||||||
res = await client({
|
|
||||||
method: 'get',
|
|
||||||
instance: 'local',
|
|
||||||
endpoint: 'timelines/public',
|
|
||||||
query
|
|
||||||
})
|
|
||||||
return {
|
|
||||||
toots: res.body
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'RemotePublic':
|
|
||||||
res = await client({
|
|
||||||
method: 'get',
|
|
||||||
instance: 'remote',
|
|
||||||
endpoint: 'timelines/public',
|
|
||||||
query
|
|
||||||
})
|
|
||||||
return {
|
|
||||||
toots: res.body
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'Notifications':
|
|
||||||
res = await client({
|
|
||||||
method: 'get',
|
|
||||||
instance: 'local',
|
|
||||||
endpoint: 'notifications',
|
|
||||||
query
|
|
||||||
})
|
|
||||||
return {
|
|
||||||
toots: res.body
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'Account_Default':
|
|
||||||
res = await client({
|
|
||||||
method: 'get',
|
|
||||||
instance: 'local',
|
|
||||||
endpoint: `accounts/${account}/statuses`,
|
|
||||||
query: {
|
|
||||||
pinned: 'true'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
const toots = res.body
|
|
||||||
res = await client({
|
|
||||||
method: 'get',
|
|
||||||
instance: 'local',
|
|
||||||
endpoint: `accounts/${account}/statuses`,
|
|
||||||
query: {
|
|
||||||
exclude_replies: 'true'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
toots.push(...res.body)
|
|
||||||
return { toots: toots }
|
|
||||||
|
|
||||||
case 'Account_All':
|
|
||||||
res = await client({
|
|
||||||
method: 'get',
|
|
||||||
instance: 'local',
|
|
||||||
endpoint: `accounts/${account}/statuses`,
|
|
||||||
query
|
|
||||||
})
|
|
||||||
return {
|
|
||||||
toots: res.body
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'Account_Media':
|
|
||||||
res = await client({
|
|
||||||
method: 'get',
|
|
||||||
instance: 'local',
|
|
||||||
endpoint: `accounts/${account}/statuses`,
|
|
||||||
query: {
|
|
||||||
only_media: 'true'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return {
|
|
||||||
toots: res.body
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'Hashtag':
|
|
||||||
res = await client({
|
|
||||||
method: 'get',
|
|
||||||
instance: 'local',
|
|
||||||
endpoint: `timelines/tag/${hashtag}`,
|
|
||||||
query
|
|
||||||
})
|
|
||||||
return {
|
|
||||||
toots: res.body
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'List':
|
|
||||||
res = await client({
|
|
||||||
method: 'get',
|
|
||||||
instance: 'local',
|
|
||||||
endpoint: `timelines/list/${list}`,
|
|
||||||
query
|
|
||||||
})
|
|
||||||
return {
|
|
||||||
toots: res.body
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'Toot':
|
|
||||||
const current = await client({
|
|
||||||
method: 'get',
|
|
||||||
instance: 'local',
|
|
||||||
endpoint: `statuses/${toot}`
|
|
||||||
})
|
|
||||||
const context = await client({
|
|
||||||
method: 'get',
|
|
||||||
instance: 'local',
|
|
||||||
endpoint: `statuses/${toot}/context`
|
|
||||||
})
|
|
||||||
return {
|
|
||||||
toots: [...context.ancestors, current, ...context.descendants],
|
|
||||||
pointer: context.ancestors.length
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
console.error('First time fetching timeline error')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const timelineInitState: store.TimelineState = {
|
|
||||||
toots: [],
|
|
||||||
pointer: undefined,
|
|
||||||
status: 'idle'
|
|
||||||
}
|
|
||||||
|
|
||||||
export const timelineSlice = createSlice({
|
|
||||||
name: 'timeline',
|
|
||||||
initialState: {
|
|
||||||
Following: timelineInitState,
|
|
||||||
Local: timelineInitState,
|
|
||||||
LocalPublic: timelineInitState,
|
|
||||||
RemotePublic: timelineInitState,
|
|
||||||
Notifications: timelineInitState,
|
|
||||||
Hashtag: timelineInitState,
|
|
||||||
List: timelineInitState,
|
|
||||||
Toot: timelineInitState,
|
|
||||||
Account_Default: timelineInitState,
|
|
||||||
Account_All: timelineInitState,
|
|
||||||
Account_Media: timelineInitState
|
|
||||||
},
|
|
||||||
reducers: {
|
|
||||||
reset (state, action: PayloadAction<store.TimelinePage>) {
|
|
||||||
//@ts-ignore
|
|
||||||
state[action.payload] = timelineInitState
|
|
||||||
},
|
|
||||||
updateStatus (state, action) {
|
|
||||||
Object.keys(state).map((page: store.TimelinePage) => {
|
|
||||||
//@ts-ignore
|
|
||||||
const index: number = state[page].toots.findIndex(
|
|
||||||
(toot: mastodon.Status) => toot.id === action.payload.id
|
|
||||||
)
|
|
||||||
if (index !== -1) {
|
|
||||||
//@ts-ignore
|
|
||||||
state[page].toots[index] = action.payload
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
extraReducers: builder => {
|
|
||||||
builder.addCase(fetch.pending, (state, action: AnyAction) => {
|
|
||||||
//@ts-ignore
|
|
||||||
state[action.meta.arg.page].status = 'loading'
|
|
||||||
})
|
|
||||||
|
|
||||||
builder.addCase(fetch.fulfilled, (state, action) => {
|
|
||||||
//@ts-ignore
|
|
||||||
state[action.meta.arg.page].status = 'succeeded'
|
|
||||||
|
|
||||||
if (action.payload?.toots) {
|
|
||||||
if (action.meta.arg.paginationDirection === 'prev') {
|
|
||||||
//@ts-ignore
|
|
||||||
state[action.meta.arg.page].toots.unshift(...action.payload.toots)
|
|
||||||
} else {
|
|
||||||
//@ts-ignore
|
|
||||||
state[action.meta.arg.page].toots.push(...action.payload.toots)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (action.payload?.pointer) {
|
|
||||||
//@ts-ignore
|
|
||||||
state[action.meta.arg.page].pointer = action.payload.pointer
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
builder.addCase(fetch.rejected, (state, action) => {
|
|
||||||
//@ts-ignore
|
|
||||||
state[action.meta.arg.page].status = 'failed'
|
|
||||||
console.error(action.error.message)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
export const { reset, updateStatus } = timelineSlice.actions
|
|
||||||
export default timelineSlice.reducer
|
|
Loading…
Reference in New Issue