diff --git a/package.json b/package.json
index 01955b7905..d4526287d5 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "bsky.app",
- "version": "1.105.0",
+ "version": "1.106.0",
"private": true,
"engines": {
"node": ">=20"
diff --git a/src/components/VideoPostCard.tsx b/src/components/VideoPostCard.tsx
index c28adad8b3..6d1cf73f7e 100644
--- a/src/components/VideoPostCard.tsx
+++ b/src/components/VideoPostCard.tsx
@@ -3,11 +3,11 @@ import {View} from 'react-native'
import {Image} from 'expo-image'
import {LinearGradient} from 'expo-linear-gradient'
import {
- AppBskyActorDefs,
+ type AppBskyActorDefs,
AppBskyEmbedVideo,
- AppBskyFeedDefs,
+ type AppBskyFeedDefs,
AppBskyFeedPost,
- ModerationDecision,
+ type ModerationDecision,
} from '@atproto/api'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
@@ -15,7 +15,7 @@ import {useLingui} from '@lingui/react'
import {sanitizeHandle} from '#/lib/strings/handles'
import {formatCount} from '#/view/com/util/numeric/format'
import {UserAvatar} from '#/view/com/util/UserAvatar'
-import {VideoFeedSourceContext} from '#/screens/VideoFeed/types'
+import {type VideoFeedSourceContext} from '#/screens/VideoFeed/types'
import {atoms as a, useTheme} from '#/alf'
import {BLUE_HUE} from '#/alf/util/colorGeneration'
import {select} from '#/alf/util/themeSelector'
@@ -390,6 +390,7 @@ export function CompactVideoPostCard({
if (!AppBskyEmbedVideo.isView(embed)) return null
const likeCount = post?.likeCount ?? 0
+ const showLikeCount = false
const {thumbnail} = embed
const black = getBlackColor(t)
@@ -475,47 +476,51 @@ export function CompactVideoPostCard({
/>
-
+
+ style={[a.relative, a.rounded_full, {width: 24, height: 24}]}>
-
-
+ {showLikeCount && (
- {likeCount > 0 && (
-
-
-
- {formatCount(i18n, likeCount)}
-
-
- )}
+ style={[
+ a.absolute,
+ a.inset_0,
+ a.pt_2xl,
+ {
+ top: 'auto',
+ },
+ ]}>
+
+
+
+ {likeCount > 0 && (
+
+
+
+ {formatCount(i18n, likeCount)}
+
+
+ )}
+
-
+ )}
diff --git a/src/components/dialogs/EmailDialog/data/useAccountEmailState.ts b/src/components/dialogs/EmailDialog/data/useAccountEmailState.ts
index 377411107a..f25369f8db 100644
--- a/src/components/dialogs/EmailDialog/data/useAccountEmailState.ts
+++ b/src/components/dialogs/EmailDialog/data/useAccountEmailState.ts
@@ -1,7 +1,7 @@
-import {useCallback, useEffect, useState} from 'react'
-import {useQuery, useQueryClient} from '@tanstack/react-query'
+import {useEffect, useMemo, useState} from 'react'
+import {useQuery} from '@tanstack/react-query'
-import {useAgent} from '#/state/session'
+import {useAgent, useSessionApi} from '#/state/session'
import {emitEmailVerified} from '#/components/dialogs/EmailDialog/events'
export type AccountEmailState = {
@@ -11,57 +11,36 @@ export type AccountEmailState = {
export const accountEmailStateQueryKey = ['accountEmailState'] as const
-export function useInvalidateAccountEmailState() {
- const qc = useQueryClient()
-
- return useCallback(() => {
- return qc.invalidateQueries({
- queryKey: accountEmailStateQueryKey,
- })
- }, [qc])
-}
-
-export function useUpdateAccountEmailStateQueryCache() {
- const qc = useQueryClient()
-
- return useCallback(
- (data: AccountEmailState) => {
- return qc.setQueriesData(
- {
- queryKey: accountEmailStateQueryKey,
- },
- data,
- )
- },
- [qc],
- )
-}
-
export function useAccountEmailState() {
const agent = useAgent()
+ const {partialRefreshSession} = useSessionApi()
const [prevIsEmailVerified, setPrevEmailIsVerified] = useState(
!!agent.session?.emailConfirmed,
)
- const fallbackData: AccountEmailState = {
- isEmailVerified: !!agent.session?.emailConfirmed,
- email2FAEnabled: !!agent.session?.emailAuthFactor,
- }
- const query = useQuery({
+ const state: AccountEmailState = useMemo(
+ () => ({
+ isEmailVerified: !!agent.session?.emailConfirmed,
+ email2FAEnabled: !!agent.session?.emailAuthFactor,
+ }),
+ [agent.session],
+ )
+
+ /**
+ * Only here to refetch on focus, when necessary
+ */
+ useQuery({
enabled: !!agent.session,
- refetchOnWindowFocus: true,
+ /**
+ * Only refetch if the email verification s incomplete.
+ */
+ refetchOnWindowFocus: !prevIsEmailVerified,
queryKey: accountEmailStateQueryKey,
queryFn: async () => {
- // will also trigger updates to `#/state/session` data
- const {data} = await agent.resumeSession(agent.session!)
- return {
- isEmailVerified: !!data.emailConfirmed,
- email2FAEnabled: !!data.emailAuthFactor,
- }
+ await partialRefreshSession()
+ return null
},
})
- const state = query.data ?? fallbackData
-
/*
* This will emit `n` times for each instance of this hook. So the listeners
* all use `once` to prevent multiple handlers firing.
diff --git a/src/components/dialogs/EmailDialog/data/useConfirmEmail.ts b/src/components/dialogs/EmailDialog/data/useConfirmEmail.ts
index 73f824fcc6..475a8cbfb6 100644
--- a/src/components/dialogs/EmailDialog/data/useConfirmEmail.ts
+++ b/src/components/dialogs/EmailDialog/data/useConfirmEmail.ts
@@ -1,13 +1,10 @@
import {useMutation} from '@tanstack/react-query'
import {useAgent, useSession} from '#/state/session'
-import {useUpdateAccountEmailStateQueryCache} from '#/components/dialogs/EmailDialog/data/useAccountEmailState'
export function useConfirmEmail() {
const agent = useAgent()
const {currentAccount} = useSession()
- const updateAccountEmailStateQueryCache =
- useUpdateAccountEmailStateQueryCache()
return useMutation({
mutationFn: async ({token}: {token: string}) => {
@@ -19,11 +16,8 @@ export function useConfirmEmail() {
email: currentAccount.email,
token: token.trim(),
})
- const {data} = await agent.resumeSession(agent.session!)
- updateAccountEmailStateQueryCache({
- isEmailVerified: !!data.emailConfirmed,
- email2FAEnabled: !!data.emailAuthFactor,
- })
+ // will update session state at root of app
+ await agent.resumeSession(agent.session!)
},
})
}
diff --git a/src/components/dialogs/EmailDialog/data/useManageEmail2FA.ts b/src/components/dialogs/EmailDialog/data/useManageEmail2FA.ts
index 39f5fd2d9e..358bf86544 100644
--- a/src/components/dialogs/EmailDialog/data/useManageEmail2FA.ts
+++ b/src/components/dialogs/EmailDialog/data/useManageEmail2FA.ts
@@ -1,13 +1,10 @@
import {useMutation} from '@tanstack/react-query'
import {useAgent, useSession} from '#/state/session'
-import {useUpdateAccountEmailStateQueryCache} from '#/components/dialogs/EmailDialog/data/useAccountEmailState'
export function useManageEmail2FA() {
const agent = useAgent()
const {currentAccount} = useSession()
- const updateAccountEmailStateQueryCache =
- useUpdateAccountEmailStateQueryCache()
return useMutation({
mutationFn: async ({
@@ -25,11 +22,8 @@ export function useManageEmail2FA() {
emailAuthFactor: enabled,
token,
})
- const {data} = await agent.resumeSession(agent.session!)
- updateAccountEmailStateQueryCache({
- isEmailVerified: !!data.emailConfirmed,
- email2FAEnabled: !!data.emailAuthFactor,
- })
+ // will update session state at root of app
+ await agent.resumeSession(agent.session!)
},
})
}
diff --git a/src/lib/api/feed/home.ts b/src/lib/api/feed/home.ts
index e6bc45bea0..7a0d72d915 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 ba6952a81d..e7fba570bc 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/statsig/gates.ts b/src/lib/statsig/gates.ts
index efd7d605a0..3b1106480d 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 f509f2980a..f2d3ffca9b 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/locale/locales/en/messages.po b/src/locale/locales/en/messages.po
index 3013c54b63..098665dbe7 100644
--- a/src/locale/locales/en/messages.po
+++ b/src/locale/locales/en/messages.po
@@ -671,7 +671,7 @@ msgstr ""
msgid "Add another account"
msgstr ""
-#: src/view/com/composer/Composer.tsx:773
+#: src/view/com/composer/Composer.tsx:776
msgid "Add another post"
msgstr ""
@@ -702,7 +702,7 @@ msgstr ""
msgid "Add muted words and tags"
msgstr ""
-#: src/view/com/composer/Composer.tsx:1335
+#: src/view/com/composer/Composer.tsx:1340
msgid "Add new post"
msgstr ""
@@ -1139,11 +1139,11 @@ msgstr ""
msgid "Are you sure you want to remove this from your feeds?"
msgstr ""
-#: src/view/com/composer/Composer.tsx:724
+#: src/view/com/composer/Composer.tsx:725
msgid "Are you sure you'd like to discard this draft?"
msgstr ""
-#: src/view/com/composer/Composer.tsx:905
+#: src/view/com/composer/Composer.tsx:910
msgid "Are you sure you'd like to discard this post?"
msgstr ""
@@ -1522,8 +1522,8 @@ msgstr ""
#: src/screens/Settings/Settings.tsx:283
#: src/screens/Takendown.tsx:99
#: src/screens/Takendown.tsx:102
-#: src/view/com/composer/Composer.tsx:960
-#: src/view/com/composer/Composer.tsx:971
+#: src/view/com/composer/Composer.tsx:965
+#: src/view/com/composer/Composer.tsx:976
#: src/view/com/composer/photos/EditImageDialog.web.tsx:43
#: src/view/com/composer/photos/EditImageDialog.web.tsx:52
#: src/view/com/modals/ChangePassword.tsx:279
@@ -1887,7 +1887,7 @@ msgstr ""
msgid "Closes password update alert"
msgstr ""
-#: src/view/com/composer/Composer.tsx:968
+#: src/view/com/composer/Composer.tsx:973
msgid "Closes post composer and discards post draft"
msgstr ""
@@ -1944,7 +1944,7 @@ msgstr ""
msgid "Compose new post"
msgstr ""
-#: src/view/com/composer/Composer.tsx:869
+#: src/view/com/composer/Composer.tsx:874
msgid "Compose posts up to {0, plural, other {# characters}} in length"
msgstr ""
@@ -1952,7 +1952,7 @@ msgstr ""
msgid "Compose reply"
msgstr ""
-#: src/view/com/composer/Composer.tsx:1729
+#: src/view/com/composer/Composer.tsx:1734
msgid "Compressing video..."
msgstr ""
@@ -2477,7 +2477,7 @@ msgstr ""
#: src/components/PostControls/PostMenu/PostMenuItems.tsx:682
#: src/components/PostControls/PostMenu/PostMenuItems.tsx:684
-#: src/view/com/composer/Composer.tsx:879
+#: src/view/com/composer/Composer.tsx:884
msgid "Delete post"
msgstr ""
@@ -2601,8 +2601,8 @@ msgid "Disabled"
msgstr ""
#: src/screens/Profile/Header/EditProfileDialog.tsx:88
-#: src/view/com/composer/Composer.tsx:726
-#: src/view/com/composer/Composer.tsx:912
+#: src/view/com/composer/Composer.tsx:727
+#: src/view/com/composer/Composer.tsx:917
msgid "Discard"
msgstr ""
@@ -2610,11 +2610,11 @@ msgstr ""
msgid "Discard changes?"
msgstr ""
-#: src/view/com/composer/Composer.tsx:723
+#: src/view/com/composer/Composer.tsx:724
msgid "Discard draft?"
msgstr ""
-#: src/view/com/composer/Composer.tsx:904
+#: src/view/com/composer/Composer.tsx:909
msgid "Discard post?"
msgstr ""
@@ -2640,7 +2640,7 @@ msgstr ""
msgid "Dismiss"
msgstr ""
-#: src/view/com/composer/Composer.tsx:1653
+#: src/view/com/composer/Composer.tsx:1658
msgid "Dismiss error"
msgstr ""
@@ -3098,7 +3098,7 @@ msgstr ""
msgid "Entertainment"
msgstr ""
-#: src/view/com/composer/Composer.tsx:1738
+#: src/view/com/composer/Composer.tsx:1743
#: src/view/com/util/error/ErrorScreen.tsx:42
msgid "Error"
msgstr ""
@@ -3682,12 +3682,6 @@ msgstr ""
msgid "Followers you know"
msgstr ""
-#: src/view/screens/Feeds.tsx:603
-#: src/view/screens/SavedFeeds.tsx:420
-msgctxt "feed-name"
-msgid "Following"
-msgstr ""
-
#. User is following this account, click to unfollow
#: src/components/ProfileCard.tsx:484
#: src/components/ProfileHoverCard/index.web.tsx:493
@@ -3698,6 +3692,12 @@ msgstr ""
msgid "Following"
msgstr ""
+#: src/view/screens/Feeds.tsx:603
+#: src/view/screens/SavedFeeds.tsx:420
+msgctxt "feed-name"
+msgid "Following"
+msgstr ""
+
#: src/components/ProfileCard.tsx:447
#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:89
msgid "Following {0}"
@@ -4039,7 +4039,7 @@ msgid "Here is your app password!"
msgstr ""
#: src/components/VideoPostCard.tsx:178
-#: src/components/VideoPostCard.tsx:454
+#: src/components/VideoPostCard.tsx:455
msgid "Hidden"
msgstr ""
@@ -4425,7 +4425,7 @@ msgstr ""
msgid "It's just you right now! Add more people to your starter pack by searching above."
msgstr ""
-#: src/view/com/composer/Composer.tsx:1672
+#: src/view/com/composer/Composer.tsx:1677
msgid "Job ID: {0}"
msgstr ""
@@ -5761,7 +5761,7 @@ msgid "Open drawer menu"
msgstr ""
#: src/screens/Messages/components/MessageInput.web.tsx:181
-#: src/view/com/composer/Composer.tsx:1320
+#: src/view/com/composer/Composer.tsx:1325
msgid "Open emoji picker"
msgstr ""
@@ -5864,7 +5864,7 @@ msgstr ""
msgid "Opens device photo gallery"
msgstr ""
-#: src/view/com/composer/Composer.tsx:1321
+#: src/view/com/composer/Composer.tsx:1326
msgid "Opens emoji picker"
msgstr ""
@@ -6275,12 +6275,12 @@ msgctxt "description"
msgid "Post"
msgstr ""
-#: src/view/com/composer/Composer.tsx:1031
+#: src/view/com/composer/Composer.tsx:1036
msgctxt "action"
msgid "Post"
msgstr ""
-#: src/view/com/composer/Composer.tsx:1029
+#: src/view/com/composer/Composer.tsx:1034
msgctxt "action"
msgid "Post All"
msgstr ""
@@ -6453,7 +6453,7 @@ msgstr ""
msgid "Privacy Policy"
msgstr ""
-#: src/view/com/composer/Composer.tsx:1735
+#: src/view/com/composer/Composer.tsx:1740
msgid "Processing video..."
msgstr ""
@@ -6492,22 +6492,22 @@ msgid "Public, sharable lists which can be used to drive feeds."
msgstr ""
#. Accessibility label for button to publish a single post
-#: src/view/com/composer/Composer.tsx:1011
+#: src/view/com/composer/Composer.tsx:1016
msgid "Publish post"
msgstr ""
#. Accessibility label for button to publish multiple posts in a thread
-#: src/view/com/composer/Composer.tsx:1004
+#: src/view/com/composer/Composer.tsx:1009
msgid "Publish posts"
msgstr ""
#. Accessibility label for button to publish multiple replies in a thread
-#: src/view/com/composer/Composer.tsx:989
+#: src/view/com/composer/Composer.tsx:994
msgid "Publish replies"
msgstr ""
#. Accessibility label for button to publish a single reply
-#: src/view/com/composer/Composer.tsx:996
+#: src/view/com/composer/Composer.tsx:1001
msgid "Publish reply"
msgstr ""
@@ -6850,7 +6850,7 @@ msgstr ""
msgid "Replies to this post are disabled."
msgstr ""
-#: src/view/com/composer/Composer.tsx:1027
+#: src/view/com/composer/Composer.tsx:1032
msgctxt "action"
msgid "Reply"
msgstr ""
@@ -9185,7 +9185,7 @@ msgstr ""
msgid "Unsubscribed from list"
msgstr ""
-#: src/view/com/composer/Composer.tsx:811
+#: src/view/com/composer/Composer.tsx:814
msgid "Unsupported video type"
msgstr ""
@@ -9275,7 +9275,7 @@ msgstr ""
msgid "Uploading link thumbnail..."
msgstr ""
-#: src/view/com/composer/Composer.tsx:1732
+#: src/view/com/composer/Composer.tsx:1737
msgid "Uploading video..."
msgstr ""
@@ -9521,7 +9521,7 @@ msgstr ""
msgid "Video settings"
msgstr ""
-#: src/view/com/composer/Composer.tsx:1742
+#: src/view/com/composer/Composer.tsx:1747
msgid "Video uploaded"
msgstr ""
@@ -9616,7 +9616,7 @@ msgstr ""
msgid "View users who like this feed"
msgstr ""
-#: src/components/VideoPostCard.tsx:398
+#: src/components/VideoPostCard.tsx:399
msgid "View video"
msgstr ""
@@ -9834,7 +9834,7 @@ msgstr ""
#: src/view/com/auth/SplashScreen.tsx:38
#: src/view/com/auth/SplashScreen.web.tsx:99
-#: src/view/com/composer/Composer.tsx:774
+#: src/view/com/composer/Composer.tsx:777
msgid "What's up?"
msgstr ""
@@ -9916,11 +9916,11 @@ msgstr ""
msgid "Write a message"
msgstr ""
-#: src/view/com/composer/Composer.tsx:867
+#: src/view/com/composer/Composer.tsx:872
msgid "Write post"
msgstr ""
-#: src/view/com/composer/Composer.tsx:772
+#: src/view/com/composer/Composer.tsx:775
#: src/view/com/post-thread/PostThreadComposePrompt.tsx:90
msgid "Write your reply"
msgstr ""
diff --git a/src/screens/Messages/ChatList.tsx b/src/screens/Messages/ChatList.tsx
index e13f0617b8..c37a025cc9 100644
--- a/src/screens/Messages/ChatList.tsx
+++ b/src/screens/Messages/ChatList.tsx
@@ -164,12 +164,18 @@ export function MessagesScreenInner({navigation, route}: Props) {
// filter out convos that are actively being left
.filter(convo => !leftConvos.includes(convo.id))
+ const hasInboxRequests = inboxPreviewConvos?.length > 0
+
return [
- {
- type: 'INBOX',
- count: inboxPreviewConvos.length,
- profiles: inboxPreviewConvos.slice(0, 3),
- },
+ ...(hasInboxRequests
+ ? [
+ {
+ type: 'INBOX' as const,
+ count: inboxPreviewConvos.length,
+ profiles: inboxPreviewConvos.slice(0, 3),
+ },
+ ]
+ : []),
...conversations.map(
convo => ({type: 'CONVERSATION', conversation: convo}) as const,
),
@@ -223,16 +229,24 @@ export function MessagesScreenInner({navigation, route}: Props) {
return listenSoftReset(onSoftReset)
}, [onSoftReset, isScreenFocused])
- // Will always have 1 item - the inbox button
- if (conversations.length < 2) {
+ // NOTE(APiligrim)
+ // Show empty state only if there are no conversations at all
+ const actualConversations = conversations.filter(
+ item => item.type === 'CONVERSATION',
+ )
+ const hasInboxRequests = inboxPreviewConvos?.length > 0
+
+ if (actualConversations.length === 0) {
return (
-
+ {hasInboxRequests && (
+
+ )}
{isLoading ? (
) : (
diff --git a/src/state/ageAssurance/index.tsx b/src/state/ageAssurance/index.tsx
index 3451b1139d..6cdd8d9299 100644
--- a/src/state/ageAssurance/index.tsx
+++ b/src/state/ageAssurance/index.tsx
@@ -4,7 +4,6 @@ import {useQuery} from '@tanstack/react-query'
import {networkRetry} from '#/lib/async/retry'
import {useGetAndRegisterPushToken} from '#/lib/notifications/notifications'
-import {useGate} from '#/lib/statsig/statsig'
import {isNetworkError} from '#/lib/strings/errors'
import {
type AgeAssuranceAPIContextType,
@@ -41,7 +40,6 @@ const AgeAssuranceAPIContext = createContext({
* performance.
*/
export function Provider({children}: {children: React.ReactNode}) {
- const gate = useGate()
const agent = useAgent()
const {geolocation} = useGeolocation()
const isAgeAssuranceEnabled = useIsAgeAssuranceEnabled()
@@ -78,12 +76,10 @@ export function Provider({children}: {children: React.ReactNode}) {
account: agent.session?.did,
})
- if (gate('age_assurance')) {
- await getAndRegisterPushToken({
- isAgeRestricted:
- !!geolocation?.isAgeRestrictedGeo && data.status !== 'assured',
- })
- }
+ await getAndRegisterPushToken({
+ isAgeRestricted:
+ !!geolocation?.isAgeRestrictedGeo && data.status !== 'assured',
+ })
return data
} catch (e) {
diff --git a/src/state/ageAssurance/useAgeAssurance.ts b/src/state/ageAssurance/useAgeAssurance.ts
index 0215cc88dc..0613848687 100644
--- a/src/state/ageAssurance/useAgeAssurance.ts
+++ b/src/state/ageAssurance/useAgeAssurance.ts
@@ -28,7 +28,8 @@ export function useAgeAssurance(): AgeAssurance {
return useMemo(() => {
const isReady = aa.isReady && preferencesLoaded
- const isDeclaredUnderage = (declaredAge || 0) < 18
+ const isDeclaredUnderage =
+ declaredAge !== undefined ? declaredAge < 18 : false
const state: AgeAssurance = {
isReady,
status: aa.status,
diff --git a/src/state/ageAssurance/useIsAgeAssuranceEnabled.ts b/src/state/ageAssurance/useIsAgeAssuranceEnabled.ts
index 06fe46d236..b020e3c573 100644
--- a/src/state/ageAssurance/useIsAgeAssuranceEnabled.ts
+++ b/src/state/ageAssurance/useIsAgeAssuranceEnabled.ts
@@ -1,14 +1,11 @@
import {useMemo} from 'react'
-import {useGate} from '#/lib/statsig/statsig'
import {useGeolocation} from '#/state/geolocation'
export function useIsAgeAssuranceEnabled() {
- const gate = useGate()
const {geolocation} = useGeolocation()
return useMemo(() => {
- const enabled = gate('age_assurance')
- return enabled && !!geolocation?.isAgeRestrictedGeo
- }, [geolocation, gate])
+ return !!geolocation?.isAgeRestrictedGeo
+ }, [geolocation])
}
diff --git a/src/state/session/index.tsx b/src/state/session/index.tsx
index 92bd7babee..32026323f5 100644
--- a/src/state/session/index.tsx
+++ b/src/state/session/index.tsx
@@ -48,6 +48,7 @@ const ApiContext = React.createContext({
logoutEveryAccount: async () => {},
resumeSession: async () => {},
removeAccount: () => {},
+ partialRefreshSession: async () => {},
})
export function Provider({children}: React.PropsWithChildren<{}>) {
@@ -139,7 +140,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
)
const logoutCurrentAccount = React.useCallback<
- SessionApiContext['logoutEveryAccount']
+ SessionApiContext['logoutCurrentAccount']
>(
logContext => {
addSessionDebugLog({type: 'method:start', method: 'logout'})
@@ -215,6 +216,23 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
[onAgentSessionChange, cancelPendingTask, shouldUseOauth],
)
+ const partialRefreshSession = React.useCallback<
+ SessionApiContext['partialRefreshSession']
+ >(async () => {
+ const agent = state.currentAgentState.agent as BskyAppAgent
+ const signal = cancelPendingTask()
+ const {data} = await agent.com.atproto.server.getSession()
+ if (signal.aborted) return
+ dispatch({
+ type: 'partial-refresh-session',
+ accountDid: agent.session!.did,
+ patch: {
+ emailConfirmed: data.emailConfirmed,
+ emailAuthFactor: data.emailAuthFactor,
+ },
+ })
+ }, [state, cancelPendingTask])
+
const removeAccount = React.useCallback(
account => {
addSessionDebugLog({
@@ -301,6 +319,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
logoutEveryAccount,
resumeSession,
removeAccount,
+ partialRefreshSession,
}),
[
createAccount,
@@ -309,6 +328,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
logoutEveryAccount,
resumeSession,
removeAccount,
+ partialRefreshSession,
],
)
diff --git a/src/state/session/reducer.ts b/src/state/session/reducer.ts
index d638f4d535..5a3dca8d16 100644
--- a/src/state/session/reducer.ts
+++ b/src/state/session/reducer.ts
@@ -1,4 +1,4 @@
-import {type AtpSessionEvent} from '@atproto/api'
+import {type AtpSessionEvent, type BskyAgent} from '@atproto/api'
import {createPublicAgent} from './agent'
import {wrapSessionReducerForLogging} from './logging'
@@ -52,6 +52,11 @@ export type Action =
syncedAccounts: SessionAccount[]
syncedCurrentDid: string | undefined
}
+ | {
+ type: 'partial-refresh-session'
+ accountDid: string
+ patch: Pick
+ }
function createPublicAgentState(): AgentState {
return {
@@ -180,6 +185,39 @@ let reducer = (state: State, action: Action): State => {
needsPersist: false, // Synced from another tab. Don't persist to avoid cycles.
}
}
+ case 'partial-refresh-session': {
+ const {accountDid, patch} = action
+ const agent = state.currentAgentState.agent as BskyAgent
+
+ /*
+ * Only mutating values that are safe. Be very careful with this.
+ */
+ if (agent.session) {
+ agent.session.emailConfirmed =
+ patch.emailConfirmed ?? agent.session.emailConfirmed
+ agent.session.emailAuthFactor =
+ patch.emailAuthFactor ?? agent.session.emailAuthFactor
+ }
+
+ return {
+ ...state,
+ currentAgentState: {
+ ...state.currentAgentState,
+ agent,
+ },
+ accounts: state.accounts.map(a => {
+ if (a.did === accountDid) {
+ return {
+ ...a,
+ emailConfirmed: patch.emailConfirmed ?? a.emailConfirmed,
+ emailAuthFactor: patch.emailAuthFactor ?? a.emailAuthFactor,
+ }
+ }
+ return a
+ }),
+ needsPersist: true,
+ }
+ }
}
}
reducer = wrapSessionReducerForLogging(reducer)
diff --git a/src/state/session/types.ts b/src/state/session/types.ts
index 8dedf18ba2..e2a7bc6679 100644
--- a/src/state/session/types.ts
+++ b/src/state/session/types.ts
@@ -43,4 +43,12 @@ export type SessionApiContext = {
) => void
resumeSession: (account: SessionAccount) => Promise
removeAccount: (account: SessionAccount) => void
+ /**
+ * Calls `getSession` and updates select fields on the current account and
+ * `BskyAgent`. This is an alternative to `resumeSession`, which updates
+ * current account/agent using the `persistSessionHandler`, but is more load
+ * bearing. This patches in updates without causing any side effects via
+ * `persistSessionHandler`.
+ */
+ partialRefreshSession: () => Promise
}