about summary refs log tree commit diff
path: root/src/lib/hooks
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/hooks')
-rw-r--r--src/lib/hooks/useEmail.ts32
-rw-r--r--src/lib/hooks/useHandleRef.ts39
-rw-r--r--src/lib/hooks/useMinimalShellTransform.ts20
-rw-r--r--src/lib/hooks/useOpenLink.ts24
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()
+}