Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Poll periodically for new posts

+89 -3
+1
package.json
··· 31 31 "react-circular-progressbar": "^2.1.0", 32 32 "react-dom": "17.0.2", 33 33 "react-native": "0.68.2", 34 + "react-native-appstate-hook": "^1.0.6", 34 35 "react-native-gesture-handler": "^2.5.0", 35 36 "react-native-inappbrowser-reborn": "^3.6.3", 36 37 "react-native-linear-gradient": "^2.6.2",
+27 -1
src/state/models/feed-view.ts
··· 149 149 // state 150 150 isLoading = false 151 151 isRefreshing = false 152 + hasNewLatest = false 152 153 hasLoaded = false 153 154 error = '' 154 155 params: GetTimeline.QueryParams | GetAuthorFeed.QueryParams ··· 195 196 return this.hasLoaded && !this.hasContent 196 197 } 197 198 199 + setHasNewLatest(v: boolean) { 200 + this.hasNewLatest = v 201 + } 202 + 198 203 // public api 199 204 // = 200 205 ··· 209 214 return this._loadPromise 210 215 } 211 216 await this._pendingWork() 217 + this.setHasNewLatest(false) 212 218 this._loadPromise = this._initialLoad(isRefreshing) 213 219 await this._loadPromise 214 220 this._loadPromise = undefined ··· 242 248 return this._loadLatestPromise 243 249 } 244 250 await this._pendingWork() 251 + this.setHasNewLatest(false) 245 252 this._loadLatestPromise = this._loadLatest() 246 253 await this._loadLatestPromise 247 254 this._loadLatestPromise = undefined ··· 260 267 this._updatePromise = undefined 261 268 } 262 269 270 + /** 271 + * Check if new postrs are available 272 + */ 273 + async checkForLatest() { 274 + if (this.hasNewLatest) { 275 + return 276 + } 277 + await this._pendingWork() 278 + const res = await this._getFeed({limit: 1}) 279 + this.setHasNewLatest( 280 + res.data.feed[0] && 281 + (this.feed.length === 0 || res.data.feed[0].uri !== this.feed[0]?.uri), 282 + ) 283 + } 284 + 263 285 // state transitions 264 286 // = 265 287 ··· 380 402 381 403 private _prependAll(res: GetTimeline.Response | GetAuthorFeed.Response) { 382 404 let counter = this.feed.length 405 + const toPrepend = [] 383 406 for (const item of res.data.feed) { 384 407 if (this.feed.find(item2 => item2.uri === item.uri)) { 385 - return // stop here - we've hit a post we already ahve 408 + return // stop here - we've hit a post we already have 386 409 } 410 + toPrepend.unshift(item) // reverse the order 411 + } 412 + for (const item of toPrepend) { 387 413 this._prepend(counter++, item) 388 414 } 389 415 }
+2
src/view/index.ts
··· 6 6 import {faAngleRight} from '@fortawesome/free-solid-svg-icons/faAngleRight' 7 7 import {faArrowLeft} from '@fortawesome/free-solid-svg-icons/faArrowLeft' 8 8 import {faArrowRight} from '@fortawesome/free-solid-svg-icons/faArrowRight' 9 + import {faArrowUp} from '@fortawesome/free-solid-svg-icons/faArrowUp' 9 10 import {faArrowRightFromBracket} from '@fortawesome/free-solid-svg-icons' 10 11 import {faArrowUpFromBracket} from '@fortawesome/free-solid-svg-icons/faArrowUpFromBracket' 11 12 import {faArrowUpRightFromSquare} from '@fortawesome/free-solid-svg-icons/faArrowUpRightFromSquare' ··· 64 65 faAngleRight, 65 66 faArrowLeft, 66 67 faArrowRight, 68 + faArrowUp, 67 69 faArrowRightFromBracket, 68 70 faArrowUpFromBracket, 69 71 faArrowUpRightFromSquare,
+54 -2
src/view/screens/Home.tsx
··· 1 1 import React, {useState, useEffect, useMemo} from 'react' 2 - import {View} from 'react-native' 2 + import {StyleSheet, Text, TouchableOpacity, View} from 'react-native' 3 3 import {observer} from 'mobx-react-lite' 4 + import useAppState from 'react-native-appstate-hook' 5 + import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' 4 6 import {ViewHeader} from '../com/util/ViewHeader' 5 7 import {Feed} from '../com/posts/Feed' 6 8 import {FAB} from '../com/util/FloatingActionButton' 7 9 import {useStores} from '../../state' 8 10 import {FeedModel} from '../../state/models/feed-view' 9 11 import {ScreenParams} from '../routes' 10 - import {s} from '../lib/styles' 12 + import {s, colors} from '../lib/styles' 11 13 12 14 export const Home = observer(function Home({ 13 15 visible, ··· 15 17 }: ScreenParams) { 16 18 const store = useStores() 17 19 const [hasSetup, setHasSetup] = useState<boolean>(false) 20 + const {appState} = useAppState({ 21 + onForeground: () => doPoll(true), 22 + }) 18 23 const defaultFeedView = useMemo<FeedModel>( 19 24 () => 20 25 new FeedModel(store, 'home', { ··· 23 28 [store], 24 29 ) 25 30 31 + const doPoll = (knownActive = false) => { 32 + if ((!knownActive && appState !== 'active') || !visible) { 33 + return 34 + } 35 + if (defaultFeedView.isLoading) { 36 + return 37 + } 38 + console.log('Polling home feed') 39 + defaultFeedView.checkForLatest().catch(e => { 40 + console.error('Failed to poll feed', e) 41 + }) 42 + } 43 + 26 44 useEffect(() => { 27 45 let aborted = false 46 + const pollInterval = setInterval(() => doPoll(), 15e3) 28 47 if (!visible) { 48 + console.log('hit') 29 49 return 30 50 } 31 51 if (hasSetup) { ··· 40 60 }) 41 61 } 42 62 return () => { 63 + clearInterval(pollInterval) 43 64 aborted = true 44 65 } 45 66 }, [visible, store]) ··· 53 74 const onPressTryAgain = () => { 54 75 defaultFeedView.refresh() 55 76 } 77 + const onPressLoadLatest = () => { 78 + defaultFeedView.refresh() 79 + scrollElRef?.current?.scrollToOffset({offset: 0}) 80 + } 56 81 57 82 return ( 58 83 <View style={s.flex1}> ··· 64 89 style={{flex: 1}} 65 90 onPressTryAgain={onPressTryAgain} 66 91 /> 92 + {defaultFeedView.hasNewLatest ? ( 93 + <TouchableOpacity style={styles.loadLatest} onPress={onPressLoadLatest}> 94 + <FontAwesomeIcon icon="arrow-up" style={{color: colors.white}} /> 95 + <Text style={styles.loadLatestText}>Load new posts</Text> 96 + </TouchableOpacity> 97 + ) : undefined} 67 98 <FAB icon="pen-nib" onPress={onComposePress} /> 68 99 </View> 69 100 ) 70 101 }) 102 + 103 + const styles = StyleSheet.create({ 104 + loadLatest: { 105 + flexDirection: 'row', 106 + position: 'absolute', 107 + left: 10, 108 + bottom: 15, 109 + backgroundColor: colors.pink3, 110 + paddingHorizontal: 10, 111 + paddingVertical: 8, 112 + borderRadius: 30, 113 + shadowColor: '#000', 114 + shadowOpacity: 0.3, 115 + shadowOffset: {width: 0, height: 1}, 116 + }, 117 + loadLatestText: { 118 + color: colors.white, 119 + fontWeight: 'bold', 120 + marginLeft: 5, 121 + }, 122 + })
+5
yarn.lock
··· 10148 10148 resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" 10149 10149 integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== 10150 10150 10151 + react-native-appstate-hook@^1.0.6: 10152 + version "1.0.6" 10153 + resolved "https://registry.yarnpkg.com/react-native-appstate-hook/-/react-native-appstate-hook-1.0.6.tgz#cbc16e7b89cfaea034cabd999f00e99053cabd06" 10154 + integrity sha512-0hPVyf5yLxCSVrrNEuGqN1ZnSSj3Ye2gZex0NtcK/AHYwMc0rXWFNZjBKOoZSouspqu3hXBbQ6NOUSTzrME1AQ== 10155 + 10151 10156 react-native-codegen@^0.0.17: 10152 10157 version "0.0.17" 10153 10158 resolved "https://registry.yarnpkg.com/react-native-codegen/-/react-native-codegen-0.0.17.tgz#83fb814d94061cbd46667f510d2ddba35ffb50ac"