Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

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

Improve reliability of screen titles

+60 -37
+26 -11
src/state/models/navigation.ts
··· 1 1 import {makeAutoObservable} from 'mobx' 2 2 import {isObj, hasProp} from '../lib/type-guards' 3 3 4 - let __tabId = 0 5 - function genTabId() { 6 - return ++__tabId 4 + let __id = 0 5 + function genId() { 6 + return ++__id 7 7 } 8 8 9 9 interface HistoryItem { 10 10 url: string 11 11 ts: number 12 12 title?: string 13 + id: number 13 14 } 14 15 16 + export type HistoryPtr = [number, number] 17 + 15 18 export class NavigationTabModel { 16 - id = genTabId() 17 - history: HistoryItem[] = [{url: '/', ts: Date.now()}] 19 + id = genId() 20 + history: HistoryItem[] = [{url: '/', ts: Date.now(), id: genId()}] 18 21 index = 0 19 22 isNewTab = false 20 23 ··· 47 50 url: item.url, 48 51 title: item.title, 49 52 index: start + i, 53 + id: item.id, 50 54 })) 51 55 } 52 56 ··· 61 65 url: item.url, 62 66 title: item.title, 63 67 index: start + i, 68 + id: item.id, 64 69 })) 65 70 } 66 71 ··· 78 83 if (this.index < this.history.length - 1) { 79 84 this.history.length = this.index + 1 80 85 } 81 - this.history.push({url, title, ts: Date.now()}) 86 + this.history.push({url, title, ts: Date.now(), id: genId()}) 82 87 this.index = this.history.length - 1 83 88 } 84 89 } ··· 86 91 refresh() { 87 92 this.history = [ 88 93 ...this.history.slice(0, this.index), 89 - {url: this.current.url, title: this.current.title, ts: Date.now()}, 94 + { 95 + url: this.current.url, 96 + title: this.current.title, 97 + ts: Date.now(), 98 + id: this.current.id, 99 + }, 90 100 ...this.history.slice(this.index + 1), 91 101 ] 92 102 } ··· 109 119 } 110 120 } 111 121 112 - setTitle(title: string) { 113 - this.current.title = title 122 + setTitle(id: number, title: string) { 123 + this.history = this.history.map(h => { 124 + if (h.id === id) { 125 + return {...h, title} 126 + } 127 + return h 128 + }) 114 129 } 115 130 116 131 setIsNewTab(v: boolean) { ··· 203 218 this.tab.refresh() 204 219 } 205 220 206 - setTitle(title: string) { 207 - this.tab.setTitle(title) 221 + setTitle(ptr: HistoryPtr, title: string) { 222 + this.tabs.find(t => t.id === ptr[0])?.setTitle(ptr[1], title) 208 223 } 209 224 210 225 // tab management
+1
src/view/routes.ts
··· 17 17 import {Settings} from './screens/Settings' 18 18 19 19 export type ScreenParams = { 20 + navIdx: [number, number] 20 21 params: Record<string, any> 21 22 visible: boolean 22 23 scrollElRef?: MutableRefObject<FlatList<any> | undefined>
+2 -2
src/view/screens/Contacts.tsx
··· 8 8 import {ScreenParams} from '../routes' 9 9 import {useStores} from '../../state' 10 10 11 - export const Contacts = ({visible, params}: ScreenParams) => { 11 + export const Contacts = ({navIdx, visible, params}: ScreenParams) => { 12 12 const store = useStores() 13 13 const selectorInterp = useSharedValue(0) 14 14 15 15 useEffect(() => { 16 16 if (visible) { 17 - store.nav.setTitle(`Contacts`) 17 + store.nav.setTitle(navIdx, `Contacts`) 18 18 } 19 19 }, [store, visible]) 20 20
+2 -1
src/view/screens/Home.tsx
··· 12 12 import {s, colors} from '../lib/styles' 13 13 14 14 export const Home = observer(function Home({ 15 + navIdx, 15 16 visible, 16 17 scrollElRef, 17 18 }: ScreenParams) { ··· 51 52 console.log('Updating home feed') 52 53 defaultFeedView.update() 53 54 } else { 54 - store.nav.setTitle('Home') 55 + store.nav.setTitle(navIdx, 'Home') 55 56 console.log('Fetching home feed') 56 57 defaultFeedView.setup().then(() => { 57 58 if (aborted) return
+2 -2
src/view/screens/Notifications.tsx
··· 7 7 import {NotificationsViewModel} from '../../state/models/notifications-view' 8 8 import {ScreenParams} from '../routes' 9 9 10 - export const Notifications = ({visible}: ScreenParams) => { 10 + export const Notifications = ({navIdx, visible}: ScreenParams) => { 11 11 const [hasSetup, setHasSetup] = useState<boolean>(false) 12 12 const [notesView, setNotesView] = useState< 13 13 NotificationsViewModel | undefined ··· 24 24 console.log('Updating notifications feed') 25 25 notesView?.update() 26 26 } else { 27 - store.nav.setTitle('Notifications') 27 + store.nav.setTitle(navIdx, 'Notifications') 28 28 const newNotesView = new NotificationsViewModel(store, {}) 29 29 setNotesView(newNotesView) 30 30 newNotesView.setup().then(() => {
+2 -2
src/view/screens/PostDownvotedBy.tsx
··· 6 6 import {useStores} from '../../state' 7 7 import {makeRecordUri} from '../lib/strings' 8 8 9 - export const PostDownvotedBy = ({visible, params}: ScreenParams) => { 9 + export const PostDownvotedBy = ({navIdx, visible, params}: ScreenParams) => { 10 10 const store = useStores() 11 11 const {name, rkey} = params 12 12 const uri = makeRecordUri(name, 'app.bsky.feed.post', rkey) 13 13 14 14 useEffect(() => { 15 15 if (visible) { 16 - store.nav.setTitle('Downvoted by') 16 + store.nav.setTitle(navIdx, 'Downvoted by') 17 17 } 18 18 }, [store, visible]) 19 19
+2 -2
src/view/screens/PostRepostedBy.tsx
··· 6 6 import {useStores} from '../../state' 7 7 import {makeRecordUri} from '../lib/strings' 8 8 9 - export const PostRepostedBy = ({visible, params}: ScreenParams) => { 9 + export const PostRepostedBy = ({navIdx, visible, params}: ScreenParams) => { 10 10 const store = useStores() 11 11 const {name, rkey} = params 12 12 const uri = makeRecordUri(name, 'app.bsky.feed.post', rkey) 13 13 14 14 useEffect(() => { 15 15 if (visible) { 16 - store.nav.setTitle('Reposted by') 16 + store.nav.setTitle(navIdx, 'Reposted by') 17 17 } 18 18 }, [store, visible]) 19 19
+2 -2
src/view/screens/PostThread.tsx
··· 6 6 import {ScreenParams} from '../routes' 7 7 import {useStores} from '../../state' 8 8 9 - export const PostThread = ({visible, params}: ScreenParams) => { 9 + export const PostThread = ({navIdx, visible, params}: ScreenParams) => { 10 10 const store = useStores() 11 11 const {name, rkey} = params 12 12 const uri = makeRecordUri(name, 'app.bsky.feed.post', rkey) 13 13 14 14 useEffect(() => { 15 15 if (visible) { 16 - store.nav.setTitle(`Post by ${name}`) 16 + store.nav.setTitle(navIdx, `Post by ${name}`) 17 17 } 18 18 }, [visible, store.nav, name]) 19 19
+2 -2
src/view/screens/PostUpvotedBy.tsx
··· 6 6 import {useStores} from '../../state' 7 7 import {makeRecordUri} from '../lib/strings' 8 8 9 - export const PostUpvotedBy = ({visible, params}: ScreenParams) => { 9 + export const PostUpvotedBy = ({navIdx, visible, params}: ScreenParams) => { 10 10 const store = useStores() 11 11 const {name, rkey} = params 12 12 const uri = makeRecordUri(name, 'app.bsky.feed.post', rkey) 13 13 14 14 useEffect(() => { 15 15 if (visible) { 16 - store.nav.setTitle('Upvoted by') 16 + store.nav.setTitle(navIdx, 'Upvoted by') 17 17 } 18 18 }, [store, visible]) 19 19
+2 -2
src/view/screens/Profile.tsx
··· 23 23 const END_ITEM = {_reactKey: '__end__'} 24 24 const EMPTY_ITEM = {_reactKey: '__empty__'} 25 25 26 - export const Profile = observer(({visible, params}: ScreenParams) => { 26 + export const Profile = observer(({navIdx, visible, params}: ScreenParams) => { 27 27 const store = useStores() 28 28 const [hasSetup, setHasSetup] = useState<boolean>(false) 29 29 const uiState = useMemo( ··· 41 41 uiState.update() 42 42 } else { 43 43 console.log('Fetching profile for', params.name) 44 - store.nav.setTitle(params.name) 44 + store.nav.setTitle(navIdx, params.name) 45 45 uiState.setup().then(() => { 46 46 if (aborted) return 47 47 setHasSetup(true)
+2 -2
src/view/screens/ProfileFollowers.tsx
··· 5 5 import {ScreenParams} from '../routes' 6 6 import {useStores} from '../../state' 7 7 8 - export const ProfileFollowers = ({visible, params}: ScreenParams) => { 8 + export const ProfileFollowers = ({navIdx, visible, params}: ScreenParams) => { 9 9 const store = useStores() 10 10 const {name} = params 11 11 12 12 useEffect(() => { 13 13 if (visible) { 14 - store.nav.setTitle(`Followers of ${name}`) 14 + store.nav.setTitle(navIdx, `Followers of ${name}`) 15 15 } 16 16 }, [store, visible, name]) 17 17
+2 -2
src/view/screens/ProfileFollows.tsx
··· 5 5 import {ScreenParams} from '../routes' 6 6 import {useStores} from '../../state' 7 7 8 - export const ProfileFollows = ({visible, params}: ScreenParams) => { 8 + export const ProfileFollows = ({navIdx, visible, params}: ScreenParams) => { 9 9 const store = useStores() 10 10 const {name} = params 11 11 12 12 useEffect(() => { 13 13 if (visible) { 14 - store.nav.setTitle(`Followed by ${name}`) 14 + store.nav.setTitle(navIdx, `Followed by ${name}`) 15 15 } 16 16 }, [store, visible, name]) 17 17
+2 -2
src/view/screens/ProfileMembers.tsx
··· 5 5 import {ScreenParams} from '../routes' 6 6 import {useStores} from '../../state' 7 7 8 - export const ProfileMembers = ({visible, params}: ScreenParams) => { 8 + export const ProfileMembers = ({navIdx, visible, params}: ScreenParams) => { 9 9 const store = useStores() 10 10 const {name} = params 11 11 12 12 useEffect(() => { 13 13 if (visible) { 14 - store.nav.setTitle(`Members of ${name}`) 14 + store.nav.setTitle(navIdx, `Members of ${name}`) 15 15 } 16 16 }, [store, visible, name]) 17 17
+2 -2
src/view/screens/Search.tsx
··· 7 7 import {useStores} from '../../state' 8 8 import {colors} from '../lib/styles' 9 9 10 - export const Search = ({visible, params}: ScreenParams) => { 10 + export const Search = ({navIdx, visible, params}: ScreenParams) => { 11 11 const store = useStores() 12 12 const {name} = params 13 13 14 14 useEffect(() => { 15 15 if (visible) { 16 - store.nav.setTitle(`Search`) 16 + store.nav.setTitle(navIdx, `Search`) 17 17 } 18 18 }, [store, visible, name]) 19 19 const onComposePress = () => {
+5 -2
src/view/screens/Settings.tsx
··· 8 8 import {Link} from '../com/util/Link' 9 9 import {UserAvatar} from '../com/util/UserAvatar' 10 10 11 - export const Settings = observer(function Settings({visible}: ScreenParams) { 11 + export const Settings = observer(function Settings({ 12 + navIdx, 13 + visible, 14 + }: ScreenParams) { 12 15 const store = useStores() 13 16 14 17 useEffect(() => { 15 18 if (!visible) { 16 19 return 17 20 } 18 - store.nav.setTitle('Settings') 21 + store.nav.setTitle(navIdx, 'Settings') 19 22 }, [visible, store]) 20 23 21 24 const onPressSignout = () => {
+4 -1
src/view/shell/mobile/index.tsx
··· 268 268 <GestureDetector gesture={swipeGesture}> 269 269 <ScreenContainer style={styles.screenContainer}> 270 270 {screenRenderDesc.screens.map( 271 - ({Com, params, key, current, previous}) => { 271 + ({Com, navIdx, params, key, current, previous}) => { 272 272 return ( 273 273 <Screen 274 274 key={key} ··· 293 293 ]}> 294 294 <Com 295 295 params={params} 296 + navIdx={navIdx} 296 297 visible={current} 297 298 scrollElRef={current ? scrollElRef : undefined} 298 299 /> ··· 361 362 */ 362 363 type ScreenRenderDesc = MatchResult & { 363 364 key: string 365 + navIdx: [number, number] 364 366 current: boolean 365 367 previous: boolean 366 368 isNewTab: boolean ··· 388 390 hasNewTab = hasNewTab || tab.isNewTab 389 391 return Object.assign(matchRes, { 390 392 key: `t${tab.id}-s${screen.index}`, 393 + navIdx: [tab.id, screen.id], 391 394 current: isCurrent, 392 395 previous: isPrevious, 393 396 isNewTab: tab.isNewTab,