mirror of https://github.com/tooot-app/app
First commit
Public timeline working, with refreshing and load more
This commit is contained in:
parent
fb152fece9
commit
4af19d0588
21
App.js
21
App.js
|
@ -1,21 +0,0 @@
|
|||
import { StatusBar } from 'expo-status-bar';
|
||||
import React from 'react';
|
||||
import { StyleSheet, Text, View } from 'react-native';
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text>Open up App.js to start working on your app!</Text>
|
||||
<StatusBar style="auto" />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: '#fff',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
});
|
|
@ -0,0 +1,25 @@
|
|||
import 'react-native-gesture-handler'
|
||||
import { NavigationContainer } from '@react-navigation/native'
|
||||
import { createStackNavigator } from '@react-navigation/stack'
|
||||
|
||||
import React from 'react'
|
||||
import store from './app/store'
|
||||
import { Provider } from 'react-redux'
|
||||
import { StatusBar } from 'expo-status-bar'
|
||||
|
||||
import ScreenTimeline from './screens/Timeline'
|
||||
|
||||
const Stack = createStackNavigator()
|
||||
|
||||
export default function App () {
|
||||
return (
|
||||
<NavigationContainer>
|
||||
<StatusBar style='auto' />
|
||||
<Provider store={store}>
|
||||
<Stack.Navigator>
|
||||
<Stack.Screen name='Timeline' component={ScreenTimeline} />
|
||||
</Stack.Navigator>
|
||||
</Provider>
|
||||
</NavigationContainer>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
export async function client(endpoint, { body, ...customConfig } = {}) {
|
||||
let data
|
||||
try {
|
||||
const response = await window.fetch(endpoint, config)
|
||||
data = await response.json()
|
||||
if (response.ok) {
|
||||
return data
|
||||
}
|
||||
throw new Error(response.statusText)
|
||||
} catch (err) {
|
||||
return Promise.reject(err.message ? err.message : data)
|
||||
}
|
||||
}
|
||||
|
||||
client.get = function (endpoint, customConfig = {}) {
|
||||
return client(endpoint, { ...customConfig, method: 'GET' })
|
||||
}
|
||||
|
||||
client.post = function (endpoint, body, customConfig = {}) {
|
||||
return client(endpoint, { ...customConfig, body })
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
import { configureStore } from '@reduxjs/toolkit'
|
||||
|
||||
import timelineReducer from '../screens/timelineSlice'
|
||||
|
||||
export default configureStore({
|
||||
reducer: {
|
||||
timeline: timelineReducer
|
||||
},
|
||||
middleware: getDefaultMiddleware =>
|
||||
getDefaultMiddleware({
|
||||
immutableCheck: false,
|
||||
serializableCheck: false
|
||||
})
|
||||
})
|
|
@ -0,0 +1,47 @@
|
|||
import React from 'react'
|
||||
import { Image, StyleSheet, Text, View } from 'react-native'
|
||||
import HTML from 'react-native-render-html'
|
||||
|
||||
import relative_time from '../utils/relative-time'
|
||||
|
||||
export default function TootTimeline ({ item }) {
|
||||
return (
|
||||
<View style={styles.tootTimeline}>
|
||||
<View style={styles.header}>
|
||||
<Image source={{ uri: item.account.avatar }} style={styles.avatar} />
|
||||
<View>
|
||||
<View style={styles.name}>
|
||||
<Text>{item.account.display_name}</Text>
|
||||
<Text>{item.account.acct}</Text>
|
||||
</View>
|
||||
<View>
|
||||
<Text>{relative_time(item.created_at)}</Text>
|
||||
{item.application && item.application.name !== 'Web' && (
|
||||
<Text onPress={() => Linking.openURL(item.application.website)}>
|
||||
{item.application.name}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
{item.content ? <HTML html={item.content} /> : <></>}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
tootTimeline: {
|
||||
flex: 1,
|
||||
padding: 15
|
||||
},
|
||||
header: {
|
||||
flexDirection: 'row'
|
||||
},
|
||||
avatar: {
|
||||
width: 40,
|
||||
height: 40
|
||||
},
|
||||
name: {
|
||||
flexDirection: 'row'
|
||||
}
|
||||
})
|
|
@ -1105,6 +1105,14 @@
|
|||
"minimist": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"@egjs/hammerjs": {
|
||||
"version": "2.0.17",
|
||||
"resolved": "https://registry.npmjs.org/@egjs/hammerjs/-/hammerjs-2.0.17.tgz",
|
||||
"integrity": "sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A==",
|
||||
"requires": {
|
||||
"@types/hammerjs": "^2.0.36"
|
||||
}
|
||||
},
|
||||
"@expo/configure-splash-screen": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@expo/configure-splash-screen/-/configure-splash-screen-0.2.0.tgz",
|
||||
|
@ -1666,6 +1674,80 @@
|
|||
"resolved": "https://registry.npmjs.org/@react-native-community/cli-types/-/cli-types-4.10.1.tgz",
|
||||
"integrity": "sha512-ael2f1onoPF3vF7YqHGWy7NnafzGu+yp88BbFbP0ydoCP2xGSUzmZVw0zakPTC040Id+JQ9WeFczujMkDy6jYQ=="
|
||||
},
|
||||
"@react-native-community/masked-view": {
|
||||
"version": "0.1.10",
|
||||
"resolved": "https://registry.npmjs.org/@react-native-community/masked-view/-/masked-view-0.1.10.tgz",
|
||||
"integrity": "sha512-rk4sWFsmtOw8oyx8SD3KSvawwaK7gRBSEIy2TAwURyGt+3TizssXP1r8nx3zY+R7v2vYYHXZ+k2/GULAT/bcaQ=="
|
||||
},
|
||||
"@react-navigation/core": {
|
||||
"version": "5.12.5",
|
||||
"resolved": "https://registry.npmjs.org/@react-navigation/core/-/core-5.12.5.tgz",
|
||||
"integrity": "sha512-+QdQDtC75K1sBfACwCUNFlrCOYf36GYGr9YURHugm3LDEHUwAj7HPJA8FFLw1Rmp5N/P5q9Uct2B/XxJhYzKqA==",
|
||||
"requires": {
|
||||
"@react-navigation/routers": "^5.4.12",
|
||||
"escape-string-regexp": "^4.0.0",
|
||||
"nanoid": "^3.1.12",
|
||||
"query-string": "^6.13.5",
|
||||
"react-is": "^16.13.0",
|
||||
"use-subscription": "^1.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"escape-string-regexp": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
|
||||
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@react-navigation/native": {
|
||||
"version": "5.7.6",
|
||||
"resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-5.7.6.tgz",
|
||||
"integrity": "sha512-u+o9Ifs4//Ah6UczXiaAV+hiWPL0NyTbErj5WayeQUd6K5IIbA1UwumRVWs2Xp8q/4Q9h6FpPHUcKOyiTxhaqA==",
|
||||
"requires": {
|
||||
"@react-navigation/core": "^5.12.5",
|
||||
"nanoid": "^3.1.12"
|
||||
}
|
||||
},
|
||||
"@react-navigation/routers": {
|
||||
"version": "5.4.12",
|
||||
"resolved": "https://registry.npmjs.org/@react-navigation/routers/-/routers-5.4.12.tgz",
|
||||
"integrity": "sha512-IwMmxeb5e6LboljhakmhtrHBXLYFrFDr2c1GjAG538e4MjT4QGi/ZYckAxCh/NqKI0knnzqKppPl2NsOMv/NoQ==",
|
||||
"requires": {
|
||||
"nanoid": "^3.1.12"
|
||||
}
|
||||
},
|
||||
"@react-navigation/stack": {
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/@react-navigation/stack/-/stack-5.9.3.tgz",
|
||||
"integrity": "sha512-/CJ5Rsrc9bMI20dD8oC/QSZHARMFuUv8DeoiYE7R2N0M44cFupF1CrzaZBBC/S4Zi1ahZ0A+Hj/gAzAEQrNTvA==",
|
||||
"requires": {
|
||||
"color": "^3.1.2",
|
||||
"react-native-iphone-x-helper": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"@reduxjs/toolkit": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.4.0.tgz",
|
||||
"integrity": "sha512-hkxQwVx4BNVRsYdxjNF6cAseRmtrkpSlcgJRr3kLUcHPIAMZAmMJkXmHh/eUEGTMqPzsYpJLM7NN2w9fxQDuGw==",
|
||||
"requires": {
|
||||
"immer": "^7.0.3",
|
||||
"redux": "^4.0.0",
|
||||
"redux-thunk": "^2.3.0",
|
||||
"reselect": "^4.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"reselect": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/reselect/-/reselect-4.0.0.tgz",
|
||||
"integrity": "sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@types/hammerjs": {
|
||||
"version": "2.0.36",
|
||||
"resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.36.tgz",
|
||||
"integrity": "sha512-7TUK/k2/QGpEAv/BCwSHlYu3NXZhQ9ZwBYpzr9tjlPIL2C5BeGhH3DmVavRx3ZNyELX5TLC91JTz/cen6AAtIQ=="
|
||||
},
|
||||
"@types/istanbul-lib-coverage": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz",
|
||||
|
@ -2161,6 +2243,23 @@
|
|||
"node-int64": "^0.4.0"
|
||||
}
|
||||
},
|
||||
"buffer": {
|
||||
"version": "4.9.2",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz",
|
||||
"integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==",
|
||||
"requires": {
|
||||
"base64-js": "^1.0.2",
|
||||
"ieee754": "^1.1.4",
|
||||
"isarray": "^1.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"isarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
|
||||
}
|
||||
}
|
||||
},
|
||||
"buffer-alloc": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz",
|
||||
|
@ -2348,6 +2447,15 @@
|
|||
"object-visit": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"color": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/color/-/color-3.1.3.tgz",
|
||||
"integrity": "sha512-xgXAcTHa2HeFCGLE9Xs/R82hujGtu9Jd9x4NW3T34+OMs7VoPsjwzRczKHvTAHeJwWFwX5j15+MgAppE8ztObQ==",
|
||||
"requires": {
|
||||
"color-convert": "^1.9.1",
|
||||
"color-string": "^1.5.4"
|
||||
}
|
||||
},
|
||||
"color-convert": {
|
||||
"version": "1.9.3",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||
|
@ -2689,6 +2797,49 @@
|
|||
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
|
||||
"integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
|
||||
},
|
||||
"dom-serializer": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz",
|
||||
"integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==",
|
||||
"requires": {
|
||||
"domelementtype": "^2.0.1",
|
||||
"entities": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"domelementtype": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.2.tgz",
|
||||
"integrity": "sha512-wFwTwCVebUrMgGeAwRL/NhZtHAUyT9n9yg4IMDwf10+6iCMxSkVq9MGCVEH+QZWo1nNidy8kNvwmv4zWHDTqvA=="
|
||||
},
|
||||
"entities": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz",
|
||||
"integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"domelementtype": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz",
|
||||
"integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w=="
|
||||
},
|
||||
"domhandler": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz",
|
||||
"integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==",
|
||||
"requires": {
|
||||
"domelementtype": "1"
|
||||
}
|
||||
},
|
||||
"domutils": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz",
|
||||
"integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==",
|
||||
"requires": {
|
||||
"dom-serializer": "0",
|
||||
"domelementtype": "1"
|
||||
}
|
||||
},
|
||||
"ee-first": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||
|
@ -2725,6 +2876,11 @@
|
|||
"once": "^1.4.0"
|
||||
}
|
||||
},
|
||||
"entities": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz",
|
||||
"integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w=="
|
||||
},
|
||||
"envinfo": {
|
||||
"version": "7.7.3",
|
||||
"resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.7.3.tgz",
|
||||
|
@ -2857,6 +3013,11 @@
|
|||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz",
|
||||
"integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q=="
|
||||
},
|
||||
"events": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz",
|
||||
"integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ="
|
||||
},
|
||||
"exec-sh": {
|
||||
"version": "0.3.4",
|
||||
"resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.4.tgz",
|
||||
|
@ -3570,6 +3731,41 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"hoist-non-react-statics": {
|
||||
"version": "2.5.5",
|
||||
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz",
|
||||
"integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw=="
|
||||
},
|
||||
"html-entities": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.3.1.tgz",
|
||||
"integrity": "sha512-rhE/4Z3hIhzHAUKbW8jVcCyuT5oJCXXqhN/6mXXVCpzTmvJnoH2HL/bt3EZ6p55jbFJBeAe1ZNpL5BugLujxNA=="
|
||||
},
|
||||
"htmlparser2": {
|
||||
"version": "3.10.1",
|
||||
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz",
|
||||
"integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==",
|
||||
"requires": {
|
||||
"domelementtype": "^1.3.1",
|
||||
"domhandler": "^2.3.0",
|
||||
"domutils": "^1.5.1",
|
||||
"entities": "^1.1.1",
|
||||
"inherits": "^2.0.1",
|
||||
"readable-stream": "^3.1.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"readable-stream": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
|
||||
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
|
||||
"requires": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
"util-deprecate": "^1.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"http-errors": {
|
||||
"version": "1.7.3",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz",
|
||||
|
@ -3595,6 +3791,11 @@
|
|||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||
}
|
||||
},
|
||||
"ieee754": {
|
||||
"version": "1.1.13",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
|
||||
"integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg=="
|
||||
},
|
||||
"image-size": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/image-size/-/image-size-0.6.3.tgz",
|
||||
|
@ -3605,6 +3806,11 @@
|
|||
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.3.0.tgz",
|
||||
"integrity": "sha512-HR7EVodfFUdQCTIeySw+WDRFJlPcLOJbXfwwZ7Oom6tjsvZ3bOkCDJHehQC3nxJrv7+f9XecwazynjU8e4Vw3Q=="
|
||||
},
|
||||
"immer": {
|
||||
"version": "7.0.14",
|
||||
"resolved": "https://registry.npmjs.org/immer/-/immer-7.0.14.tgz",
|
||||
"integrity": "sha512-BxCs6pJwhgSEUEOZjywW7OA8DXVzfHjkBelSEl0A+nEu0+zS4cFVdNOONvt55N4WOm8Pu4xqSPYxhm1Lv2iBBA=="
|
||||
},
|
||||
"import-fresh": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz",
|
||||
|
@ -5349,6 +5555,11 @@
|
|||
"integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==",
|
||||
"optional": true
|
||||
},
|
||||
"nanoid": {
|
||||
"version": "3.1.12",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.12.tgz",
|
||||
"integrity": "sha512-1qstj9z5+x491jfiC4Nelk+f8XBad7LN20PmyWINJEMRSf3wcAjAWysw1qaA8z6NSKe2sjq1hRSDpBH5paCb6A=="
|
||||
},
|
||||
"nanomatch": {
|
||||
"version": "1.2.13",
|
||||
"resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz",
|
||||
|
@ -5849,6 +6060,16 @@
|
|||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.9.4.tgz",
|
||||
"integrity": "sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ=="
|
||||
},
|
||||
"query-string": {
|
||||
"version": "6.13.6",
|
||||
"resolved": "https://registry.npmjs.org/query-string/-/query-string-6.13.6.tgz",
|
||||
"integrity": "sha512-/WWZ7d9na6s2wMEGdVCVgKWE9Rt7nYyNIf7k8xmHXcesPMlEzicWo3lbYwHyA4wBktI2KrXxxZeACLbE84hvSQ==",
|
||||
"requires": {
|
||||
"decode-uri-component": "^0.2.0",
|
||||
"split-on-first": "^1.0.0",
|
||||
"strict-uri-encode": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"querystringify": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
|
||||
|
@ -6273,11 +6494,51 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"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",
|
||||
"integrity": "sha512-1CrjJf8Z6Iz2XWzfZknYtsm2sud5Lu/pLhhokkgBIKttxqGDtetDEVFDJOTJWJyKCrUPk0X5tnWi/diSF4q++w==",
|
||||
"requires": {
|
||||
"@egjs/hammerjs": "^2.0.17",
|
||||
"hoist-non-react-statics": "^2.3.1",
|
||||
"invariant": "^2.2.4",
|
||||
"prop-types": "^15.7.2"
|
||||
}
|
||||
},
|
||||
"react-native-iphone-x-helper": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-iphone-x-helper/-/react-native-iphone-x-helper-1.3.0.tgz",
|
||||
"integrity": "sha512-+/bcZWFeZt0xSS/+3CHM5K7qPL4vDO/3ARLIowzFpUPGZiPsv9+NET+XNqqseRYwFJwYMmtX+Q4TZKxAVy09ew=="
|
||||
},
|
||||
"react-native-reanimated": {
|
||||
"version": "1.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-1.13.1.tgz",
|
||||
"integrity": "sha512-3sF46jts9MbktgIasf0sTM8uhOYO5a5Q3YyQ4X1jjSE82n/fY2nW3XTFsLGfLEpK2ir4XSDhQWVgFHazaXZTww==",
|
||||
"requires": {
|
||||
"fbjs": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"react-native-render-html": {
|
||||
"version": "4.2.4",
|
||||
"resolved": "https://registry.npmjs.org/react-native-render-html/-/react-native-render-html-4.2.4.tgz",
|
||||
"integrity": "sha512-OiLItEzKgS7dzD9XI5bHhjcUEfpWdzH1FgexzjbBdICPfYjmmcefpcRmLZY1+HMfxJ7wL8iF1PzTF48LchGTBA==",
|
||||
"requires": {
|
||||
"buffer": "^4.5.1",
|
||||
"events": "^1.1.0",
|
||||
"html-entities": "^1.2.0",
|
||||
"htmlparser2": "3.10.1"
|
||||
}
|
||||
},
|
||||
"react-native-safe-area-context": {
|
||||
"version": "3.1.4",
|
||||
"resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-3.1.4.tgz",
|
||||
"integrity": "sha512-bXx3hqz4LovFoMnJIRGIWL2oJ/PHadXviBKvgZV9yNErtURQLJSn0yfQytVtiqslhaBMZOJwH4R6HiClyofvBg=="
|
||||
},
|
||||
"react-native-screens": {
|
||||
"version": "2.10.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-2.10.1.tgz",
|
||||
"integrity": "sha512-Z2kKSk4AwWRQNCBmTjViuBQK0/Lx0jc25TZptn/2gKYUCOuVRvCekoA26u0Tsb3BIQ8tWDsZW14OwDlFUXW1aw=="
|
||||
},
|
||||
"react-native-web": {
|
||||
"version": "0.13.18",
|
||||
"resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.13.18.tgz",
|
||||
|
@ -6294,6 +6555,44 @@
|
|||
"react-timer-mixin": "^0.13.4"
|
||||
}
|
||||
},
|
||||
"react-native-webview": {
|
||||
"version": "10.7.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-10.7.0.tgz",
|
||||
"integrity": "sha512-4TSYwJqMBUTKB9+xqGbPwx+eLXbp6RRD7lQ2BumT8eSTfuuqr2rXNqcrlKU1VRla7QGGYowmYmxl2aXIx5k9wA==",
|
||||
"requires": {
|
||||
"escape-string-regexp": "2.0.0",
|
||||
"invariant": "2.2.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"escape-string-regexp": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz",
|
||||
"integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"react-redux": {
|
||||
"version": "7.2.1",
|
||||
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.1.tgz",
|
||||
"integrity": "sha512-T+VfD/bvgGTUA74iW9d2i5THrDQWbweXP0AVNI8tNd1Rk5ch1rnMiJkDD67ejw7YBKM4+REvcvqRuWJb7BLuEg==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"hoist-non-react-statics": "^3.3.0",
|
||||
"loose-envify": "^1.4.0",
|
||||
"prop-types": "^15.7.2",
|
||||
"react-is": "^16.9.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"hoist-non-react-statics": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
|
||||
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
|
||||
"requires": {
|
||||
"react-is": "^16.7.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"react-refresh": {
|
||||
"version": "0.4.3",
|
||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.4.3.tgz",
|
||||
|
@ -6325,6 +6624,27 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"redux": {
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/redux/-/redux-4.0.5.tgz",
|
||||
"integrity": "sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w==",
|
||||
"requires": {
|
||||
"loose-envify": "^1.4.0",
|
||||
"symbol-observable": "^1.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"symbol-observable": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz",
|
||||
"integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"redux-thunk": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.3.0.tgz",
|
||||
"integrity": "sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw=="
|
||||
},
|
||||
"regenerate": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.1.tgz",
|
||||
|
@ -6905,6 +7225,11 @@
|
|||
"resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz",
|
||||
"integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM="
|
||||
},
|
||||
"split-on-first": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz",
|
||||
"integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw=="
|
||||
},
|
||||
"split-string": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
|
||||
|
@ -6960,6 +7285,11 @@
|
|||
"resolved": "https://registry.npmjs.org/stream-buffers/-/stream-buffers-2.2.0.tgz",
|
||||
"integrity": "sha1-kdX1Ew0c75bc+n9yaUUYh0HQnuQ="
|
||||
},
|
||||
"strict-uri-encode": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz",
|
||||
"integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY="
|
||||
},
|
||||
"string-width": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
|
||||
|
|
13
package.json
13
package.json
|
@ -8,12 +8,23 @@
|
|||
"eject": "expo eject"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-native-community/masked-view": "0.1.10",
|
||||
"@react-navigation/native": "^5.7.6",
|
||||
"@react-navigation/stack": "^5.9.3",
|
||||
"@reduxjs/toolkit": "^1.4.0",
|
||||
"expo": "~39.0.2",
|
||||
"expo-status-bar": "~1.0.2",
|
||||
"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-web": "~0.13.12"
|
||||
"react-native-gesture-handler": "~1.7.0",
|
||||
"react-native-reanimated": "~1.13.0",
|
||||
"react-native-render-html": "^4.2.4",
|
||||
"react-native-safe-area-context": "3.1.4",
|
||||
"react-native-screens": "~2.10.1",
|
||||
"react-native-web": "~0.13.12",
|
||||
"react-native-webview": "10.7.0",
|
||||
"react-redux": "^7.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "~7.9.0"
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
import React, { useEffect } from 'react'
|
||||
import { ActivityIndicator, FlatList, View } from 'react-native'
|
||||
import { useSelector, useDispatch } from 'react-redux'
|
||||
|
||||
import TootTimeline from '../components/TootTimeline'
|
||||
|
||||
import { allToots, fetchNewer, fetchOlder } from './timelineSlice'
|
||||
|
||||
export default function ScreenTimeline () {
|
||||
const dispatch = useDispatch()
|
||||
const toots = useSelector(allToots)
|
||||
|
||||
const fetchNewerStatus = useSelector(state => state.timeline.status)
|
||||
const fetchNewerError = useSelector(state => state.timeline.error)
|
||||
const fetchOlderStatus = useSelector(state => state.timeline.status)
|
||||
const fetchOlderError = useSelector(state => state.timeline.error)
|
||||
|
||||
useEffect(() => {
|
||||
if (fetchOlderStatus === 'idle') {
|
||||
dispatch(fetchOlder())
|
||||
}
|
||||
}, [fetchOlderStatus, dispatch])
|
||||
|
||||
let content
|
||||
|
||||
if (fetchOlderStatus === 'error') {
|
||||
content = <Text>{fetchOlderError}</Text>
|
||||
} else {
|
||||
content = (
|
||||
<>
|
||||
<FlatList
|
||||
data={toots}
|
||||
keyExtractor={({ id }) => id}
|
||||
renderItem={TootTimeline}
|
||||
onRefresh={() => dispatch(fetchNewer(toots[0].id))}
|
||||
refreshing={fetchNewerStatus === 'loading'}
|
||||
onEndReached={() => dispatch(fetchOlder(toots[toots.length - 1].id))}
|
||||
onEndReachedThreshold={0.2}
|
||||
style={{ height: '100%', width: '100%' }}
|
||||
/>
|
||||
{fetchOlderStatus === 'loading' && <ActivityIndicator />}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
return <View>{content}</View>
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'
|
||||
|
||||
export const fetchNewer = createAsyncThunk('timeline/fetchNewer', async id => {
|
||||
let data
|
||||
try {
|
||||
const response = await fetch(
|
||||
`https://m.cmx.im/api/v1/timelines/public?since_id=${id}`
|
||||
)
|
||||
data = await response.json()
|
||||
if (response.ok) {
|
||||
return data
|
||||
}
|
||||
throw new Error(response.statusText)
|
||||
} catch (err) {
|
||||
return Promise.reject(err.message ? err.message : data)
|
||||
}
|
||||
})
|
||||
|
||||
export const fetchOlder = createAsyncThunk('timeline/fetchOlder', async id => {
|
||||
let data
|
||||
try {
|
||||
const response = await fetch(
|
||||
`https://m.cmx.im/api/v1/timelines/public${id ? `?max_id=${id}` : ''}`
|
||||
)
|
||||
data = await response.json()
|
||||
if (response.ok) {
|
||||
return data
|
||||
}
|
||||
throw new Error(response.statusText)
|
||||
} catch (err) {
|
||||
return Promise.reject(err.message ? err.message : data)
|
||||
}
|
||||
})
|
||||
|
||||
export const timelineSlice = createSlice({
|
||||
name: 'timeline',
|
||||
initialState: {
|
||||
toots: [],
|
||||
status: 'idle',
|
||||
error: null
|
||||
},
|
||||
extraReducers: {
|
||||
[fetchNewer.pending]: (state, action) => {
|
||||
state.status = 'loading'
|
||||
},
|
||||
[fetchNewer.fulfilled]: (state, action) => {
|
||||
state.status = 'succeeded'
|
||||
state.toots.unshift(...action.payload)
|
||||
},
|
||||
[fetchNewer.rejected]: (state, action) => {
|
||||
state.status = 'failed'
|
||||
state.error = action.payload
|
||||
},
|
||||
[fetchOlder.pending]: (state, action) => {
|
||||
state.status = 'loading'
|
||||
},
|
||||
[fetchOlder.fulfilled]: (state, action) => {
|
||||
state.status = 'succeeded'
|
||||
state.toots.push(...action.payload)
|
||||
},
|
||||
[fetchOlder.rejected]: (state, action) => {
|
||||
state.status = 'failed'
|
||||
state.error = action.payload
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// export const { update } = timelineSlice.actions
|
||||
|
||||
export const allToots = state => state.timeline.toots
|
||||
|
||||
export default timelineSlice.reducer
|
|
@ -0,0 +1,19 @@
|
|||
export default function relative_time (date) {
|
||||
var units = {
|
||||
year: 24 * 60 * 60 * 1000 * 365,
|
||||
month: (24 * 60 * 60 * 1000 * 365) / 12,
|
||||
day: 24 * 60 * 60 * 1000,
|
||||
hour: 60 * 60 * 1000,
|
||||
minute: 60 * 1000,
|
||||
second: 1000
|
||||
}
|
||||
|
||||
var rtf = new Intl.RelativeTimeFormat('zh', { numeric: 'auto' })
|
||||
|
||||
var elapsed = new Date(date) - new Date()
|
||||
|
||||
// "Math.abs" accounts for both "past" & "future" scenarios
|
||||
for (var u in units)
|
||||
if (Math.abs(elapsed) > units[u] || u == 'second')
|
||||
return rtf.format(Math.round(elapsed / units[u]), u)
|
||||
}
|
Loading…
Reference in New Issue