[APP-1158] Gate features by email verification state (#8305)
* Use new hook in all locations * Format * Seems to work, not great duplication * Wrap all open composer calls * Remove unneeded spans * Missed one * Fix handler on Conversation * Gate new chat in header * Add comment * Remove whoopsie * Format * add hackfix for dialog not showing * add prompt to accept chat btn * navigation not necessary * send back one screen, rather than home * Update comment --------- Co-authored-by: Samuel Newman <mozzius@protonmail.com>
This commit is contained in:
@@ -62,7 +62,6 @@ import {
|
||||
type SupportedMimeTypes,
|
||||
} from '#/lib/constants'
|
||||
import {useAnimatedScrollHandler} from '#/lib/hooks/useAnimatedScrollHandler_FIXED'
|
||||
import {useEmail} from '#/lib/hooks/useEmail'
|
||||
import {useIsKeyboardVisible} from '#/lib/hooks/useIsKeyboardVisible'
|
||||
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
|
||||
import {usePalette} from '#/lib/hooks/usePalette'
|
||||
@@ -120,10 +119,6 @@ import * as Toast from '#/view/com/util/Toast'
|
||||
import {UserAvatar} from '#/view/com/util/UserAvatar'
|
||||
import {atoms as a, native, useTheme} from '#/alf'
|
||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||
import {
|
||||
EmailDialogScreenID,
|
||||
useEmailDialogControl,
|
||||
} from '#/components/dialogs/EmailDialog'
|
||||
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
|
||||
import {EmojiArc_Stroke2_Corner0_Rounded as EmojiSmile} from '#/components/icons/Emoji'
|
||||
import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times'
|
||||
@@ -333,25 +328,6 @@ export const ComposePost = ({
|
||||
}
|
||||
}, [onPressCancel, closeAllDialogs, closeAllModals])
|
||||
|
||||
const {needsEmailVerification} = useEmail()
|
||||
const emailDialogControl = useEmailDialogControl()
|
||||
|
||||
useEffect(() => {
|
||||
if (needsEmailVerification) {
|
||||
emailDialogControl.open({
|
||||
id: EmailDialogScreenID.Verify,
|
||||
instructions: [
|
||||
<Trans key="pre-compose">
|
||||
Before creating a post, you must first verify your email.
|
||||
</Trans>,
|
||||
],
|
||||
onCloseWithoutVerifying: () => {
|
||||
onClose()
|
||||
},
|
||||
})
|
||||
}
|
||||
}, [needsEmailVerification, emailDialogControl, onClose])
|
||||
|
||||
const missingAltError = useMemo(() => {
|
||||
if (!requireAltTextEnabled) {
|
||||
return
|
||||
|
||||
@@ -1,26 +1,19 @@
|
||||
import {useCallback} from 'react'
|
||||
import {Keyboard} from 'react-native'
|
||||
import {ImagePickerAsset} from 'expo-image-picker'
|
||||
import {type ImagePickerAsset} from 'expo-image-picker'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {
|
||||
SUPPORTED_MIME_TYPES,
|
||||
SupportedMimeTypes,
|
||||
type SupportedMimeTypes,
|
||||
VIDEO_MAX_DURATION_MS,
|
||||
} from '#/lib/constants'
|
||||
import {BSKY_SERVICE} from '#/lib/constants'
|
||||
import {useVideoLibraryPermission} from '#/lib/hooks/usePermissions'
|
||||
import {getHostnameFromUrl} from '#/lib/strings/url-helpers'
|
||||
import {isWeb} from '#/platform/detection'
|
||||
import {isNative} from '#/platform/detection'
|
||||
import {useSession} from '#/state/session'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {Button} from '#/components/Button'
|
||||
import {useDialogControl} from '#/components/Dialog'
|
||||
import {VerifyEmailDialog} from '#/components/dialogs/VerifyEmailDialog'
|
||||
import {VideoClip_Stroke2_Corner0_Rounded as VideoClipIcon} from '#/components/icons/VideoClip'
|
||||
import * as Prompt from '#/components/Prompt'
|
||||
import {pickVideo} from './pickVideo'
|
||||
|
||||
type Props = {
|
||||
@@ -33,66 +26,45 @@ export function SelectVideoBtn({onSelectVideo, disabled, setError}: Props) {
|
||||
const {_} = useLingui()
|
||||
const t = useTheme()
|
||||
const {requestVideoAccessIfNeeded} = useVideoLibraryPermission()
|
||||
const control = Prompt.usePromptControl()
|
||||
const {currentAccount} = useSession()
|
||||
|
||||
const onPressSelectVideo = useCallback(async () => {
|
||||
if (isNative && !(await requestVideoAccessIfNeeded())) {
|
||||
return
|
||||
}
|
||||
|
||||
if (
|
||||
currentAccount &&
|
||||
!currentAccount.emailConfirmed &&
|
||||
getHostnameFromUrl(currentAccount.service) ===
|
||||
getHostnameFromUrl(BSKY_SERVICE)
|
||||
) {
|
||||
Keyboard.dismiss()
|
||||
control.open()
|
||||
} else {
|
||||
const response = await pickVideo()
|
||||
if (response.assets && response.assets.length > 0) {
|
||||
const asset = response.assets[0]
|
||||
try {
|
||||
if (isWeb) {
|
||||
// asset.duration is null for gifs (see the TODO in pickVideo.web.ts)
|
||||
if (asset.duration && asset.duration > VIDEO_MAX_DURATION_MS) {
|
||||
throw Error(_(msg`Videos must be less than 3 minutes long`))
|
||||
}
|
||||
// compression step on native converts to mp4, so no need to check there
|
||||
if (
|
||||
!SUPPORTED_MIME_TYPES.includes(
|
||||
asset.mimeType as SupportedMimeTypes,
|
||||
)
|
||||
) {
|
||||
throw Error(_(msg`Unsupported video type: ${asset.mimeType}`))
|
||||
}
|
||||
} else {
|
||||
if (typeof asset.duration !== 'number') {
|
||||
throw Error('Asset is not a video')
|
||||
}
|
||||
if (asset.duration > VIDEO_MAX_DURATION_MS) {
|
||||
throw Error(_(msg`Videos must be less than 3 minutes long`))
|
||||
}
|
||||
const response = await pickVideo()
|
||||
if (response.assets && response.assets.length > 0) {
|
||||
const asset = response.assets[0]
|
||||
try {
|
||||
if (isWeb) {
|
||||
// asset.duration is null for gifs (see the TODO in pickVideo.web.ts)
|
||||
if (asset.duration && asset.duration > VIDEO_MAX_DURATION_MS) {
|
||||
throw Error(_(msg`Videos must be less than 3 minutes long`))
|
||||
}
|
||||
onSelectVideo(asset)
|
||||
} catch (err) {
|
||||
if (err instanceof Error) {
|
||||
setError(err.message)
|
||||
} else {
|
||||
setError(_(msg`An error occurred while selecting the video`))
|
||||
// compression step on native converts to mp4, so no need to check there
|
||||
if (
|
||||
!SUPPORTED_MIME_TYPES.includes(asset.mimeType as SupportedMimeTypes)
|
||||
) {
|
||||
throw Error(_(msg`Unsupported video type: ${asset.mimeType}`))
|
||||
}
|
||||
} else {
|
||||
if (typeof asset.duration !== 'number') {
|
||||
throw Error('Asset is not a video')
|
||||
}
|
||||
if (asset.duration > VIDEO_MAX_DURATION_MS) {
|
||||
throw Error(_(msg`Videos must be less than 3 minutes long`))
|
||||
}
|
||||
}
|
||||
onSelectVideo(asset)
|
||||
} catch (err) {
|
||||
if (err instanceof Error) {
|
||||
setError(err.message)
|
||||
} else {
|
||||
setError(_(msg`An error occurred while selecting the video`))
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [
|
||||
requestVideoAccessIfNeeded,
|
||||
currentAccount,
|
||||
control,
|
||||
setError,
|
||||
_,
|
||||
onSelectVideo,
|
||||
])
|
||||
}, [requestVideoAccessIfNeeded, setError, _, onSelectVideo])
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -111,30 +83,6 @@ export function SelectVideoBtn({onSelectVideo, disabled, setError}: Props) {
|
||||
style={disabled && t.atoms.text_contrast_low}
|
||||
/>
|
||||
</Button>
|
||||
<VerifyEmailPrompt control={control} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function VerifyEmailPrompt({control}: {control: Prompt.PromptControlProps}) {
|
||||
const {_} = useLingui()
|
||||
const verifyEmailDialogControl = useDialogControl()
|
||||
|
||||
return (
|
||||
<>
|
||||
<Prompt.Basic
|
||||
control={control}
|
||||
title={_(msg`Verified email required`)}
|
||||
description={_(
|
||||
msg`To upload videos to Bluesky, you must first verify your email.`,
|
||||
)}
|
||||
confirmButtonCta={_(msg`Verify now`)}
|
||||
confirmButtonColor="primary"
|
||||
onConfirm={() => {
|
||||
verifyEmailDialogControl.open()
|
||||
}}
|
||||
/>
|
||||
<VerifyEmailDialog control={verifyEmailDialogControl} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,32 +1,32 @@
|
||||
import React from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {AppBskyActorDefs, AppBskyFeedDefs} from '@atproto/api'
|
||||
import {type AppBskyActorDefs, AppBskyFeedDefs} from '@atproto/api'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {NavigationProp, useNavigation} from '@react-navigation/native'
|
||||
import {type NavigationProp, useNavigation} from '@react-navigation/native'
|
||||
import {useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {VIDEO_FEED_URIS} from '#/lib/constants'
|
||||
import {useOpenComposer} from '#/lib/hooks/useOpenComposer'
|
||||
import {ComposeIcon2} from '#/lib/icons'
|
||||
import {getRootNavigation, getTabState, TabState} from '#/lib/routes/helpers'
|
||||
import {AllNavigatorParams} from '#/lib/routes/types'
|
||||
import {type AllNavigatorParams} from '#/lib/routes/types'
|
||||
import {logEvent} from '#/lib/statsig/statsig'
|
||||
import {s} from '#/lib/styles'
|
||||
import {isNative} from '#/platform/detection'
|
||||
import {listenSoftReset} from '#/state/events'
|
||||
import {FeedFeedbackProvider, useFeedFeedback} from '#/state/feed-feedback'
|
||||
import {useSetHomeBadge} from '#/state/home-badge'
|
||||
import {SavedFeedSourceInfo} from '#/state/queries/feed'
|
||||
import {type SavedFeedSourceInfo} from '#/state/queries/feed'
|
||||
import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed'
|
||||
import {FeedDescriptor, FeedParams} from '#/state/queries/post-feed'
|
||||
import {type FeedDescriptor, type FeedParams} from '#/state/queries/post-feed'
|
||||
import {truncateAndInvalidate} from '#/state/queries/util'
|
||||
import {useSession} from '#/state/session'
|
||||
import {useSetMinimalShellMode} from '#/state/shell'
|
||||
import {useComposerControls} from '#/state/shell/composer'
|
||||
import {useHeaderOffset} from '#/components/hooks/useHeaderOffset'
|
||||
import {PostFeed} from '../posts/PostFeed'
|
||||
import {FAB} from '../util/fab/FAB'
|
||||
import {ListMethods} from '../util/List'
|
||||
import {type ListMethods} from '../util/List'
|
||||
import {LoadLatestBtn} from '../util/load-latest/LoadLatestBtn'
|
||||
import {MainScrollProvider} from '../util/MainScrollProvider'
|
||||
|
||||
@@ -57,7 +57,7 @@ export function FeedPage({
|
||||
const {_} = useLingui()
|
||||
const navigation = useNavigation<NavigationProp<AllNavigatorParams>>()
|
||||
const queryClient = useQueryClient()
|
||||
const {openComposer} = useComposerControls()
|
||||
const {openComposer} = useOpenComposer()
|
||||
const [isScrolledDown, setIsScrolledDown] = React.useState(false)
|
||||
const setMinimalShellMode = useSetMinimalShellMode()
|
||||
const headerOffset = useHeaderOffset()
|
||||
|
||||
@@ -5,7 +5,7 @@ import Animated from 'react-native-reanimated'
|
||||
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
||||
import {
|
||||
AppBskyFeedDefs,
|
||||
AppBskyFeedThreadgate,
|
||||
type AppBskyFeedThreadgate,
|
||||
moderatePost,
|
||||
} from '@atproto/api'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
@@ -14,6 +14,7 @@ import {useLingui} from '@lingui/react'
|
||||
import {HITSLOP_10} from '#/lib/constants'
|
||||
import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
|
||||
import {useMinimalShellFabTransform} from '#/lib/hooks/useMinimalShellTransform'
|
||||
import {useOpenComposer} from '#/lib/hooks/useOpenComposer'
|
||||
import {useSetTitle} from '#/lib/hooks/useSetTitle'
|
||||
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
||||
import {clamp} from '#/lib/numbers'
|
||||
@@ -25,19 +26,18 @@ import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||
import {
|
||||
fillThreadModerationCache,
|
||||
sortThread,
|
||||
ThreadBlocked,
|
||||
ThreadModerationCache,
|
||||
ThreadNode,
|
||||
ThreadNotFound,
|
||||
ThreadPost,
|
||||
type ThreadBlocked,
|
||||
type ThreadModerationCache,
|
||||
type ThreadNode,
|
||||
type ThreadNotFound,
|
||||
type ThreadPost,
|
||||
usePostThreadQuery,
|
||||
} from '#/state/queries/post-thread'
|
||||
import {useSetThreadViewPreferencesMutation} from '#/state/queries/preferences'
|
||||
import {usePreferencesQuery} from '#/state/queries/preferences'
|
||||
import {useSession} from '#/state/session'
|
||||
import {useComposerControls} from '#/state/shell'
|
||||
import {useMergedThreadgateHiddenReplies} from '#/state/threadgate-hidden-replies'
|
||||
import {List, ListMethods} from '#/view/com/util/List'
|
||||
import {List, type ListMethods} from '#/view/com/util/List'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {Button, ButtonIcon} from '#/components/Button'
|
||||
import {SettingsSliderVertical_Stroke2_Corner0_Rounded as SettingsSlider} from '#/components/icons/SettingsSlider'
|
||||
@@ -394,7 +394,7 @@ export function PostThread({uri}: {uri: string | undefined}) {
|
||||
[refetch],
|
||||
)
|
||||
|
||||
const {openComposer} = useComposerControls()
|
||||
const {openComposer} = useOpenComposer()
|
||||
const onPressReply = React.useCallback(() => {
|
||||
if (thread?.type !== 'post') {
|
||||
return
|
||||
|
||||
@@ -17,6 +17,7 @@ import {msg, Plural, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {MAX_POST_LINES} from '#/lib/constants'
|
||||
import {useOpenComposer} from '#/lib/hooks/useOpenComposer'
|
||||
import {useOpenLink} from '#/lib/hooks/useOpenLink'
|
||||
import {usePalette} from '#/lib/hooks/usePalette'
|
||||
import {makeProfileLink} from '#/lib/routes/links'
|
||||
@@ -36,7 +37,6 @@ import {useProfileShadow} from '#/state/cache/profile-shadow'
|
||||
import {useLanguagePrefs} from '#/state/preferences'
|
||||
import {type ThreadPost} from '#/state/queries/post-thread'
|
||||
import {useSession} from '#/state/session'
|
||||
import {useComposerControls} from '#/state/shell/composer'
|
||||
import {useMergedThreadgateHiddenReplies} from '#/state/threadgate-hidden-replies'
|
||||
import {PostThreadFollowBtn} from '#/view/com/post-thread/PostThreadFollowBtn'
|
||||
import {ErrorMessage} from '#/view/com/util/error/ErrorMessage'
|
||||
@@ -204,7 +204,7 @@ let PostThreadItemLoaded = ({
|
||||
const pal = usePalette('default')
|
||||
const {_, i18n} = useLingui()
|
||||
const langPrefs = useLanguagePrefs()
|
||||
const {openComposer} = useComposerControls()
|
||||
const {openComposer} = useOpenComposer()
|
||||
const [limitLines, setLimitLines] = React.useState(
|
||||
() => countLines(richText?.text) >= MAX_POST_LINES,
|
||||
)
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import React, {useMemo, useState} from 'react'
|
||||
import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native'
|
||||
import {type StyleProp, StyleSheet, View, type ViewStyle} from 'react-native'
|
||||
import {
|
||||
AppBskyFeedDefs,
|
||||
type AppBskyFeedDefs,
|
||||
AppBskyFeedPost,
|
||||
AtUri,
|
||||
moderatePost,
|
||||
ModerationDecision,
|
||||
type ModerationDecision,
|
||||
RichText as RichTextAPI,
|
||||
} from '@atproto/api'
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||
@@ -14,15 +14,19 @@ import {useLingui} from '@lingui/react'
|
||||
import {useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {MAX_POST_LINES} from '#/lib/constants'
|
||||
import {useOpenComposer} from '#/lib/hooks/useOpenComposer'
|
||||
import {usePalette} from '#/lib/hooks/usePalette'
|
||||
import {makeProfileLink} from '#/lib/routes/links'
|
||||
import {countLines} from '#/lib/strings/helpers'
|
||||
import {colors, s} from '#/lib/styles'
|
||||
import {POST_TOMBSTONE, Shadow, usePostShadow} from '#/state/cache/post-shadow'
|
||||
import {
|
||||
POST_TOMBSTONE,
|
||||
type Shadow,
|
||||
usePostShadow,
|
||||
} from '#/state/cache/post-shadow'
|
||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||
import {precacheProfile} from '#/state/queries/profile'
|
||||
import {useSession} from '#/state/session'
|
||||
import {useComposerControls} from '#/state/shell/composer'
|
||||
import {AviFollowButton} from '#/view/com/posts/AviFollowButton'
|
||||
import {atoms as a} from '#/alf'
|
||||
import {ProfileHoverCard} from '#/components/ProfileHoverCard'
|
||||
@@ -113,7 +117,7 @@ function PostInner({
|
||||
const queryClient = useQueryClient()
|
||||
const pal = usePalette('default')
|
||||
const {_} = useLingui()
|
||||
const {openComposer} = useComposerControls()
|
||||
const {openComposer} = useOpenComposer()
|
||||
const [limitLines, setLimitLines] = useState(
|
||||
() => countLines(richText?.text) >= MAX_POST_LINES,
|
||||
)
|
||||
|
||||
@@ -1,35 +1,39 @@
|
||||
import React, {memo, useMemo, useState} from 'react'
|
||||
import {StyleSheet, View} from 'react-native'
|
||||
import {
|
||||
AppBskyActorDefs,
|
||||
type AppBskyActorDefs,
|
||||
AppBskyFeedDefs,
|
||||
AppBskyFeedPost,
|
||||
AppBskyFeedThreadgate,
|
||||
AtUri,
|
||||
ModerationDecision,
|
||||
type ModerationDecision,
|
||||
RichText as RichTextAPI,
|
||||
} from '@atproto/api'
|
||||
import {
|
||||
FontAwesomeIcon,
|
||||
FontAwesomeIconStyle,
|
||||
type FontAwesomeIconStyle,
|
||||
} from '@fortawesome/react-native-fontawesome'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {isReasonFeedSource, ReasonFeedSource} from '#/lib/api/feed/types'
|
||||
import {isReasonFeedSource, type ReasonFeedSource} from '#/lib/api/feed/types'
|
||||
import {MAX_POST_LINES} from '#/lib/constants'
|
||||
import {useOpenComposer} from '#/lib/hooks/useOpenComposer'
|
||||
import {usePalette} from '#/lib/hooks/usePalette'
|
||||
import {makeProfileLink} from '#/lib/routes/links'
|
||||
import {sanitizeDisplayName} from '#/lib/strings/display-names'
|
||||
import {sanitizeHandle} from '#/lib/strings/handles'
|
||||
import {countLines} from '#/lib/strings/helpers'
|
||||
import {s} from '#/lib/styles'
|
||||
import {POST_TOMBSTONE, Shadow, usePostShadow} from '#/state/cache/post-shadow'
|
||||
import {
|
||||
POST_TOMBSTONE,
|
||||
type Shadow,
|
||||
usePostShadow,
|
||||
} from '#/state/cache/post-shadow'
|
||||
import {useFeedFeedbackContext} from '#/state/feed-feedback'
|
||||
import {precacheProfile} from '#/state/queries/profile'
|
||||
import {useSession} from '#/state/session'
|
||||
import {useComposerControls} from '#/state/shell/composer'
|
||||
import {useMergedThreadgateHiddenReplies} from '#/state/threadgate-hidden-replies'
|
||||
import {FeedNameText} from '#/view/com/util/FeedInfoText'
|
||||
import {PostCtrls} from '#/view/com/util/post-ctrls/PostCtrls'
|
||||
@@ -43,7 +47,7 @@ import {Repost_Stroke2_Corner2_Rounded as RepostIcon} from '#/components/icons/R
|
||||
import {ContentHider} from '#/components/moderation/ContentHider'
|
||||
import {LabelsOnMyPost} from '#/components/moderation/LabelsOnMe'
|
||||
import {PostAlerts} from '#/components/moderation/PostAlerts'
|
||||
import {AppModerationCause} from '#/components/Pills'
|
||||
import {type AppModerationCause} from '#/components/Pills'
|
||||
import {ProfileHoverCard} from '#/components/ProfileHoverCard'
|
||||
import {RichText} from '#/components/RichText'
|
||||
import {SubtleWebHover} from '#/components/SubtleWebHover'
|
||||
@@ -150,7 +154,7 @@ let FeedItemInner = ({
|
||||
rootPost: AppBskyFeedDefs.PostView
|
||||
}): React.ReactNode => {
|
||||
const queryClient = useQueryClient()
|
||||
const {openComposer} = useComposerControls()
|
||||
const {openComposer} = useOpenComposer()
|
||||
const pal = usePalette('default')
|
||||
const {_} = useLingui()
|
||||
|
||||
|
||||
@@ -8,11 +8,11 @@ import {
|
||||
} from 'react-native'
|
||||
import * as Clipboard from 'expo-clipboard'
|
||||
import {
|
||||
AppBskyFeedDefs,
|
||||
AppBskyFeedPost,
|
||||
AppBskyFeedThreadgate,
|
||||
type AppBskyFeedDefs,
|
||||
type AppBskyFeedPost,
|
||||
type AppBskyFeedThreadgate,
|
||||
AtUri,
|
||||
RichText as RichTextAPI,
|
||||
type RichText as RichTextAPI,
|
||||
} from '@atproto/api'
|
||||
import {msg, plural} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
@@ -22,18 +22,18 @@ import {DISCOVER_DEBUG_DIDS, POST_CTRL_HITSLOP} from '#/lib/constants'
|
||||
import {CountWheel} from '#/lib/custom-animations/CountWheel'
|
||||
import {AnimatedLikeIcon} from '#/lib/custom-animations/LikeIcon'
|
||||
import {useHaptics} from '#/lib/haptics'
|
||||
import {useOpenComposer} from '#/lib/hooks/useOpenComposer'
|
||||
import {makeProfileLink} from '#/lib/routes/links'
|
||||
import {shareUrl} from '#/lib/sharing'
|
||||
import {useGate} from '#/lib/statsig/statsig'
|
||||
import {toShareUrl} from '#/lib/strings/url-helpers'
|
||||
import {Shadow} from '#/state/cache/types'
|
||||
import {type Shadow} from '#/state/cache/types'
|
||||
import {useFeedFeedbackContext} from '#/state/feed-feedback'
|
||||
import {
|
||||
usePostLikeMutationQueue,
|
||||
usePostRepostMutationQueue,
|
||||
} from '#/state/queries/post'
|
||||
import {useRequireAuth, useSession} from '#/state/session'
|
||||
import {useComposerControls} from '#/state/shell/composer'
|
||||
import {
|
||||
ProgressGuideAction,
|
||||
useProgressGuideControls,
|
||||
@@ -74,7 +74,7 @@ let PostCtrls = ({
|
||||
}): React.ReactNode => {
|
||||
const t = useTheme()
|
||||
const {_, i18n} = useLingui()
|
||||
const {openComposer} = useComposerControls()
|
||||
const {openComposer} = useOpenComposer()
|
||||
const {currentAccount} = useSession()
|
||||
const [queueLike, queueUnlike] = usePostLikeMutationQueue(post, logContext)
|
||||
const [queueRepost, queueUnrepost] = usePostRepostMutationQueue(
|
||||
|
||||
@@ -1,30 +1,33 @@
|
||||
import React from 'react'
|
||||
import {ActivityIndicator, StyleSheet, View} from 'react-native'
|
||||
import {AppBskyFeedDefs} from '@atproto/api'
|
||||
import {type AppBskyFeedDefs} from '@atproto/api'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {useFocusEffect} from '@react-navigation/native'
|
||||
import debounce from 'lodash.debounce'
|
||||
|
||||
import {useOpenComposer} from '#/lib/hooks/useOpenComposer'
|
||||
import {usePalette} from '#/lib/hooks/usePalette'
|
||||
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
||||
import {ComposeIcon2} from '#/lib/icons'
|
||||
import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
|
||||
import {
|
||||
type CommonNavigatorParams,
|
||||
type NativeStackScreenProps,
|
||||
} from '#/lib/routes/types'
|
||||
import {cleanError} from '#/lib/strings/errors'
|
||||
import {s} from '#/lib/styles'
|
||||
import {isNative, isWeb} from '#/platform/detection'
|
||||
import {
|
||||
SavedFeedItem,
|
||||
type SavedFeedItem,
|
||||
useGetPopularFeedsQuery,
|
||||
useSavedFeeds,
|
||||
useSearchPopularFeedsMutation,
|
||||
} from '#/state/queries/feed'
|
||||
import {useSession} from '#/state/session'
|
||||
import {useSetMinimalShellMode} from '#/state/shell'
|
||||
import {useComposerControls} from '#/state/shell/composer'
|
||||
import {ErrorMessage} from '#/view/com/util/error/ErrorMessage'
|
||||
import {FAB} from '#/view/com/util/fab/FAB'
|
||||
import {List, ListMethods} from '#/view/com/util/List'
|
||||
import {List, type ListMethods} from '#/view/com/util/List'
|
||||
import {FeedFeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
|
||||
import {Text} from '#/view/com/util/text/Text'
|
||||
import {NoFollowingFeed} from '#/screens/Feeds/NoFollowingFeed'
|
||||
@@ -102,7 +105,7 @@ type FlatlistSlice =
|
||||
|
||||
export function FeedsScreen(_props: Props) {
|
||||
const pal = usePalette('default')
|
||||
const {openComposer} = useComposerControls()
|
||||
const {openComposer} = useOpenComposer()
|
||||
const {isMobile} = useWebMediaQueries()
|
||||
const [query, setQuery] = React.useState('')
|
||||
const [isPTR, setIsPTR] = React.useState(false)
|
||||
|
||||
+17
-20
@@ -4,16 +4,17 @@ import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {useFocusEffect, useNavigation} from '@react-navigation/native'
|
||||
|
||||
import {useEmail} from '#/lib/hooks/useEmail'
|
||||
import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
|
||||
import {NavigationProp} from '#/lib/routes/types'
|
||||
import {useRequireEmailVerification} from '#/lib/hooks/useRequireEmailVerification'
|
||||
import {
|
||||
type CommonNavigatorParams,
|
||||
type NativeStackScreenProps,
|
||||
} from '#/lib/routes/types'
|
||||
import {type NavigationProp} from '#/lib/routes/types'
|
||||
import {useModalControls} from '#/state/modals'
|
||||
import {useSetMinimalShellMode} from '#/state/shell'
|
||||
import {MyLists} from '#/view/com/lists/MyLists'
|
||||
import {atoms as a} from '#/alf'
|
||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||
import {useDialogControl} from '#/components/Dialog'
|
||||
import {VerifyEmailDialog} from '#/components/dialogs/VerifyEmailDialog'
|
||||
import {PlusLarge_Stroke2_Corner0_Rounded as PlusIcon} from '#/components/icons/Plus'
|
||||
import * as Layout from '#/components/Layout'
|
||||
|
||||
@@ -23,8 +24,7 @@ export function ListsScreen({}: Props) {
|
||||
const setMinimalShellMode = useSetMinimalShellMode()
|
||||
const navigation = useNavigation<NavigationProp>()
|
||||
const {openModal} = useModalControls()
|
||||
const {needsEmailVerification} = useEmail()
|
||||
const control = useDialogControl()
|
||||
const requireEmailVerification = useRequireEmailVerification()
|
||||
|
||||
useFocusEffect(
|
||||
React.useCallback(() => {
|
||||
@@ -33,11 +33,6 @@ export function ListsScreen({}: Props) {
|
||||
)
|
||||
|
||||
const onPressNewList = React.useCallback(() => {
|
||||
if (needsEmailVerification) {
|
||||
control.open()
|
||||
return
|
||||
}
|
||||
|
||||
openModal({
|
||||
name: 'create-or-edit-list',
|
||||
purpose: 'app.bsky.graph.defs#curatelist',
|
||||
@@ -51,7 +46,15 @@ export function ListsScreen({}: Props) {
|
||||
} catch {}
|
||||
},
|
||||
})
|
||||
}, [needsEmailVerification, control, openModal, navigation])
|
||||
}, [openModal, navigation])
|
||||
|
||||
const wrappedOnPressNewList = requireEmailVerification(onPressNewList, {
|
||||
instructions: [
|
||||
<Trans key="lists">
|
||||
Before creating a list, you must first verify your email.
|
||||
</Trans>,
|
||||
],
|
||||
})
|
||||
|
||||
return (
|
||||
<Layout.Screen testID="listsScreen">
|
||||
@@ -68,7 +71,7 @@ export function ListsScreen({}: Props) {
|
||||
color="secondary"
|
||||
variant="solid"
|
||||
size="small"
|
||||
onPress={onPressNewList}>
|
||||
onPress={wrappedOnPressNewList}>
|
||||
<ButtonIcon icon={PlusIcon} />
|
||||
<ButtonText>
|
||||
<Trans context="action">New</Trans>
|
||||
@@ -76,12 +79,6 @@ export function ListsScreen({}: Props) {
|
||||
</Button>
|
||||
</Layout.Header.Outer>
|
||||
<MyLists filter="curate" style={a.flex_grow} />
|
||||
<VerifyEmailDialog
|
||||
reasonText={_(
|
||||
msg`Before creating a list, you must first verify your email.`,
|
||||
)}
|
||||
control={control}
|
||||
/>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,16 +4,17 @@ import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {useFocusEffect, useNavigation} from '@react-navigation/native'
|
||||
|
||||
import {useEmail} from '#/lib/hooks/useEmail'
|
||||
import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
|
||||
import {NavigationProp} from '#/lib/routes/types'
|
||||
import {useRequireEmailVerification} from '#/lib/hooks/useRequireEmailVerification'
|
||||
import {
|
||||
type CommonNavigatorParams,
|
||||
type NativeStackScreenProps,
|
||||
} from '#/lib/routes/types'
|
||||
import {type NavigationProp} from '#/lib/routes/types'
|
||||
import {useModalControls} from '#/state/modals'
|
||||
import {useSetMinimalShellMode} from '#/state/shell'
|
||||
import {MyLists} from '#/view/com/lists/MyLists'
|
||||
import {atoms as a} from '#/alf'
|
||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||
import {useDialogControl} from '#/components/Dialog'
|
||||
import {VerifyEmailDialog} from '#/components/dialogs/VerifyEmailDialog'
|
||||
import {PlusLarge_Stroke2_Corner0_Rounded as PlusIcon} from '#/components/icons/Plus'
|
||||
import * as Layout from '#/components/Layout'
|
||||
|
||||
@@ -23,8 +24,7 @@ export function ModerationModlistsScreen({}: Props) {
|
||||
const setMinimalShellMode = useSetMinimalShellMode()
|
||||
const navigation = useNavigation<NavigationProp>()
|
||||
const {openModal} = useModalControls()
|
||||
const {needsEmailVerification} = useEmail()
|
||||
const control = useDialogControl()
|
||||
const requireEmailVerification = useRequireEmailVerification()
|
||||
|
||||
useFocusEffect(
|
||||
React.useCallback(() => {
|
||||
@@ -33,11 +33,6 @@ export function ModerationModlistsScreen({}: Props) {
|
||||
)
|
||||
|
||||
const onPressNewList = React.useCallback(() => {
|
||||
if (needsEmailVerification) {
|
||||
control.open()
|
||||
return
|
||||
}
|
||||
|
||||
openModal({
|
||||
name: 'create-or-edit-list',
|
||||
purpose: 'app.bsky.graph.defs#modlist',
|
||||
@@ -51,7 +46,15 @@ export function ModerationModlistsScreen({}: Props) {
|
||||
} catch {}
|
||||
},
|
||||
})
|
||||
}, [needsEmailVerification, control, openModal, navigation])
|
||||
}, [openModal, navigation])
|
||||
|
||||
const wrappedOnPressNewList = requireEmailVerification(onPressNewList, {
|
||||
instructions: [
|
||||
<Trans key="modlist">
|
||||
Before creating a list, you must first verify your email.
|
||||
</Trans>,
|
||||
],
|
||||
})
|
||||
|
||||
return (
|
||||
<Layout.Screen testID="moderationModlistsScreen">
|
||||
@@ -68,7 +71,7 @@ export function ModerationModlistsScreen({}: Props) {
|
||||
color="secondary"
|
||||
variant="solid"
|
||||
size="small"
|
||||
onPress={onPressNewList}>
|
||||
onPress={wrappedOnPressNewList}>
|
||||
<ButtonIcon icon={PlusIcon} />
|
||||
<ButtonText>
|
||||
<Trans context="action">New</Trans>
|
||||
@@ -76,12 +79,6 @@ export function ModerationModlistsScreen({}: Props) {
|
||||
</Button>
|
||||
</Layout.Header.Outer>
|
||||
<MyLists filter="mod" style={a.flex_grow} />
|
||||
<VerifyEmailDialog
|
||||
reasonText={_(
|
||||
msg`Before creating a list, you must first verify your email.`,
|
||||
)}
|
||||
control={control}
|
||||
/>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -6,10 +6,11 @@ import {useFocusEffect, useIsFocused} from '@react-navigation/native'
|
||||
import {useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
|
||||
import {useOpenComposer} from '#/lib/hooks/useOpenComposer'
|
||||
import {ComposeIcon2} from '#/lib/icons'
|
||||
import {
|
||||
NativeStackScreenProps,
|
||||
NotificationsTabNavigatorParams,
|
||||
type NativeStackScreenProps,
|
||||
type NotificationsTabNavigatorParams,
|
||||
} from '#/lib/routes/types'
|
||||
import {s} from '#/lib/styles'
|
||||
import {logger} from '#/logger'
|
||||
@@ -22,12 +23,11 @@ import {
|
||||
} from '#/state/queries/notifications/unread'
|
||||
import {truncateAndInvalidate} from '#/state/queries/util'
|
||||
import {useSetMinimalShellMode} from '#/state/shell'
|
||||
import {useComposerControls} from '#/state/shell/composer'
|
||||
import {NotificationFeed} from '#/view/com/notifications/NotificationFeed'
|
||||
import {Pager} from '#/view/com/pager/Pager'
|
||||
import {TabBar} from '#/view/com/pager/TabBar'
|
||||
import {FAB} from '#/view/com/util/fab/FAB'
|
||||
import {ListMethods} from '#/view/com/util/List'
|
||||
import {type ListMethods} from '#/view/com/util/List'
|
||||
import {LoadLatestBtn} from '#/view/com/util/load-latest/LoadLatestBtn'
|
||||
import {MainScrollProvider} from '#/view/com/util/MainScrollProvider'
|
||||
import {atoms as a} from '#/alf'
|
||||
@@ -49,7 +49,7 @@ type Props = NativeStackScreenProps<
|
||||
>
|
||||
export function NotificationsScreen({}: Props) {
|
||||
const {_} = useLingui()
|
||||
const {openComposer} = useComposerControls()
|
||||
const {openComposer} = useOpenComposer()
|
||||
const unreadNotifs = useUnreadNotifications()
|
||||
const hasNew = !!unreadNotifs
|
||||
const {checkUnread: checkUnreadAll} = useUnreadNotificationsApi()
|
||||
|
||||
@@ -2,9 +2,9 @@ import React, {useCallback, useMemo} from 'react'
|
||||
import {StyleSheet} from 'react-native'
|
||||
import {SafeAreaView} from 'react-native-safe-area-context'
|
||||
import {
|
||||
AppBskyActorDefs,
|
||||
type AppBskyActorDefs,
|
||||
moderateProfile,
|
||||
ModerationOpts,
|
||||
type ModerationOpts,
|
||||
RichText as RichTextAPI,
|
||||
} from '@atproto/api'
|
||||
import {msg} from '@lingui/macro'
|
||||
@@ -12,9 +12,13 @@ import {useLingui} from '@lingui/react'
|
||||
import {useFocusEffect} from '@react-navigation/native'
|
||||
import {useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {useOpenComposer} from '#/lib/hooks/useOpenComposer'
|
||||
import {useSetTitle} from '#/lib/hooks/useSetTitle'
|
||||
import {ComposeIcon2} from '#/lib/icons'
|
||||
import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
|
||||
import {
|
||||
type CommonNavigatorParams,
|
||||
type NativeStackScreenProps,
|
||||
} from '#/lib/routes/types'
|
||||
import {combinedDisplayName} from '#/lib/strings/display-names'
|
||||
import {cleanError} from '#/lib/strings/errors'
|
||||
import {isInvalidHandle} from '#/lib/strings/handles'
|
||||
@@ -28,13 +32,12 @@ import {useProfileQuery} from '#/state/queries/profile'
|
||||
import {useResolveDidQuery} from '#/state/queries/resolve-uri'
|
||||
import {useAgent, useSession} from '#/state/session'
|
||||
import {useSetMinimalShellMode} from '#/state/shell'
|
||||
import {useComposerControls} from '#/state/shell/composer'
|
||||
import {ProfileFeedgens} from '#/view/com/feeds/ProfileFeedgens'
|
||||
import {ProfileLists} from '#/view/com/lists/ProfileLists'
|
||||
import {PagerWithHeader} from '#/view/com/pager/PagerWithHeader'
|
||||
import {ErrorScreen} from '#/view/com/util/error/ErrorScreen'
|
||||
import {FAB} from '#/view/com/util/fab/FAB'
|
||||
import {ListRef} from '#/view/com/util/List'
|
||||
import {type ListRef} from '#/view/com/util/List'
|
||||
import {ProfileHeader, ProfileHeaderLoading} from '#/screens/Profile/Header'
|
||||
import {ProfileFeedSection} from '#/screens/Profile/Sections/Feed'
|
||||
import {ProfileLabelsSection} from '#/screens/Profile/Sections/Labels'
|
||||
@@ -165,7 +168,7 @@ function ProfileScreenLoaded({
|
||||
const profile = useProfileShadow(profileUnshadowed)
|
||||
const {hasSession, currentAccount} = useSession()
|
||||
const setMinimalShellMode = useSetMinimalShellMode()
|
||||
const {openComposer} = useComposerControls()
|
||||
const {openComposer} = useOpenComposer()
|
||||
const {
|
||||
data: labelerInfo,
|
||||
error: labelerError,
|
||||
|
||||
@@ -16,6 +16,7 @@ import {useNavigation} from '@react-navigation/native'
|
||||
import {useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {useHaptics} from '#/lib/haptics'
|
||||
import {useOpenComposer} from '#/lib/hooks/useOpenComposer'
|
||||
import {usePalette} from '#/lib/hooks/usePalette'
|
||||
import {useSetTitle} from '#/lib/hooks/useSetTitle'
|
||||
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
||||
@@ -54,7 +55,6 @@ import {useResolveUriQuery} from '#/state/queries/resolve-uri'
|
||||
import {truncateAndInvalidate} from '#/state/queries/util'
|
||||
import {useSession} from '#/state/session'
|
||||
import {useSetMinimalShellMode} from '#/state/shell'
|
||||
import {useComposerControls} from '#/state/shell/composer'
|
||||
import {ListMembers} from '#/view/com/lists/ListMembers'
|
||||
import {PagerWithHeader} from '#/view/com/pager/PagerWithHeader'
|
||||
import {PostFeed} from '#/view/com/posts/PostFeed'
|
||||
@@ -155,7 +155,7 @@ function ProfileListScreenLoaded({
|
||||
}) {
|
||||
const {_} = useLingui()
|
||||
const queryClient = useQueryClient()
|
||||
const {openComposer} = useComposerControls()
|
||||
const {openComposer} = useOpenComposer()
|
||||
const setMinimalShellMode = useSetMinimalShellMode()
|
||||
const {currentAccount} = useSession()
|
||||
const {rkey} = route.params
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
} from '@react-navigation/native'
|
||||
|
||||
import {useAccountSwitcher} from '#/lib/hooks/useAccountSwitcher'
|
||||
import {useOpenComposer} from '#/lib/hooks/useOpenComposer'
|
||||
import {usePalette} from '#/lib/hooks/usePalette'
|
||||
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
||||
import {getCurrentRoute, isTab} from '#/lib/routes/helpers'
|
||||
@@ -25,7 +26,6 @@ import {useUnreadMessageCount} from '#/state/queries/messages/list-conversations
|
||||
import {useUnreadNotifications} from '#/state/queries/notifications/unread'
|
||||
import {useProfilesQuery} from '#/state/queries/profile'
|
||||
import {type SessionAccount, useSession, useSessionApi} from '#/state/session'
|
||||
import {useComposerControls} from '#/state/shell/composer'
|
||||
import {useLoggedOutViewControls} from '#/state/shell/logged-out'
|
||||
import {useCloseAllActiveElements} from '#/state/util'
|
||||
import {LoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
|
||||
@@ -447,7 +447,7 @@ function NavItem({count, hasNew, href, icon, iconFilled, label}: NavItemProps) {
|
||||
function ComposeBtn() {
|
||||
const {currentAccount} = useSession()
|
||||
const {getState} = useNavigation()
|
||||
const {openComposer} = useComposerControls()
|
||||
const {openComposer} = useOpenComposer()
|
||||
const {_} = useLingui()
|
||||
const {leftNavMinimal} = useLayoutBreakpoints()
|
||||
const [isFetchingHandle, setIsFetchingHandle] = React.useState(false)
|
||||
|
||||
Reference in New Issue
Block a user