Poll periodically for new posts

zio/stable
Paul Frazee 2022-11-17 12:33:19 -06:00
parent f6e591339d
commit eae5ac839c
5 changed files with 89 additions and 3 deletions

View File

@ -31,6 +31,7 @@
"react-circular-progressbar": "^2.1.0", "react-circular-progressbar": "^2.1.0",
"react-dom": "17.0.2", "react-dom": "17.0.2",
"react-native": "0.68.2", "react-native": "0.68.2",
"react-native-appstate-hook": "^1.0.6",
"react-native-gesture-handler": "^2.5.0", "react-native-gesture-handler": "^2.5.0",
"react-native-inappbrowser-reborn": "^3.6.3", "react-native-inappbrowser-reborn": "^3.6.3",
"react-native-linear-gradient": "^2.6.2", "react-native-linear-gradient": "^2.6.2",

View File

@ -149,6 +149,7 @@ export class FeedModel {
// state // state
isLoading = false isLoading = false
isRefreshing = false isRefreshing = false
hasNewLatest = false
hasLoaded = false hasLoaded = false
error = '' error = ''
params: GetTimeline.QueryParams | GetAuthorFeed.QueryParams params: GetTimeline.QueryParams | GetAuthorFeed.QueryParams
@ -195,6 +196,10 @@ export class FeedModel {
return this.hasLoaded && !this.hasContent return this.hasLoaded && !this.hasContent
} }
setHasNewLatest(v: boolean) {
this.hasNewLatest = v
}
// public api // public api
// = // =
@ -209,6 +214,7 @@ export class FeedModel {
return this._loadPromise return this._loadPromise
} }
await this._pendingWork() await this._pendingWork()
this.setHasNewLatest(false)
this._loadPromise = this._initialLoad(isRefreshing) this._loadPromise = this._initialLoad(isRefreshing)
await this._loadPromise await this._loadPromise
this._loadPromise = undefined this._loadPromise = undefined
@ -242,6 +248,7 @@ export class FeedModel {
return this._loadLatestPromise return this._loadLatestPromise
} }
await this._pendingWork() await this._pendingWork()
this.setHasNewLatest(false)
this._loadLatestPromise = this._loadLatest() this._loadLatestPromise = this._loadLatest()
await this._loadLatestPromise await this._loadLatestPromise
this._loadLatestPromise = undefined this._loadLatestPromise = undefined
@ -260,6 +267,21 @@ export class FeedModel {
this._updatePromise = undefined this._updatePromise = undefined
} }
/**
* Check if new postrs are available
*/
async checkForLatest() {
if (this.hasNewLatest) {
return
}
await this._pendingWork()
const res = await this._getFeed({limit: 1})
this.setHasNewLatest(
res.data.feed[0] &&
(this.feed.length === 0 || res.data.feed[0].uri !== this.feed[0]?.uri),
)
}
// state transitions // state transitions
// = // =
@ -380,10 +402,14 @@ export class FeedModel {
private _prependAll(res: GetTimeline.Response | GetAuthorFeed.Response) { private _prependAll(res: GetTimeline.Response | GetAuthorFeed.Response) {
let counter = this.feed.length let counter = this.feed.length
const toPrepend = []
for (const item of res.data.feed) { for (const item of res.data.feed) {
if (this.feed.find(item2 => item2.uri === item.uri)) { if (this.feed.find(item2 => item2.uri === item.uri)) {
return // stop here - we've hit a post we already ahve return // stop here - we've hit a post we already have
} }
toPrepend.unshift(item) // reverse the order
}
for (const item of toPrepend) {
this._prepend(counter++, item) this._prepend(counter++, item)
} }
} }

View File

@ -6,6 +6,7 @@ import {faAngleLeft} from '@fortawesome/free-solid-svg-icons/faAngleLeft'
import {faAngleRight} from '@fortawesome/free-solid-svg-icons/faAngleRight' import {faAngleRight} from '@fortawesome/free-solid-svg-icons/faAngleRight'
import {faArrowLeft} from '@fortawesome/free-solid-svg-icons/faArrowLeft' import {faArrowLeft} from '@fortawesome/free-solid-svg-icons/faArrowLeft'
import {faArrowRight} from '@fortawesome/free-solid-svg-icons/faArrowRight' import {faArrowRight} from '@fortawesome/free-solid-svg-icons/faArrowRight'
import {faArrowUp} from '@fortawesome/free-solid-svg-icons/faArrowUp'
import {faArrowRightFromBracket} from '@fortawesome/free-solid-svg-icons' import {faArrowRightFromBracket} from '@fortawesome/free-solid-svg-icons'
import {faArrowUpFromBracket} from '@fortawesome/free-solid-svg-icons/faArrowUpFromBracket' import {faArrowUpFromBracket} from '@fortawesome/free-solid-svg-icons/faArrowUpFromBracket'
import {faArrowUpRightFromSquare} from '@fortawesome/free-solid-svg-icons/faArrowUpRightFromSquare' import {faArrowUpRightFromSquare} from '@fortawesome/free-solid-svg-icons/faArrowUpRightFromSquare'
@ -64,6 +65,7 @@ export function setup() {
faAngleRight, faAngleRight,
faArrowLeft, faArrowLeft,
faArrowRight, faArrowRight,
faArrowUp,
faArrowRightFromBracket, faArrowRightFromBracket,
faArrowUpFromBracket, faArrowUpFromBracket,
faArrowUpRightFromSquare, faArrowUpRightFromSquare,

View File

@ -1,13 +1,15 @@
import React, {useState, useEffect, useMemo} from 'react' import React, {useState, useEffect, useMemo} from 'react'
import {View} from 'react-native' import {StyleSheet, Text, TouchableOpacity, View} from 'react-native'
import {observer} from 'mobx-react-lite' import {observer} from 'mobx-react-lite'
import useAppState from 'react-native-appstate-hook'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {ViewHeader} from '../com/util/ViewHeader' import {ViewHeader} from '../com/util/ViewHeader'
import {Feed} from '../com/posts/Feed' import {Feed} from '../com/posts/Feed'
import {FAB} from '../com/util/FloatingActionButton' import {FAB} from '../com/util/FloatingActionButton'
import {useStores} from '../../state' import {useStores} from '../../state'
import {FeedModel} from '../../state/models/feed-view' import {FeedModel} from '../../state/models/feed-view'
import {ScreenParams} from '../routes' import {ScreenParams} from '../routes'
import {s} from '../lib/styles' import {s, colors} from '../lib/styles'
export const Home = observer(function Home({ export const Home = observer(function Home({
visible, visible,
@ -15,6 +17,9 @@ export const Home = observer(function Home({
}: ScreenParams) { }: ScreenParams) {
const store = useStores() const store = useStores()
const [hasSetup, setHasSetup] = useState<boolean>(false) const [hasSetup, setHasSetup] = useState<boolean>(false)
const {appState} = useAppState({
onForeground: () => doPoll(true),
})
const defaultFeedView = useMemo<FeedModel>( const defaultFeedView = useMemo<FeedModel>(
() => () =>
new FeedModel(store, 'home', { new FeedModel(store, 'home', {
@ -23,9 +28,24 @@ export const Home = observer(function Home({
[store], [store],
) )
const doPoll = (knownActive = false) => {
if ((!knownActive && appState !== 'active') || !visible) {
return
}
if (defaultFeedView.isLoading) {
return
}
console.log('Polling home feed')
defaultFeedView.checkForLatest().catch(e => {
console.error('Failed to poll feed', e)
})
}
useEffect(() => { useEffect(() => {
let aborted = false let aborted = false
const pollInterval = setInterval(() => doPoll(), 15e3)
if (!visible) { if (!visible) {
console.log('hit')
return return
} }
if (hasSetup) { if (hasSetup) {
@ -40,6 +60,7 @@ export const Home = observer(function Home({
}) })
} }
return () => { return () => {
clearInterval(pollInterval)
aborted = true aborted = true
} }
}, [visible, store]) }, [visible, store])
@ -53,6 +74,10 @@ export const Home = observer(function Home({
const onPressTryAgain = () => { const onPressTryAgain = () => {
defaultFeedView.refresh() defaultFeedView.refresh()
} }
const onPressLoadLatest = () => {
defaultFeedView.refresh()
scrollElRef?.current?.scrollToOffset({offset: 0})
}
return ( return (
<View style={s.flex1}> <View style={s.flex1}>
@ -64,7 +89,34 @@ export const Home = observer(function Home({
style={{flex: 1}} style={{flex: 1}}
onPressTryAgain={onPressTryAgain} onPressTryAgain={onPressTryAgain}
/> />
{defaultFeedView.hasNewLatest ? (
<TouchableOpacity style={styles.loadLatest} onPress={onPressLoadLatest}>
<FontAwesomeIcon icon="arrow-up" style={{color: colors.white}} />
<Text style={styles.loadLatestText}>Load new posts</Text>
</TouchableOpacity>
) : undefined}
<FAB icon="pen-nib" onPress={onComposePress} /> <FAB icon="pen-nib" onPress={onComposePress} />
</View> </View>
) )
}) })
const styles = StyleSheet.create({
loadLatest: {
flexDirection: 'row',
position: 'absolute',
left: 10,
bottom: 15,
backgroundColor: colors.pink3,
paddingHorizontal: 10,
paddingVertical: 8,
borderRadius: 30,
shadowColor: '#000',
shadowOpacity: 0.3,
shadowOffset: {width: 0, height: 1},
},
loadLatestText: {
color: colors.white,
fontWeight: 'bold',
marginLeft: 5,
},
})

View File

@ -10148,6 +10148,11 @@ react-is@^16.13.1, react-is@^16.7.0:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
react-native-appstate-hook@^1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/react-native-appstate-hook/-/react-native-appstate-hook-1.0.6.tgz#cbc16e7b89cfaea034cabd999f00e99053cabd06"
integrity sha512-0hPVyf5yLxCSVrrNEuGqN1ZnSSj3Ye2gZex0NtcK/AHYwMc0rXWFNZjBKOoZSouspqu3hXBbQ6NOUSTzrME1AQ==
react-native-codegen@^0.0.17: react-native-codegen@^0.0.17:
version "0.0.17" version "0.0.17"
resolved "https://registry.yarnpkg.com/react-native-codegen/-/react-native-codegen-0.0.17.tgz#83fb814d94061cbd46667f510d2ddba35ffb50ac" resolved "https://registry.yarnpkg.com/react-native-codegen/-/react-native-codegen-0.0.17.tgz#83fb814d94061cbd46667f510d2ddba35ffb50ac"