diff options
Diffstat (limited to 'src/lib/hooks')
-rw-r--r-- | src/lib/hooks/useEmail.ts | 32 | ||||
-rw-r--r-- | src/lib/hooks/useHandleRef.ts | 39 | ||||
-rw-r--r-- | src/lib/hooks/useMinimalShellTransform.ts | 20 | ||||
-rw-r--r-- | src/lib/hooks/useOpenLink.ts | 24 |
4 files changed, 105 insertions, 10 deletions
diff --git a/src/lib/hooks/useEmail.ts b/src/lib/hooks/useEmail.ts new file mode 100644 index 000000000..a8f4c6ad2 --- /dev/null +++ b/src/lib/hooks/useEmail.ts @@ -0,0 +1,32 @@ +import {useServiceConfigQuery} from '#/state/queries/email-verification-required' +import {useProfileQuery} from '#/state/queries/profile' +import {useSession} from '#/state/session' +import {BSKY_SERVICE} from '../constants' +import {getHostnameFromUrl} from '../strings/url-helpers' + +export function useEmail() { + const {currentAccount} = useSession() + + const {data: serviceConfig} = useServiceConfigQuery() + const {data: profile} = useProfileQuery({did: currentAccount?.did}) + + const checkEmailConfirmed = !!serviceConfig?.checkEmailConfirmed + + // Date set for 11 AM PST on the 18th of November + const isNewEnough = + !!profile?.createdAt && + Date.parse(profile.createdAt) >= Date.parse('2024-11-18T19:00:00.000Z') + + const isSelfHost = + currentAccount && + getHostnameFromUrl(currentAccount.service) !== + getHostnameFromUrl(BSKY_SERVICE) + + const needsEmailVerification = + !isSelfHost && + checkEmailConfirmed && + !currentAccount?.emailConfirmed && + isNewEnough + + return {needsEmailVerification} +} diff --git a/src/lib/hooks/useHandleRef.ts b/src/lib/hooks/useHandleRef.ts new file mode 100644 index 000000000..167ba270b --- /dev/null +++ b/src/lib/hooks/useHandleRef.ts @@ -0,0 +1,39 @@ +import {useState} from 'react' +import {AnimatedRef, measure, MeasuredDimensions} from 'react-native-reanimated' + +export type HandleRef = { + (node: any): void + current: null | number +} + +// This is a lighterweight alternative to `useAnimatedRef()` for imperative UI thread actions. +// Render it like <View ref={ref} />, then pass `ref.current` to `measureHandle()` and such. +export function useHandleRef(): HandleRef { + return useState(() => { + const ref = (node: any) => { + if (node) { + ref.current = + node._nativeTag ?? + node.__nativeTag ?? + node.canonical?.nativeTag ?? + null + } else { + ref.current = null + } + } + ref.current = null + return ref + })[0] as HandleRef +} + +// When using this version, you need to read ref.current on the JS thread, and pass it to UI. +export function measureHandle( + current: number | null, +): MeasuredDimensions | null { + 'worklet' + if (current !== null) { + return measure((() => current) as AnimatedRef<any>) + } else { + return null + } +} diff --git a/src/lib/hooks/useMinimalShellTransform.ts b/src/lib/hooks/useMinimalShellTransform.ts index 678776755..6f16fa0f9 100644 --- a/src/lib/hooks/useMinimalShellTransform.ts +++ b/src/lib/hooks/useMinimalShellTransform.ts @@ -10,15 +10,16 @@ export function useMinimalShellHeaderTransform() { const {headerHeight} = useShellLayout() const headerTransform = useAnimatedStyle(() => { + const headerModeValue = headerMode.get() return { - pointerEvents: headerMode.value === 0 ? 'auto' : 'none', - opacity: Math.pow(1 - headerMode.value, 2), + pointerEvents: headerModeValue === 0 ? 'auto' : 'none', + opacity: Math.pow(1 - headerModeValue, 2), transform: [ { translateY: interpolate( - headerMode.value, + headerModeValue, [0, 1], - [0, -headerHeight.value], + [0, -headerHeight.get()], ), }, ], @@ -33,15 +34,16 @@ export function useMinimalShellFooterTransform() { const {footerHeight} = useShellLayout() const footerTransform = useAnimatedStyle(() => { + const footerModeValue = footerMode.get() return { - pointerEvents: footerMode.value === 0 ? 'auto' : 'none', - opacity: Math.pow(1 - footerMode.value, 2), + pointerEvents: footerModeValue === 0 ? 'auto' : 'none', + opacity: Math.pow(1 - footerModeValue, 2), transform: [ { translateY: interpolate( - footerMode.value, + footerModeValue, [0, 1], - [0, footerHeight.value], + [0, footerHeight.get()], ), }, ], @@ -58,7 +60,7 @@ export function useMinimalShellFabTransform() { return { transform: [ { - translateY: interpolate(footerMode.value, [0, 1], [-44, 0]), + translateY: interpolate(footerMode.get(), [0, 1], [-44, 0]), }, ], } diff --git a/src/lib/hooks/useOpenLink.ts b/src/lib/hooks/useOpenLink.ts index 5b75695b8..727821670 100644 --- a/src/lib/hooks/useOpenLink.ts +++ b/src/lib/hooks/useOpenLink.ts @@ -4,12 +4,14 @@ import * as WebBrowser from 'expo-web-browser' import { createBskyAppAbsoluteUrl, + isBskyAppUrl, isBskyRSSUrl, isRelativeUrl, } from '#/lib/strings/url-helpers' import {isNative} from '#/platform/detection' import {useModalControls} from '#/state/modals' import {useInAppBrowser} from '#/state/preferences/in-app-browser' +import {useOptOutOfUtm} from '#/state/preferences/opt-out-of-utm' import {useTheme} from '#/alf' import {useSheetWrapper} from '#/components/Dialog/sheet-wrapper' @@ -18,6 +20,7 @@ export function useOpenLink() { const enabled = useInAppBrowser() const t = useTheme() const sheetWrapper = useSheetWrapper() + const optOutOfUtm = useOptOutOfUtm() const openLink = useCallback( async (url: string, override?: boolean) => { @@ -26,6 +29,9 @@ export function useOpenLink() { } if (isNative && !url.startsWith('mailto:')) { + if (!optOutOfUtm && !isBskyAppUrl(url) && url.startsWith('http')) { + url = addUtmSource(url) + } if (override === undefined && enabled === undefined) { openModal({ name: 'in-app-browser-consent', @@ -47,8 +53,24 @@ export function useOpenLink() { } Linking.openURL(url) }, - [enabled, openModal, t, sheetWrapper], + [enabled, openModal, t, sheetWrapper, optOutOfUtm], ) return openLink } + +function addUtmSource(url: string): string { + let parsedUrl + try { + parsedUrl = new URL(url) + } catch (e) { + return url + } + if (!parsedUrl.searchParams.has('utm_source')) { + parsedUrl.searchParams.set('utm_source', 'bluesky') + if (!parsedUrl.searchParams.has('utm_medium')) { + parsedUrl.searchParams.set('utm_medium', 'social') + } + } + return parsedUrl.toString() +} |