Merge branch 'main' into 3p-moderators
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
import React from 'react'
|
||||
|
||||
export const useDedupe = () => {
|
||||
const canDo = React.useRef(true)
|
||||
|
||||
return React.useRef((cb: () => unknown) => {
|
||||
if (canDo.current) {
|
||||
canDo.current = false
|
||||
setTimeout(() => {
|
||||
canDo.current = true
|
||||
}, 250)
|
||||
cb()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}).current
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
import React from 'react'
|
||||
import {useNavigation} from '@react-navigation/core'
|
||||
import {AllNavigatorParams, NavigationProp} from 'lib/routes/types'
|
||||
import type {NavigationAction} from '@react-navigation/routers'
|
||||
import {NavigationState} from '@react-navigation/native'
|
||||
import {useDedupe} from 'lib/hooks/useDedupe'
|
||||
|
||||
export type DebouncedNavigationProp = Pick<
|
||||
NavigationProp,
|
||||
| 'popToTop'
|
||||
| 'push'
|
||||
| 'navigate'
|
||||
| 'canGoBack'
|
||||
| 'replace'
|
||||
| 'dispatch'
|
||||
| 'goBack'
|
||||
| 'getState'
|
||||
>
|
||||
|
||||
export function useNavigationDeduped() {
|
||||
const navigation = useNavigation<NavigationProp>()
|
||||
const dedupe = useDedupe()
|
||||
|
||||
return React.useMemo(
|
||||
(): DebouncedNavigationProp => ({
|
||||
// Types from @react-navigation/routers/lib/typescript/src/StackRouter.ts
|
||||
push: <RouteName extends keyof AllNavigatorParams>(
|
||||
...args: undefined extends AllNavigatorParams[RouteName]
|
||||
?
|
||||
| [screen: RouteName]
|
||||
| [screen: RouteName, params: AllNavigatorParams[RouteName]]
|
||||
: [screen: RouteName, params: AllNavigatorParams[RouteName]]
|
||||
) => {
|
||||
dedupe(() => navigation.push(...args))
|
||||
},
|
||||
// Types from @react-navigation/core/src/types.tsx
|
||||
navigate: <RouteName extends keyof AllNavigatorParams>(
|
||||
...args: RouteName extends unknown
|
||||
? undefined extends AllNavigatorParams[RouteName]
|
||||
?
|
||||
| [screen: RouteName]
|
||||
| [screen: RouteName, params: AllNavigatorParams[RouteName]]
|
||||
: [screen: RouteName, params: AllNavigatorParams[RouteName]]
|
||||
: never
|
||||
) => {
|
||||
dedupe(() => navigation.navigate(...args))
|
||||
},
|
||||
// Types from @react-navigation/routers/lib/typescript/src/StackRouter.ts
|
||||
replace: <RouteName extends keyof AllNavigatorParams>(
|
||||
...args: undefined extends AllNavigatorParams[RouteName]
|
||||
?
|
||||
| [screen: RouteName]
|
||||
| [screen: RouteName, params: AllNavigatorParams[RouteName]]
|
||||
: [screen: RouteName, params: AllNavigatorParams[RouteName]]
|
||||
) => {
|
||||
dedupe(() => navigation.replace(...args))
|
||||
},
|
||||
dispatch: (
|
||||
action:
|
||||
| NavigationAction
|
||||
| ((state: NavigationState) => NavigationAction),
|
||||
) => {
|
||||
dedupe(() => navigation.dispatch(action))
|
||||
},
|
||||
popToTop: () => {
|
||||
dedupe(() => navigation.popToTop())
|
||||
},
|
||||
goBack: () => {
|
||||
dedupe(() => navigation.goBack())
|
||||
},
|
||||
canGoBack: () => {
|
||||
return navigation.canGoBack()
|
||||
},
|
||||
getState: () => {
|
||||
return navigation.getState()
|
||||
},
|
||||
}),
|
||||
[dedupe, navigation],
|
||||
)
|
||||
}
|
||||
@@ -2,25 +2,9 @@ import * as Updates from 'expo-updates'
|
||||
import {useCallback, useEffect} from 'react'
|
||||
import {AppState} from 'react-native'
|
||||
import {logger} from '#/logger'
|
||||
import {useModalControls} from '#/state/modals'
|
||||
import {t} from '@lingui/macro'
|
||||
|
||||
export function useOTAUpdate() {
|
||||
const {openModal} = useModalControls()
|
||||
|
||||
// HELPER FUNCTIONS
|
||||
const showUpdatePopup = useCallback(() => {
|
||||
openModal({
|
||||
name: 'confirm',
|
||||
title: t`Update Available`,
|
||||
message: t`A new version of the app is available. Please update to continue using the app.`,
|
||||
onPressConfirm: async () => {
|
||||
Updates.reloadAsync().catch(err => {
|
||||
throw err
|
||||
})
|
||||
},
|
||||
})
|
||||
}, [openModal])
|
||||
const checkForUpdate = useCallback(async () => {
|
||||
logger.debug('useOTAUpdate: Checking for update...')
|
||||
try {
|
||||
@@ -32,32 +16,26 @@ export function useOTAUpdate() {
|
||||
}
|
||||
// Otherwise fetch the update in the background, so even if the user rejects switching to latest version it will be done automatically on next relaunch.
|
||||
await Updates.fetchUpdateAsync()
|
||||
// show a popup modal
|
||||
showUpdatePopup()
|
||||
} catch (e) {
|
||||
logger.error('useOTAUpdate: Error while checking for update', {
|
||||
message: e,
|
||||
})
|
||||
}
|
||||
}, [showUpdatePopup])
|
||||
const updateEventListener = useCallback(
|
||||
(event: Updates.UpdateEvent) => {
|
||||
logger.debug('useOTAUpdate: Listening for update...')
|
||||
if (event.type === Updates.UpdateEventType.ERROR) {
|
||||
logger.error('useOTAUpdate: Error while listening for update', {
|
||||
message: event.message,
|
||||
})
|
||||
} else if (event.type === Updates.UpdateEventType.NO_UPDATE_AVAILABLE) {
|
||||
// Handle no update available
|
||||
// do nothing
|
||||
} else if (event.type === Updates.UpdateEventType.UPDATE_AVAILABLE) {
|
||||
// Handle update available
|
||||
// open modal, ask for user confirmation, and reload the app
|
||||
showUpdatePopup()
|
||||
}
|
||||
},
|
||||
[showUpdatePopup],
|
||||
)
|
||||
}, [])
|
||||
const updateEventListener = useCallback((event: Updates.UpdateEvent) => {
|
||||
logger.debug('useOTAUpdate: Listening for update...')
|
||||
if (event.type === Updates.UpdateEventType.ERROR) {
|
||||
logger.error('useOTAUpdate: Error while listening for update', {
|
||||
message: event.message,
|
||||
})
|
||||
} else if (event.type === Updates.UpdateEventType.NO_UPDATE_AVAILABLE) {
|
||||
// Handle no update available
|
||||
// do nothing
|
||||
} else if (event.type === Updates.UpdateEventType.UPDATE_AVAILABLE) {
|
||||
// Handle update available
|
||||
// open modal, ask for user confirmation, and reload the app
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
// ADD EVENT LISTENERS
|
||||
|
||||
@@ -13,6 +13,7 @@ interface ReportOptions {
|
||||
account: ReportOption[]
|
||||
post: ReportOption[]
|
||||
list: ReportOption[]
|
||||
feedgen: ReportOption[]
|
||||
other: ReportOption[]
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -12,9 +12,9 @@ import {Share} from 'react-native'
|
||||
*/
|
||||
export async function shareUrl(url: string) {
|
||||
if (isAndroid) {
|
||||
Share.share({message: url})
|
||||
await Share.share({message: url})
|
||||
} else if (isIOS) {
|
||||
Share.share({url})
|
||||
await Share.share({url})
|
||||
} else {
|
||||
// React Native Share is not supported by web. Web Share API
|
||||
// has increasing but not full support, so default to clipboard
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
import React from 'react'
|
||||
import {
|
||||
Statsig,
|
||||
StatsigProvider,
|
||||
useGate as useStatsigGate,
|
||||
} from 'statsig-react'
|
||||
import {useSession} from '../../state/session'
|
||||
import {sha256} from 'js-sha256'
|
||||
|
||||
const statsigOptions = {
|
||||
environment: {
|
||||
tier: process.env.NODE_ENV === 'development' ? 'development' : 'production',
|
||||
},
|
||||
// Don't block on waiting for network. The fetched config will kick in on next load.
|
||||
// This ensures the UI is always consistent and doesn't update mid-session.
|
||||
// Note this makes cold load (no local storage) and private mode return `false` for all gates.
|
||||
initTimeoutMs: 1,
|
||||
}
|
||||
|
||||
export function logEvent(
|
||||
eventName: string,
|
||||
value?: string | number | null,
|
||||
metadata?: Record<string, string> | null,
|
||||
) {
|
||||
Statsig.logEvent(eventName, value, metadata)
|
||||
}
|
||||
|
||||
export function useGate(gateName: string) {
|
||||
const {isLoading, value} = useStatsigGate(gateName)
|
||||
if (isLoading) {
|
||||
// This should not happen because of waitForInitialization={true}.
|
||||
console.error('Did not expected isLoading to ever be true.')
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
function toStatsigUser(did: string | undefined) {
|
||||
let userID: string | undefined
|
||||
if (did) {
|
||||
userID = sha256(did)
|
||||
}
|
||||
return {userID}
|
||||
}
|
||||
|
||||
export function Provider({children}: {children: React.ReactNode}) {
|
||||
const {currentAccount} = useSession()
|
||||
const currentStatsigUser = React.useMemo(
|
||||
() => toStatsigUser(currentAccount?.did),
|
||||
[currentAccount?.did],
|
||||
)
|
||||
|
||||
React.useEffect(() => {
|
||||
function refresh() {
|
||||
// Intentionally refetching the config using the JS SDK rather than React SDK
|
||||
// so that the new config is stored in cache but isn't used during this session.
|
||||
// It will kick in for the next reload.
|
||||
Statsig.updateUser(currentStatsigUser)
|
||||
}
|
||||
const id = setInterval(refresh, 3 * 60e3 /* 3 min */)
|
||||
return () => clearInterval(id)
|
||||
}, [currentStatsigUser])
|
||||
|
||||
return (
|
||||
<StatsigProvider
|
||||
sdkKey="client-SXJakO39w9vIhl3D44u8UupyzFl4oZ2qPIkjwcvuPsV"
|
||||
mountKey={currentStatsigUser.userID}
|
||||
user={currentStatsigUser}
|
||||
// This isn't really blocking due to short initTimeoutMs above.
|
||||
// However, it ensures `isLoading` is always `false`.
|
||||
waitForInitialization={true}
|
||||
options={statsigOptions}>
|
||||
{children}
|
||||
</StatsigProvider>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user