Shell behaviors update (react-query refactor) (#1915)
* Move tick-every-minute into a hook/context * Move soft-reset event out of the shell model * Update soft-reset listener on new search page * Implement session-loaded and session-dropped events * Update analytics and push-notifications to use new session system
This commit is contained in:
parent
f23e9978d8
commit
6616b2bff0
20 changed files with 186 additions and 136 deletions
38
src/state/events.ts
Normal file
38
src/state/events.ts
Normal file
|
@ -0,0 +1,38 @@
|
|||
import EventEmitter from 'eventemitter3'
|
||||
import {BskyAgent} from '@atproto/api'
|
||||
import {SessionAccount} from './session'
|
||||
|
||||
type UnlistenFn = () => void
|
||||
|
||||
const emitter = new EventEmitter()
|
||||
|
||||
// a "soft reset" typically means scrolling to top and loading latest
|
||||
// but it can depend on the screen
|
||||
export function emitSoftReset() {
|
||||
emitter.emit('soft-reset')
|
||||
}
|
||||
export function listenSoftReset(fn: () => void): UnlistenFn {
|
||||
emitter.on('soft-reset', fn)
|
||||
return () => emitter.off('soft-reset', fn)
|
||||
}
|
||||
|
||||
export function emitSessionLoaded(
|
||||
sessionAccount: SessionAccount,
|
||||
agent: BskyAgent,
|
||||
) {
|
||||
emitter.emit('session-loaded', sessionAccount, agent)
|
||||
}
|
||||
export function listenSessionLoaded(
|
||||
fn: (sessionAccount: SessionAccount, agent: BskyAgent) => void,
|
||||
): UnlistenFn {
|
||||
emitter.on('session-loaded', fn)
|
||||
return () => emitter.off('session-loaded', fn)
|
||||
}
|
||||
|
||||
export function emitSessionDropped() {
|
||||
emitter.emit('session-dropped')
|
||||
}
|
||||
export function listenSessionDropped(fn: () => void): UnlistenFn {
|
||||
emitter.on('session-dropped', fn)
|
||||
return () => emitter.off('session-dropped', fn)
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import {AppBskyActorDefs} from '@atproto/api'
|
||||
import {RootStoreModel} from '../root-store'
|
||||
import {makeAutoObservable, runInAction} from 'mobx'
|
||||
import {makeAutoObservable} from 'mobx'
|
||||
import {
|
||||
shouldRequestEmailConfirmation,
|
||||
setEmailConfirmationRequested,
|
||||
|
@ -40,14 +40,12 @@ export class ImagesLightbox implements LightboxModel {
|
|||
export class ShellUiModel {
|
||||
isLightboxActive = false
|
||||
activeLightbox: ProfileImageLightbox | ImagesLightbox | null = null
|
||||
tickEveryMinute = Date.now()
|
||||
|
||||
constructor(public rootStore: RootStoreModel) {
|
||||
makeAutoObservable(this, {
|
||||
rootStore: false,
|
||||
})
|
||||
|
||||
this.setupClock()
|
||||
this.setupLoginModals()
|
||||
}
|
||||
|
||||
|
@ -83,14 +81,6 @@ export class ShellUiModel {
|
|||
this.activeLightbox = null
|
||||
}
|
||||
|
||||
setupClock() {
|
||||
setInterval(() => {
|
||||
runInAction(() => {
|
||||
this.tickEveryMinute = Date.now()
|
||||
})
|
||||
}, 60_000)
|
||||
}
|
||||
|
||||
setupLoginModals() {
|
||||
this.rootStore.onSessionReady(() => {
|
||||
if (shouldRequestEmailConfirmation(this.rootStore.session)) {
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import React from 'react'
|
||||
import {DeviceEventEmitter} from 'react-native'
|
||||
import {BskyAgent, AtpPersistSessionHandler} from '@atproto/api'
|
||||
|
||||
import {networkRetry} from '#/lib/async/retry'
|
||||
|
@ -7,6 +6,7 @@ import {logger} from '#/logger'
|
|||
import * as persisted from '#/state/persisted'
|
||||
import {PUBLIC_BSKY_AGENT} from '#/state/queries'
|
||||
import {IS_PROD} from '#/lib/constants'
|
||||
import {emitSessionLoaded, emitSessionDropped} from '../events'
|
||||
|
||||
export type SessionAccount = persisted.PersistedAccount
|
||||
|
||||
|
@ -98,7 +98,9 @@ function createPersistSessionHandler(
|
|||
logger.DebugContext.session,
|
||||
)
|
||||
|
||||
if (expired) DeviceEventEmitter.emit('session-dropped')
|
||||
if (expired) {
|
||||
emitSessionDropped()
|
||||
}
|
||||
|
||||
persistSessionCallback({
|
||||
expired,
|
||||
|
@ -180,6 +182,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
|||
|
||||
setState(s => ({...s, agent}))
|
||||
upsertAccount(account)
|
||||
emitSessionLoaded(account, agent)
|
||||
|
||||
logger.debug(
|
||||
`session: created account`,
|
||||
|
@ -230,6 +233,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
|||
|
||||
setState(s => ({...s, agent}))
|
||||
upsertAccount(account)
|
||||
emitSessionLoaded(account, agent)
|
||||
|
||||
logger.debug(
|
||||
`session: logged in`,
|
||||
|
@ -291,6 +295,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
|||
|
||||
setState(s => ({...s, agent}))
|
||||
upsertAccount(account)
|
||||
emitSessionLoaded(account, agent)
|
||||
},
|
||||
[upsertAccount],
|
||||
)
|
||||
|
|
|
@ -6,6 +6,7 @@ import {Provider as MinimalModeProvider} from './minimal-mode'
|
|||
import {Provider as ColorModeProvider} from './color-mode'
|
||||
import {Provider as OnboardingProvider} from './onboarding'
|
||||
import {Provider as ComposerProvider} from './composer'
|
||||
import {Provider as TickEveryMinuteProvider} from './tick-every-minute'
|
||||
|
||||
export {useIsDrawerOpen, useSetDrawerOpen} from './drawer-open'
|
||||
export {
|
||||
|
@ -15,6 +16,8 @@ export {
|
|||
export {useMinimalShellMode, useSetMinimalShellMode} from './minimal-mode'
|
||||
export {useColorMode, useSetColorMode} from './color-mode'
|
||||
export {useOnboardingState, useOnboardingDispatch} from './onboarding'
|
||||
export {useComposerState, useComposerControls} from './composer'
|
||||
export {useTickEveryMinute} from './tick-every-minute'
|
||||
|
||||
export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||
return (
|
||||
|
@ -24,7 +27,9 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
|||
<MinimalModeProvider>
|
||||
<ColorModeProvider>
|
||||
<OnboardingProvider>
|
||||
<ComposerProvider>{children}</ComposerProvider>
|
||||
<ComposerProvider>
|
||||
<TickEveryMinuteProvider>{children}</TickEveryMinuteProvider>
|
||||
</ComposerProvider>
|
||||
</OnboardingProvider>
|
||||
</ColorModeProvider>
|
||||
</MinimalModeProvider>
|
||||
|
|
|
@ -1,14 +1,24 @@
|
|||
import * as persisted from '#/state/persisted'
|
||||
import {SessionModel} from '../models/session'
|
||||
import {toHashCode} from 'lib/strings/helpers'
|
||||
import {isOnboardingActive} from './onboarding'
|
||||
import {SessionAccount} from '../session'
|
||||
import {listenSessionLoaded} from '../events'
|
||||
import {unstable__openModal} from '../modals'
|
||||
|
||||
export function shouldRequestEmailConfirmation(session: SessionModel) {
|
||||
const sess = session.currentSession
|
||||
if (!sess) {
|
||||
export function init() {
|
||||
listenSessionLoaded(account => {
|
||||
if (shouldRequestEmailConfirmation(account)) {
|
||||
unstable__openModal({name: 'verify-email', showReminder: true})
|
||||
setEmailConfirmationRequested()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function shouldRequestEmailConfirmation(account: SessionAccount) {
|
||||
if (!account) {
|
||||
return false
|
||||
}
|
||||
if (sess.emailConfirmed) {
|
||||
if (account.emailConfirmed) {
|
||||
return false
|
||||
}
|
||||
if (isOnboardingActive()) {
|
||||
|
@ -22,7 +32,7 @@ export function shouldRequestEmailConfirmation(session: SessionModel) {
|
|||
// shard the users into 2 day of the week buckets
|
||||
// (this is to avoid a sudden influx of email updates when
|
||||
// this feature rolls out)
|
||||
const code = toHashCode(sess.did) % 7
|
||||
const code = toHashCode(account.did) % 7
|
||||
if (code !== today.getDay() && code !== (today.getDay() + 1) % 7) {
|
||||
return false
|
||||
}
|
||||
|
|
20
src/state/shell/tick-every-minute.tsx
Normal file
20
src/state/shell/tick-every-minute.tsx
Normal file
|
@ -0,0 +1,20 @@
|
|||
import React from 'react'
|
||||
|
||||
type StateContext = number
|
||||
|
||||
const stateContext = React.createContext<StateContext>(0)
|
||||
|
||||
export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||
const [tick, setTick] = React.useState(Date.now())
|
||||
React.useEffect(() => {
|
||||
const i = setInterval(() => {
|
||||
setTick(Date.now())
|
||||
}, 60_000)
|
||||
return () => clearInterval(i)
|
||||
}, [])
|
||||
return <stateContext.Provider value={tick}>{children}</stateContext.Provider>
|
||||
}
|
||||
|
||||
export function useTickEveryMinute() {
|
||||
return React.useContext(stateContext)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue