Merge branch 'main' into hailey/oauth-yeag
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "bsky.app",
|
||||
"version": "1.105.0",
|
||||
"version": "1.106.0",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
|
||||
@@ -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({
|
||||
/>
|
||||
<MediaInsetBorder />
|
||||
|
||||
<View style={[a.absolute, a.inset_0]}>
|
||||
<View style={[a.absolute, a.inset_0, t.atoms.shadow_sm]}>
|
||||
<View style={[a.absolute, a.inset_0, a.p_sm, {bottom: 'auto'}]}>
|
||||
<View
|
||||
style={[a.relative, a.rounded_full, {width: 20, height: 20}]}>
|
||||
style={[a.relative, a.rounded_full, {width: 24, height: 24}]}>
|
||||
<UserAvatar
|
||||
type="user"
|
||||
size={20}
|
||||
size={24}
|
||||
avatar={post.author.avatar}
|
||||
/>
|
||||
<MediaInsetBorder />
|
||||
</View>
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
a.absolute,
|
||||
a.inset_0,
|
||||
a.pt_2xl,
|
||||
{
|
||||
top: 'auto',
|
||||
},
|
||||
]}>
|
||||
<LinearGradient
|
||||
colors={[black, 'rgba(0, 0, 0, 0)']}
|
||||
locations={[0.02, 1]}
|
||||
start={{x: 0, y: 1}}
|
||||
end={{x: 0, y: 0}}
|
||||
style={[a.absolute, a.inset_0, {opacity: 0.9}]}
|
||||
/>
|
||||
|
||||
{showLikeCount && (
|
||||
<View
|
||||
style={[a.relative, a.z_10, a.p_sm, a.flex_row, a.gap_md]}>
|
||||
{likeCount > 0 && (
|
||||
<View style={[a.flex_row, a.align_center, a.gap_xs]}>
|
||||
<Heart size="sm" fill="white" />
|
||||
<Text style={[a.text_sm, a.font_bold, {color: 'white'}]}>
|
||||
{formatCount(i18n, likeCount)}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
style={[
|
||||
a.absolute,
|
||||
a.inset_0,
|
||||
a.pt_2xl,
|
||||
{
|
||||
top: 'auto',
|
||||
},
|
||||
]}>
|
||||
<LinearGradient
|
||||
colors={[black, 'rgba(0, 0, 0, 0)']}
|
||||
locations={[0.02, 1]}
|
||||
start={{x: 0, y: 1}}
|
||||
end={{x: 0, y: 0}}
|
||||
style={[a.absolute, a.inset_0, {opacity: 0.9}]}
|
||||
/>
|
||||
|
||||
<View
|
||||
style={[a.relative, a.z_10, a.p_sm, a.flex_row, a.gap_md]}>
|
||||
{likeCount > 0 && (
|
||||
<View style={[a.flex_row, a.align_center, a.gap_xs]}>
|
||||
<Heart size="sm" fill="white" />
|
||||
<Text
|
||||
style={[a.text_sm, a.font_bold, {color: 'white'}]}>
|
||||
{formatCount(i18n, likeCount)}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
</Hider.Content>
|
||||
|
||||
@@ -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<AccountEmailState>({
|
||||
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.
|
||||
|
||||
@@ -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!)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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!)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
export type Gate =
|
||||
// Keep this alphabetic please.
|
||||
| 'age_assurance'
|
||||
| 'alt_share_icon'
|
||||
| 'debug_show_feedcontext'
|
||||
| 'debug_subscriptions'
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 ""
|
||||
|
||||
@@ -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 (
|
||||
<Layout.Screen>
|
||||
<Header newChatControl={newChatControl} />
|
||||
<Layout.Center>
|
||||
<InboxPreview
|
||||
count={inboxPreviewConvos.length}
|
||||
profiles={inboxPreviewConvos}
|
||||
/>
|
||||
{hasInboxRequests && (
|
||||
<InboxPreview
|
||||
count={inboxPreviewConvos.length}
|
||||
profiles={inboxPreviewConvos}
|
||||
/>
|
||||
)}
|
||||
{isLoading ? (
|
||||
<ChatListLoadingPlaceholder />
|
||||
) : (
|
||||
|
||||
@@ -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<AgeAssuranceAPIContextType>({
|
||||
* 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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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])
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ const ApiContext = React.createContext<SessionApiContext>({
|
||||
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<SessionApiContext['removeAccount']>(
|
||||
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,
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -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<SessionAccount, 'emailConfirmed' | 'emailAuthFactor'>
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
@@ -43,4 +43,12 @@ export type SessionApiContext = {
|
||||
) => void
|
||||
resumeSession: (account: SessionAccount) => Promise<void>
|
||||
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<void>
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user