mirror of
https://github.com/tooot-app/app
synced 2025-06-05 22:19:13 +02:00
Setup of single store works for multiple timeline
This commit is contained in:
@ -5,31 +5,29 @@ import { enableScreens } from 'react-native-screens'
|
||||
enableScreens()
|
||||
|
||||
import React from 'react'
|
||||
import store from './app/store'
|
||||
import store from 'src/stacks/common/store'
|
||||
import { Provider } from 'react-redux'
|
||||
import { StatusBar } from 'expo-status-bar'
|
||||
|
||||
import MainTimeline from 'src/stacks/MainTimeline'
|
||||
import PublicTimeline from 'src/stacks/PublicTimeline'
|
||||
import Main from 'src/stacks/Main'
|
||||
import Public from 'src/stacks/Public'
|
||||
import Notifications from 'src/stacks/Notifications'
|
||||
import Me from 'src/stacks/Me'
|
||||
|
||||
const Tab = createBottomTabNavigator()
|
||||
|
||||
export default function Main () {
|
||||
export default function Index () {
|
||||
return (
|
||||
<>
|
||||
<Provider store={store}>
|
||||
<StatusBar style='auto' />
|
||||
<NavigationContainer>
|
||||
<Tab.Navigator>
|
||||
<Tab.Screen name='MainTimeline' component={MainTimeline} />
|
||||
<Tab.Screen name='PublicTimeline' component={PublicTimeline} />
|
||||
<Tab.Screen name='Main' component={Main} />
|
||||
<Tab.Screen name='Public' component={Public} />
|
||||
{/* <Tab.Screen name='Notifications' component={Notifications} /> */}
|
||||
<Tab.Screen name='Me' component={Me} />
|
||||
</Tab.Navigator>
|
||||
</NavigationContainer>
|
||||
</Provider>
|
||||
</>
|
||||
)
|
||||
}
|
@ -1,12 +1,6 @@
|
||||
export async function client (
|
||||
instance,
|
||||
endpoint,
|
||||
query,
|
||||
{ body, ...customConfig } = {}
|
||||
) {
|
||||
if (!instance || !endpoint) {
|
||||
console.error('Missing instance or endpoint.')
|
||||
return Promise.reject('Missing instance or endpoint.')
|
||||
export async function client (url, query, { body, ...customConfig } = {}) {
|
||||
if (!url) {
|
||||
return Promise.reject('Missing URL.')
|
||||
}
|
||||
const headers = { 'Content-Type': 'application/json' }
|
||||
|
||||
@ -25,8 +19,8 @@ export async function client (
|
||||
let data
|
||||
try {
|
||||
const response = await fetch(
|
||||
`https://${instance}/api/v1/${endpoint}${
|
||||
query
|
||||
`https://${url}${
|
||||
Object.keys(query).length
|
||||
? `?${Object.keys(query)
|
||||
.map(key => `${key}=${query[key]}`)
|
||||
.join('&')}`
|
||||
|
@ -1,16 +0,0 @@
|
||||
import { configureStore } from '@reduxjs/toolkit'
|
||||
|
||||
import genericTimelineSlice from 'src/stacks/common/timelineSlice'
|
||||
|
||||
export default configureStore({
|
||||
reducer: {
|
||||
'social.xmflsct.com': genericTimelineSlice('social.xmflsct.com').slice
|
||||
.reducer,
|
||||
'm.cmx.im': genericTimelineSlice('m.cmx.im').slice.reducer
|
||||
},
|
||||
middleware: getDefaultMiddleware =>
|
||||
getDefaultMiddleware({
|
||||
immutableCheck: false,
|
||||
serializableCheck: false
|
||||
})
|
||||
})
|
14
src/stacks/Main.jsx
Normal file
14
src/stacks/Main.jsx
Normal file
@ -0,0 +1,14 @@
|
||||
import React from 'react'
|
||||
import { createNativeStackNavigator } from 'react-native-screens/native-stack'
|
||||
|
||||
import Following from 'src/stacks/Main/Following'
|
||||
|
||||
const MainStack = createNativeStackNavigator()
|
||||
|
||||
export default function Main () {
|
||||
return (
|
||||
<MainStack.Navigator>
|
||||
<MainStack.Screen name='Following' component={Following} />
|
||||
</MainStack.Navigator>
|
||||
)
|
||||
}
|
7
src/stacks/Main/Following.jsx
Normal file
7
src/stacks/Main/Following.jsx
Normal file
@ -0,0 +1,7 @@
|
||||
import React from 'react'
|
||||
|
||||
import Timeline from 'src/stacks/common/Timeline'
|
||||
|
||||
export default function Following () {
|
||||
return <Timeline endpoint='home' />
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
import React from 'react'
|
||||
import { createNativeStackNavigator } from 'react-native-screens/native-stack'
|
||||
|
||||
import Timeline from 'src/stacks/common/Timeline'
|
||||
|
||||
const MainTimelineStack = createNativeStackNavigator()
|
||||
|
||||
function Base () {
|
||||
return <Timeline instance='social.xmflsct.com' endpoint='home' />
|
||||
}
|
||||
|
||||
export default function MainTimeline () {
|
||||
return (
|
||||
<MainTimelineStack.Navigator>
|
||||
<MainTimelineStack.Screen name='Base' component={Base} />
|
||||
</MainTimelineStack.Navigator>
|
||||
)
|
||||
}
|
14
src/stacks/Public.jsx
Normal file
14
src/stacks/Public.jsx
Normal file
@ -0,0 +1,14 @@
|
||||
import React from 'react'
|
||||
import { createNativeStackNavigator } from 'react-native-screens/native-stack'
|
||||
|
||||
import CurrentPublic from 'src/stacks/Public/CurrentPublic'
|
||||
|
||||
const PublicStack = createNativeStackNavigator()
|
||||
|
||||
export default function PublicTimeline () {
|
||||
return (
|
||||
<PublicStack.Navigator>
|
||||
<PublicStack.Screen name='CurrentPublic' component={CurrentPublic} />
|
||||
</PublicStack.Navigator>
|
||||
)
|
||||
}
|
7
src/stacks/Public/CurrentPublic.jsx
Normal file
7
src/stacks/Public/CurrentPublic.jsx
Normal file
@ -0,0 +1,7 @@
|
||||
import React from 'react'
|
||||
|
||||
import Timeline from 'src/stacks/common/Timeline'
|
||||
|
||||
export default function CurrentPublic () {
|
||||
return <Timeline endpoint='public' local />
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
import React from 'react'
|
||||
import { createNativeStackNavigator } from 'react-native-screens/native-stack'
|
||||
|
||||
import Timeline from 'src/stacks/common/Timeline'
|
||||
|
||||
const PublicTimelineStack = createNativeStackNavigator()
|
||||
|
||||
function Base () {
|
||||
return <Timeline instance='social.xmflsct.com' endpoint='public' local />
|
||||
}
|
||||
|
||||
export default function PublicTimeline () {
|
||||
return (
|
||||
<PublicTimelineStack.Navigator>
|
||||
<PublicTimelineStack.Screen name='Base' component={Base} />
|
||||
</PublicTimelineStack.Navigator>
|
||||
)
|
||||
}
|
||||
|
||||
// store by page maybe
|
@ -1,23 +1,25 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import React, { useEffect } from 'react'
|
||||
import { ActivityIndicator, FlatList, View } from 'react-native'
|
||||
import { useSelector, useDispatch } from 'react-redux'
|
||||
import { connect, useSelector, useDispatch } from 'react-redux'
|
||||
|
||||
import TootTimeline from 'src/components/TootTimeline'
|
||||
import { fetch, getToots, getStatus } from './timelineSlice'
|
||||
|
||||
import genericTimelineSlice from './timelineSlice'
|
||||
|
||||
export default function Timeline ({ instance, endpoint, local }) {
|
||||
export default function Timeline ({ remote, endpoint, local }) {
|
||||
const dispatch = useDispatch()
|
||||
const toots = useSelector(genericTimelineSlice(instance).getToots)
|
||||
const status = useSelector(genericTimelineSlice(instance).getStatus)
|
||||
const toots = useSelector(state =>
|
||||
getToots(state)({ remote, endpoint, local })
|
||||
)
|
||||
const status = useSelector(state =>
|
||||
getStatus(state)({ remote, endpoint, local })
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (status === 'idle') {
|
||||
dispatch(genericTimelineSlice(instance).fetch({ endpoint, local }))
|
||||
dispatch(fetch({ remote, endpoint, local }))
|
||||
}
|
||||
}, [status, dispatch])
|
||||
|
||||
let content
|
||||
|
||||
if (status === 'error') {
|
||||
@ -31,22 +33,13 @@ export default function Timeline ({ instance, endpoint, local }) {
|
||||
renderItem={TootTimeline}
|
||||
onRefresh={() =>
|
||||
dispatch(
|
||||
genericTimelineSlice(instance).fetch({
|
||||
endpoint,
|
||||
local,
|
||||
id: toots[0].id,
|
||||
newer: true
|
||||
})
|
||||
fetch({ remote, endpoint, local, id: toots[0].id, newer: true })
|
||||
)
|
||||
}
|
||||
refreshing={status === 'loading'}
|
||||
onEndReached={() =>
|
||||
dispatch(
|
||||
genericTimelineSlice(instance).fetch({
|
||||
endpoint,
|
||||
local,
|
||||
id: toots[toots.length - 1].id
|
||||
})
|
||||
fetch({ remote, endpoint, local, id: toots[toots.length - 1].id })
|
||||
)
|
||||
}
|
||||
onEndReachedThreshold={0.5}
|
||||
@ -61,6 +54,7 @@ export default function Timeline ({ instance, endpoint, local }) {
|
||||
}
|
||||
|
||||
Timeline.propTypes = {
|
||||
instance: PropTypes.string.isRequired,
|
||||
public: PropTypes.bool
|
||||
remote: PropTypes.bool,
|
||||
endpoint: PropTypes.string.isRequired,
|
||||
local: PropTypes.bool
|
||||
}
|
||||
|
24
src/stacks/common/instanceInfoSlice.js
Normal file
24
src/stacks/common/instanceInfoSlice.js
Normal file
@ -0,0 +1,24 @@
|
||||
import { createSlice } from '@reduxjs/toolkit'
|
||||
|
||||
const instanceInfoSlice = createSlice({
|
||||
name: 'instanceInfo',
|
||||
initialState: {},
|
||||
reducers: {
|
||||
// increment (state) {
|
||||
// state.value++
|
||||
// },
|
||||
// decrement (state) {
|
||||
// state.value--
|
||||
// },
|
||||
// incrementByAmount (state, action) {
|
||||
// state.value += action.payload
|
||||
// }
|
||||
}
|
||||
})
|
||||
|
||||
export const getCurrent = state => state.current
|
||||
export const getCurrentToken = state => state.currentToken
|
||||
export const getRemote = state => state.remote
|
||||
|
||||
// export const { increment, decrement, incrementByAmount } = counterSlice.actions
|
||||
export default instanceInfoSlice.reducer
|
30
src/stacks/common/store.js
Normal file
30
src/stacks/common/store.js
Normal file
@ -0,0 +1,30 @@
|
||||
import { configureStore } from '@reduxjs/toolkit'
|
||||
|
||||
import instanceInfoSlice from 'src/stacks/common/instanceInfoSlice'
|
||||
import timelineSlice from 'src/stacks/common/timelineSlice'
|
||||
|
||||
// get site information from local storage and pass to reducers
|
||||
const preloadedState = {
|
||||
instanceInfo: {
|
||||
current: 'social.xmflsct.com',
|
||||
currentToken: 'qjzJ0IjvZ1apsn0_wBkGcdjKgX7Dao9KEPhGwggPwAo',
|
||||
remote: 'm.cmx.im'
|
||||
}
|
||||
}
|
||||
|
||||
const reducer = {
|
||||
instanceInfo: instanceInfoSlice,
|
||||
timelines: timelineSlice
|
||||
}
|
||||
|
||||
// const middleware = getDefaultMiddleware =>
|
||||
// getDefaultMiddleware({
|
||||
// immutableCheck: false,
|
||||
// serializableCheck: false
|
||||
// })
|
||||
|
||||
export default configureStore({
|
||||
preloadedState,
|
||||
reducer,
|
||||
// middleware
|
||||
})
|
@ -1,77 +1,82 @@
|
||||
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'
|
||||
|
||||
import { client } from 'src/api/client'
|
||||
import * as localStorage from 'src/utils/localStorage'
|
||||
|
||||
export default function genericTimelineSlice (instance) {
|
||||
const fetch = createAsyncThunk(
|
||||
'timeline/fetch',
|
||||
async ({ endpoint, local, id, newer }) => {
|
||||
if (!instance || !endpoint) console.error('Missing instance or endpoint.')
|
||||
// Naming convention
|
||||
// Following: home
|
||||
// Local: home/local
|
||||
// CurrentPublic: public/local
|
||||
// RemotePublic: remote
|
||||
|
||||
let query = {}
|
||||
if (local) query.local = 'true'
|
||||
if (newer) {
|
||||
query.since_id = id
|
||||
} else {
|
||||
if (id) {
|
||||
query.max_id = id
|
||||
const checkInstance = ({ remote, endpoint, local }) =>
|
||||
remote ? 'remote' : `${endpoint}${local ? '/local' : ''}`
|
||||
|
||||
export const fetch = createAsyncThunk(
|
||||
'timeline/fetch',
|
||||
async ({ remote, endpoint, local, id, newer }, { getState }) => {
|
||||
if (!endpoint) console.error('Missing endpoint')
|
||||
|
||||
const instance = remote
|
||||
? `${getState().instanceInfo.remote}/api/v1/timelines/public`
|
||||
: `${getState().instanceInfo.current}/api/v1/timelines/${endpoint}`
|
||||
|
||||
const query = {
|
||||
...(local && { local: 'true' }),
|
||||
...(newer ? { since_id: id } : id && { max_id: id })
|
||||
}
|
||||
|
||||
const header = {
|
||||
...(getState().instanceInfo.currentToken && {
|
||||
headers: {
|
||||
Authorization: `Bearer ${getState().instanceInfo.currentToken}`
|
||||
}
|
||||
}
|
||||
|
||||
let header
|
||||
const instanceToken = await localStorage.getItem()
|
||||
if (instanceToken) {
|
||||
header = { headers: { Authorization: `Bearer ${instanceToken}` } }
|
||||
}
|
||||
|
||||
return {
|
||||
data: await client.get(
|
||||
instance,
|
||||
`timelines/${endpoint}`,
|
||||
query,
|
||||
header
|
||||
),
|
||||
newer: newer
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
const slice = createSlice({
|
||||
name: instance,
|
||||
initialState: {
|
||||
toots: [],
|
||||
status: 'idle',
|
||||
error: null
|
||||
},
|
||||
extraReducers: {
|
||||
[fetch.pending]: state => {
|
||||
state.status = 'loading'
|
||||
},
|
||||
[fetch.fulfilled]: (state, action) => {
|
||||
state.status = 'succeeded'
|
||||
action.payload.newer
|
||||
? state.toots.unshift(...action.payload.data)
|
||||
: state.toots.push(...action.payload.data)
|
||||
},
|
||||
[fetch.rejected]: (state, action) => {
|
||||
state.status = 'failed'
|
||||
state.error = action.payload
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
getToots = state => state[instance].toots
|
||||
getStatus = state => state[instance].status
|
||||
|
||||
return {
|
||||
fetch,
|
||||
slice,
|
||||
getToots,
|
||||
getStatus
|
||||
return await client.get(instance, query, header)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// export const timelineSlice = genericTimelineSlice(data)
|
||||
export const timelineSlice = createSlice({
|
||||
name: 'timeline',
|
||||
initialState: {
|
||||
home: {
|
||||
toots: [],
|
||||
status: 'idle'
|
||||
},
|
||||
'home/local': {
|
||||
toots: [],
|
||||
status: 'idle'
|
||||
},
|
||||
'public/local': {
|
||||
toots: [],
|
||||
status: 'idle'
|
||||
},
|
||||
remote: {
|
||||
toots: [],
|
||||
status: 'idle'
|
||||
}
|
||||
},
|
||||
extraReducers: {
|
||||
[fetch.pending]: (state, action) => {
|
||||
state[checkInstance(action.meta.arg)].status = 'loading'
|
||||
},
|
||||
[fetch.fulfilled]: (state, action) => {
|
||||
state[checkInstance(action.meta.arg)].status = 'succeeded'
|
||||
action.meta.arg.newer
|
||||
? state[checkInstance(action.meta.arg)].toots.unshift(...action.payload)
|
||||
: state[checkInstance(action.meta.arg)].toots.push(...action.payload)
|
||||
},
|
||||
[fetch.rejected]: (state, action) => {
|
||||
console.error(action.error.message)
|
||||
state[checkInstance(action.meta.arg)].status = 'failed'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// export default timelineSlice.reducer
|
||||
export const getToots = state => instance =>
|
||||
state.timelines[checkInstance(instance)].toots
|
||||
export const getStatus = state => instance =>
|
||||
state.timelines[checkInstance(instance)].status
|
||||
|
||||
export default timelineSlice.reducer
|
||||
|
Reference in New Issue
Block a user