diff options
Diffstat (limited to 'src/lib')
-rw-r--r-- | src/lib/api/feed/home.ts | 6 | ||||
-rw-r--r-- | src/lib/custom-animations/GestureActionView.tsx | 15 | ||||
-rw-r--r-- | src/lib/hooks/useIntentHandler.ts | 18 | ||||
-rw-r--r-- | src/lib/hooks/useOTAUpdates.ts | 136 | ||||
-rw-r--r-- | src/lib/hooks/useOTAUpdates.web.ts | 9 | ||||
-rw-r--r-- | src/lib/notifications/notifications.e2e.ts | 6 | ||||
-rw-r--r-- | src/lib/statsig/gates.ts | 1 | ||||
-rw-r--r-- | src/lib/statsig/statsig.tsx | 3 | ||||
-rw-r--r-- | src/lib/strings/mention-manip.ts | 2 |
9 files changed, 154 insertions, 42 deletions
diff --git a/src/lib/api/feed/home.ts b/src/lib/api/feed/home.ts index e6bc45bea..7a0d72d91 100644 --- a/src/lib/api/feed/home.ts +++ b/src/lib/api/feed/home.ts @@ -1,9 +1,9 @@ -import {AppBskyFeedDefs, BskyAgent} from '@atproto/api' +import {type AppBskyFeedDefs, type BskyAgent} from '@atproto/api' import {PROD_DEFAULT_FEED} from '#/lib/constants' import {CustomFeedAPI} from './custom' import {FollowingFeedAPI} from './following' -import {FeedAPI, FeedAPIResponse} from './types' +import {type FeedAPI, type FeedAPIResponse} from './types' // HACK // the feed API does not include any facilities for passing down @@ -93,7 +93,7 @@ export class HomeFeedAPI implements FeedAPI { } } - if (this.usingDiscover) { + if (this.usingDiscover && !__DEV__) { const res = await this.discover.fetch({cursor, limit}) returnCursor = res.cursor posts = posts.concat(res.feed) diff --git a/src/lib/custom-animations/GestureActionView.tsx b/src/lib/custom-animations/GestureActionView.tsx index ba6952a81..e7fba570b 100644 --- a/src/lib/custom-animations/GestureActionView.tsx +++ b/src/lib/custom-animations/GestureActionView.tsx @@ -1,5 +1,5 @@ import React from 'react' -import {ColorValue, Dimensions, StyleSheet, View} from 'react-native' +import {type ColorValue, Dimensions, StyleSheet, View} from 'react-native' import {Gesture, GestureDetector} from 'react-native-gesture-handler' import Animated, { clamp, @@ -114,11 +114,16 @@ export function GestureActionView({ }, ) + // NOTE(haileyok): + // Absurdly high value so it doesn't interfere with the pan gestures above (i.e., scroll) + // reanimated doesn't offer great support for disabling y/x axes :/ + const effectivelyDisabledOffset = 200 const panGesture = Gesture.Pan() - .activeOffsetX([-10, 10]) - // Absurdly high value so it doesn't interfere with the pan gestures above (i.e., scroll) - // reanimated doesn't offer great support for disabling y/x axes :/ - .activeOffsetY([-200, 200]) + .activeOffsetX([ + actions.leftFirst ? -10 : -effectivelyDisabledOffset, + actions.rightFirst ? 10 : effectivelyDisabledOffset, + ]) + .activeOffsetY([-effectivelyDisabledOffset, effectivelyDisabledOffset]) .onStart(() => { 'worklet' isActive.set(true) diff --git a/src/lib/hooks/useIntentHandler.ts b/src/lib/hooks/useIntentHandler.ts index 6b1083aa4..c359b2bd6 100644 --- a/src/lib/hooks/useIntentHandler.ts +++ b/src/lib/hooks/useIntentHandler.ts @@ -1,8 +1,9 @@ import React from 'react' +import {Alert} from 'react-native' import * as Linking from 'expo-linking' import {useOpenComposer} from '#/lib/hooks/useOpenComposer' -import {logEvent} from '#/lib/statsig/statsig' +import {logger} from '#/logger' import {isNative} from '#/platform/detection' import {useSession} from '#/state/session' import {useCloseAllActiveElements} from '#/state/util' @@ -12,8 +13,9 @@ import { } from '#/components/ageAssurance/AgeAssuranceRedirectDialog' import {useIntentDialogs} from '#/components/intents/IntentDialogs' import {Referrer} from '../../../modules/expo-bluesky-swiss-army' +import {useApplyPullRequestOTAUpdate} from './useOTAUpdates' -type IntentType = 'compose' | 'verify-email' | 'age-assurance' +type IntentType = 'compose' | 'verify-email' | 'age-assurance' | 'apply-ota' const VALID_IMAGE_REGEX = /^[\w.:\-_/]+\|\d+(\.\d+)?\|\d+(\.\d+)?$/ @@ -27,12 +29,13 @@ export function useIntentHandler() { const ageAssuranceRedirectDialogControl = useAgeAssuranceRedirectDialogControl() const {currentAccount} = useSession() + const {tryApplyUpdate} = useApplyPullRequestOTAUpdate() React.useEffect(() => { const handleIncomingURL = (url: string) => { const referrerInfo = Referrer.getReferrerInfo() if (referrerInfo && referrerInfo.hostname !== 'bsky.app') { - logEvent('deepLink:referrerReceived', { + logger.metric('deepLink:referrerReceived', { to: url, referrer: referrerInfo?.referrer, hostname: referrerInfo?.hostname, @@ -92,6 +95,14 @@ export function useIntentHandler() { } return } + case 'apply-ota': { + const channel = params.get('channel') + if (!channel) { + Alert.alert('Error', 'No channel provided to look for.') + } else { + tryApplyUpdate(channel) + } + } default: { return } @@ -111,6 +122,7 @@ export function useIntentHandler() { verifyEmailIntent, ageAssuranceRedirectDialogControl, currentAccount, + tryApplyUpdate, ]) } diff --git a/src/lib/hooks/useOTAUpdates.ts b/src/lib/hooks/useOTAUpdates.ts index 731406dce..864d5d697 100644 --- a/src/lib/hooks/useOTAUpdates.ts +++ b/src/lib/hooks/useOTAUpdates.ts @@ -1,5 +1,5 @@ import React from 'react' -import {Alert, AppState, AppStateStatus} from 'react-native' +import {Alert, AppState, type AppStateStatus} from 'react-native' import {nativeBuildVersion} from 'expo-application' import { checkForUpdateAsync, @@ -29,6 +29,98 @@ async function setExtraParams() { ) } +async function setExtraParamsPullRequest(channel: string) { + await setExtraParamAsync( + isIOS ? 'ios-build-number' : 'android-build-number', + // Hilariously, `buildVersion` is not actually a string on Android even though the TS type says it is. + // This just ensures it gets passed as a string + `${nativeBuildVersion}`, + ) + await setExtraParamAsync('channel', channel) +} + +async function updateTestflight() { + await setExtraParams() + + const res = await checkForUpdateAsync() + if (res.isAvailable) { + await fetchUpdateAsync() + Alert.alert( + 'Update Available', + 'A new version of the app is available. Relaunch now?', + [ + { + text: 'No', + style: 'cancel', + }, + { + text: 'Relaunch', + style: 'default', + onPress: async () => { + await reloadAsync() + }, + }, + ], + ) + } +} + +export function useApplyPullRequestOTAUpdate() { + const {currentlyRunning} = useUpdates() + const [pending, setPending] = React.useState(false) + const currentChannel = currentlyRunning?.channel + const isCurrentlyRunningPullRequestDeployment = + currentChannel?.startsWith('pull-request') + + const tryApplyUpdate = async (channel: string) => { + setPending(true) + await setExtraParamsPullRequest(channel) + const res = await checkForUpdateAsync() + if (res.isAvailable) { + Alert.alert( + 'Deployment Available', + `A deployment of ${channel} is availalble. Applying this deployment may result in a bricked installation, in which case you will need to reinstall the app and may lose local data. Are you sure you want to proceed?`, + [ + { + text: 'No', + style: 'cancel', + }, + { + text: 'Relaunch', + style: 'default', + onPress: async () => { + await fetchUpdateAsync() + await reloadAsync() + }, + }, + ], + ) + } else { + Alert.alert( + 'No Deployment Available', + `No new deployments of ${channel} are currently available for your current native build.`, + ) + } + setPending(false) + } + + const revertToEmbedded = async () => { + try { + await updateTestflight() + } catch (e: any) { + logger.error('Internal OTA Update Error', {error: `${e}`}) + } + } + + return { + tryApplyUpdate, + revertToEmbedded, + isCurrentlyRunningPullRequestDeployment, + currentChannel, + pending, + } +} + export function useOTAUpdates() { const shouldReceiveUpdates = isEnabled && !__DEV__ @@ -36,7 +128,8 @@ export function useOTAUpdates() { const lastMinimize = React.useRef(0) const ranInitialCheck = React.useRef(false) const timeout = React.useRef<NodeJS.Timeout>() - const {isUpdatePending} = useUpdates() + const {currentlyRunning, isUpdatePending} = useUpdates() + const currentChannel = currentlyRunning?.channel const setCheckTimeout = React.useCallback(() => { timeout.current = setTimeout(async () => { @@ -60,36 +153,18 @@ export function useOTAUpdates() { const onIsTestFlight = React.useCallback(async () => { try { - await setExtraParams() - - const res = await checkForUpdateAsync() - if (res.isAvailable) { - await fetchUpdateAsync() - - Alert.alert( - 'Update Available', - 'A new version of the app is available. Relaunch now?', - [ - { - text: 'No', - style: 'cancel', - }, - { - text: 'Relaunch', - style: 'default', - onPress: async () => { - await reloadAsync() - }, - }, - ], - ) - } + await updateTestflight() } catch (e: any) { logger.error('Internal OTA Update Error', {error: `${e}`}) } }, []) React.useEffect(() => { + // We don't need to check anything if the current update is a PR update + if (currentChannel?.startsWith('pull-request')) { + return + } + // We use this setTimeout to allow Statsig to initialize before we check for an update // For Testflight users, we can prompt the user to update immediately whenever there's an available update. This // is suspect however with the Apple App Store guidelines, so we don't want to prompt production users to update @@ -103,12 +178,15 @@ export function useOTAUpdates() { setCheckTimeout() ranInitialCheck.current = true - }, [onIsTestFlight, setCheckTimeout, shouldReceiveUpdates]) + }, [onIsTestFlight, currentChannel, setCheckTimeout, shouldReceiveUpdates]) // After the app has been minimized for 15 minutes, we want to either A. install an update if one has become available // or B check for an update again. React.useEffect(() => { - if (!isEnabled) return + // We also don't start this timeout if the user is on a pull request update + if (!isEnabled || currentChannel?.startsWith('pull-request')) { + return + } const subscription = AppState.addEventListener( 'change', @@ -138,5 +216,5 @@ export function useOTAUpdates() { clearTimeout(timeout.current) subscription.remove() } - }, [isUpdatePending, setCheckTimeout]) + }, [isUpdatePending, currentChannel, setCheckTimeout]) } diff --git a/src/lib/hooks/useOTAUpdates.web.ts b/src/lib/hooks/useOTAUpdates.web.ts index 1baf4894e..2783a04dd 100644 --- a/src/lib/hooks/useOTAUpdates.web.ts +++ b/src/lib/hooks/useOTAUpdates.web.ts @@ -1 +1,10 @@ export function useOTAUpdates() {} +export function useApplyPullRequestOTAUpdate() { + return { + tryApplyUpdate: () => {}, + revertToEmbedded: () => {}, + isCurrentlyRunningPullRequestDeployment: false, + currentChannel: 'web-build', + pending: false, + } +} diff --git a/src/lib/notifications/notifications.e2e.ts b/src/lib/notifications/notifications.e2e.ts index 0586ac1bf..1a9d861b7 100644 --- a/src/lib/notifications/notifications.e2e.ts +++ b/src/lib/notifications/notifications.e2e.ts @@ -1,3 +1,5 @@ +import {useCallback} from 'react' + export function useNotificationsRegistration() {} export function useRequestNotificationsPermission() { @@ -6,6 +8,10 @@ export function useRequestNotificationsPermission() { ) => {} } +export function useGetAndRegisterPushToken() { + return useCallback(async ({}: {} = {}) => {}, []) +} + export async function decrementBadgeCount(_by: number) {} export async function resetBadgeCount() {} diff --git a/src/lib/statsig/gates.ts b/src/lib/statsig/gates.ts index efd7d605a..3b1106480 100644 --- a/src/lib/statsig/gates.ts +++ b/src/lib/statsig/gates.ts @@ -1,6 +1,5 @@ export type Gate = // Keep this alphabetic please. - | 'age_assurance' | 'alt_share_icon' | 'debug_show_feedcontext' | 'debug_subscriptions' diff --git a/src/lib/statsig/statsig.tsx b/src/lib/statsig/statsig.tsx index f509f2980..f2d3ffca9 100644 --- a/src/lib/statsig/statsig.tsx +++ b/src/lib/statsig/statsig.tsx @@ -8,6 +8,7 @@ import {logger} from '#/logger' import {type MetricEvents} from '#/logger/metrics' import {isWeb} from '#/platform/detection' import * as persisted from '#/state/persisted' +import packageDotJson from '../../../package.json' import {useSession} from '../../state/session' import {timeout} from '../async/timeout' import {useNonReactiveCallback} from '../hooks/useNonReactiveCallback' @@ -25,6 +26,7 @@ type StatsigUser = { // This is the place where we can add our own stuff. // Fields here have to be non-optional to be visible in the UI. platform: 'ios' | 'android' | 'web' + appVersion: string bundleIdentifier: string bundleDate: number refSrc: string @@ -210,6 +212,7 @@ function toStatsigUser(did: string | undefined): StatsigUser { refSrc, refUrl, platform: Platform.OS as 'ios' | 'android' | 'web', + appVersion: packageDotJson.version, bundleIdentifier: BUNDLE_IDENTIFIER, bundleDate: BUNDLE_DATE, appLanguage: languagePrefs.appLanguage, diff --git a/src/lib/strings/mention-manip.ts b/src/lib/strings/mention-manip.ts index 1f7cbe434..7b52f745b 100644 --- a/src/lib/strings/mention-manip.ts +++ b/src/lib/strings/mention-manip.ts @@ -7,7 +7,7 @@ export function getMentionAt( text: string, cursorPos: number, ): FoundMention | undefined { - let re = /(^|\s)@([a-z0-9.]*)/gi + let re = /(^|\s)@([a-z0-9.-]*)/gi let match while ((match = re.exec(text))) { const spaceOffset = match[1].length |