diff options
Diffstat (limited to 'src')
21 files changed, 225 insertions, 82 deletions
diff --git a/src/lib/strings/capitalize.ts b/src/lib/strings/capitalize.ts new file mode 100644 index 000000000..a3415bd00 --- /dev/null +++ b/src/lib/strings/capitalize.ts @@ -0,0 +1,3 @@ +export function capitalize(str: string) { + return str.charAt(0).toUpperCase() + str.slice(1) +} diff --git a/src/screens/Onboarding/StepInterests/InterestButton.tsx b/src/screens/Onboarding/StepInterests/InterestButton.tsx index 02413b18d..cc692dafd 100644 --- a/src/screens/Onboarding/StepInterests/InterestButton.tsx +++ b/src/screens/Onboarding/StepInterests/InterestButton.tsx @@ -4,11 +4,13 @@ import {View, ViewStyle, TextStyle} from 'react-native' import {useTheme, atoms as a, native} from '#/alf' import * as Toggle from '#/components/forms/Toggle' import {Text} from '#/components/Typography' +import {capitalize} from '#/lib/strings/capitalize' -import {INTEREST_TO_DISPLAY_NAME} from '#/screens/Onboarding/StepInterests/data' +import {Context} from '#/screens/Onboarding/state' export function InterestButton({interest}: {interest: string}) { const t = useTheme() + const {interestsDisplayNames} = React.useContext(Context) const ctx = Toggle.useItemContext() const styles = React.useMemo(() => { @@ -72,7 +74,7 @@ export function InterestButton({interest}: {interest: string}) { native({paddingTop: 2}), ctx.selected ? styles.textSelected : {}, ]}> - {INTEREST_TO_DISPLAY_NAME[interest]} + {interestsDisplayNames[interest] || capitalize(interest)} </Text> </View> ) diff --git a/src/screens/Onboarding/StepInterests/data.ts b/src/screens/Onboarding/StepInterests/data.ts deleted file mode 100644 index 00a25331c..000000000 --- a/src/screens/Onboarding/StepInterests/data.ts +++ /dev/null @@ -1,36 +0,0 @@ -export const INTEREST_TO_DISPLAY_NAME: { - [key: string]: string -} = { - news: 'News', - journalism: 'Journalism', - nature: 'Nature', - art: 'Art', - comics: 'Comics', - writers: 'Writers', - culture: 'Culture', - sports: 'Sports', - pets: 'Pets', - animals: 'Animals', - books: 'Books', - education: 'Education', - climate: 'Climate', - science: 'Science', - politics: 'Politics', - fitness: 'Fitness', - tech: 'Tech', - dev: 'Software Dev', - comedy: 'Comedy', - gaming: 'Video Games', - food: 'Food', - cooking: 'Cooking', -} - -export type ApiResponseMap = { - interests: string[] - suggestedAccountDids: { - [key: string]: string[] - } - suggestedFeedUris: { - [key: string]: string[] - } -} diff --git a/src/screens/Onboarding/StepInterests/index.tsx b/src/screens/Onboarding/StepInterests/index.tsx index 6f60991d5..5440dcd2b 100644 --- a/src/screens/Onboarding/StepInterests/index.tsx +++ b/src/screens/Onboarding/StepInterests/index.tsx @@ -17,17 +17,14 @@ import {getAgent} from '#/state/session' import {useAnalytics} from '#/lib/analytics/analytics' import {Text} from '#/components/Typography' import {useOnboardingDispatch} from '#/state/shell' +import {capitalize} from '#/lib/strings/capitalize' -import {Context} from '#/screens/Onboarding/state' +import {Context, ApiResponseMap} from '#/screens/Onboarding/state' import { Title, Description, OnboardingControls, } from '#/screens/Onboarding/Layout' -import { - ApiResponseMap, - INTEREST_TO_DISPLAY_NAME, -} from '#/screens/Onboarding/StepInterests/data' import {InterestButton} from '#/screens/Onboarding/StepInterests/InterestButton' import {IconCircle} from '#/screens/Onboarding/IconCircle' @@ -36,7 +33,7 @@ export function StepInterests() { const t = useTheme() const {track} = useAnalytics() const {gtMobile} = useBreakpoints() - const {state, dispatch} = React.useContext(Context) + const {state, dispatch, interestsDisplayNames} = React.useContext(Context) const [saving, setSaving] = React.useState(false) const [interests, setInterests] = React.useState<string[]>( state.interestsStepResults.selectedInterests.map(i => i), @@ -202,7 +199,9 @@ export function StepInterests() { <Toggle.Item key={interest} name={interest} - label={INTEREST_TO_DISPLAY_NAME[interest]}> + label={ + interestsDisplayNames[interest] || capitalize(interest) + }> <InterestButton interest={interest} /> </Toggle.Item> ))} diff --git a/src/screens/Onboarding/StepSuggestedAccounts/index.tsx b/src/screens/Onboarding/StepSuggestedAccounts/index.tsx index d3831791c..cf1f82559 100644 --- a/src/screens/Onboarding/StepSuggestedAccounts/index.tsx +++ b/src/screens/Onboarding/StepSuggestedAccounts/index.tsx @@ -14,6 +14,7 @@ import {Loader} from '#/components/Loader' import * as Toggle from '#/components/forms/Toggle' import {useModerationOpts} from '#/state/queries/preferences' import {useAnalytics} from '#/lib/analytics/analytics' +import {capitalize} from '#/lib/strings/capitalize' import {Context} from '#/screens/Onboarding/state' import { @@ -25,7 +26,6 @@ import { SuggestedAccountCard, SuggestedAccountCardPlaceholder, } from '#/screens/Onboarding/StepSuggestedAccounts/SuggestedAccountCard' -import {INTEREST_TO_DISPLAY_NAME} from '#/screens/Onboarding/StepInterests/data' import {aggregateInterestItems} from '#/screens/Onboarding/util' import {IconCircle} from '#/screens/Onboarding/IconCircle' @@ -70,7 +70,7 @@ export function Inner({ export function StepSuggestedAccounts() { const {_} = useLingui() const {track} = useAnalytics() - const {state, dispatch} = React.useContext(Context) + const {state, dispatch, interestsDisplayNames} = React.useContext(Context) const {gtMobile} = useBreakpoints() const suggestedDids = React.useMemo(() => { return aggregateInterestItems( @@ -93,10 +93,10 @@ export function StepSuggestedAccounts() { const interestsText = React.useMemo(() => { const i = state.interestsStepResults.selectedInterests.map( - i => INTEREST_TO_DISPLAY_NAME[i], + i => interestsDisplayNames[i] || capitalize(i), ) return i.join(', ') - }, [state.interestsStepResults.selectedInterests]) + }, [state.interestsStepResults.selectedInterests, interestsDisplayNames]) const handleContinue = React.useCallback(async () => { setSaving(true) diff --git a/src/screens/Onboarding/StepTopicalFeeds.tsx b/src/screens/Onboarding/StepTopicalFeeds.tsx index 516c18e6e..ef77cc128 100644 --- a/src/screens/Onboarding/StepTopicalFeeds.tsx +++ b/src/screens/Onboarding/StepTopicalFeeds.tsx @@ -10,6 +10,7 @@ import {Button, ButtonIcon, ButtonText} from '#/components/Button' import * as Toggle from '#/components/forms/Toggle' import {Loader} from '#/components/Loader' import {useAnalytics} from '#/lib/analytics/analytics' +import {capitalize} from '#/lib/strings/capitalize' import {Context} from '#/screens/Onboarding/state' import { @@ -18,14 +19,13 @@ import { OnboardingControls, } from '#/screens/Onboarding/Layout' import {FeedCard} from '#/screens/Onboarding/StepAlgoFeeds/FeedCard' -import {INTEREST_TO_DISPLAY_NAME} from '#/screens/Onboarding/StepInterests/data' import {aggregateInterestItems} from '#/screens/Onboarding/util' import {IconCircle} from '#/screens/Onboarding/IconCircle' export function StepTopicalFeeds() { const {_} = useLingui() const {track} = useAnalytics() - const {state, dispatch} = React.useContext(Context) + const {state, dispatch, interestsDisplayNames} = React.useContext(Context) const [selectedFeedUris, setSelectedFeedUris] = React.useState<string[]>([]) const [saving, setSaving] = React.useState(false) const suggestedFeedUris = React.useMemo(() => { @@ -38,10 +38,10 @@ export function StepTopicalFeeds() { const interestsText = React.useMemo(() => { const i = state.interestsStepResults.selectedInterests.map( - i => INTEREST_TO_DISPLAY_NAME[i], + i => interestsDisplayNames[i] || capitalize(i), ) return i.join(', ') - }, [state.interestsStepResults.selectedInterests]) + }, [state.interestsStepResults.selectedInterests, interestsDisplayNames]) const saveFeeds = React.useCallback(async () => { setSaving(true) diff --git a/src/screens/Onboarding/index.tsx b/src/screens/Onboarding/index.tsx index a4eb04012..9e5029e87 100644 --- a/src/screens/Onboarding/index.tsx +++ b/src/screens/Onboarding/index.tsx @@ -1,4 +1,6 @@ import React from 'react' +import {useLingui} from '@lingui/react' +import {msg} from '@lingui/macro' import {Portal} from '#/components/Portal' @@ -13,13 +15,44 @@ import {StepFinished} from '#/screens/Onboarding/StepFinished' import {StepModeration} from '#/screens/Onboarding/StepModeration' export function Onboarding() { + const {_} = useLingui() const [state, dispatch] = React.useReducer(reducer, {...initialState}) + const interestsDisplayNames = React.useMemo(() => { + return { + news: _(msg`News`), + journalism: _(msg`Journalism`), + nature: _(msg`Nature`), + art: _(msg`Art`), + comics: _(msg`Comics`), + writers: _(msg`Writers`), + culture: _(msg`Culture`), + sports: _(msg`Sports`), + pets: _(msg`Pets`), + animals: _(msg`Animals`), + books: _(msg`Books`), + education: _(msg`Education`), + climate: _(msg`Climate`), + science: _(msg`Science`), + politics: _(msg`Politics`), + fitness: _(msg`Fitness`), + tech: _(msg`Tech`), + dev: _(msg`Software Dev`), + comedy: _(msg`Comedy`), + gaming: _(msg`Video Games`), + food: _(msg`Food`), + cooking: _(msg`Cooking`), + } + }, [_]) + return ( <Portal> <OnboardingControls.Provider> <Context.Provider - value={React.useMemo(() => ({state, dispatch}), [state, dispatch])}> + value={React.useMemo( + () => ({state, dispatch, interestsDisplayNames}), + [state, dispatch, interestsDisplayNames], + )}> <Layout> {state.activeStep === 'interests' && <StepInterests />} {state.activeStep === 'suggestedAccounts' && ( diff --git a/src/screens/Onboarding/state.ts b/src/screens/Onboarding/state.ts index 164c2f5f3..bd8205ca2 100644 --- a/src/screens/Onboarding/state.ts +++ b/src/screens/Onboarding/state.ts @@ -1,6 +1,5 @@ import React from 'react' -import {ApiResponseMap} from '#/screens/Onboarding/StepInterests/data' import {logger} from '#/logger' export type OnboardingState = { @@ -59,6 +58,16 @@ export type OnboardingAction = feedUris: string[] } +export type ApiResponseMap = { + interests: string[] + suggestedAccountDids: { + [key: string]: string[] + } + suggestedFeedUris: { + [key: string]: string[] + } +} + export const initialState: OnboardingState = { hasPrev: false, totalSteps: 7, @@ -84,12 +93,41 @@ export const initialState: OnboardingState = { }, } +export const INTEREST_TO_DISPLAY_NAME_DEFAULTS: { + [key: string]: string +} = { + news: 'News', + journalism: 'Journalism', + nature: 'Nature', + art: 'Art', + comics: 'Comics', + writers: 'Writers', + culture: 'Culture', + sports: 'Sports', + pets: 'Pets', + animals: 'Animals', + books: 'Books', + education: 'Education', + climate: 'Climate', + science: 'Science', + politics: 'Politics', + fitness: 'Fitness', + tech: 'Tech', + dev: 'Software Dev', + comedy: 'Comedy', + gaming: 'Video Games', + food: 'Food', + cooking: 'Cooking', +} + export const Context = React.createContext<{ state: OnboardingState dispatch: React.Dispatch<OnboardingAction> + interestsDisplayNames: {[key: string]: string} }>({ state: {...initialState}, dispatch: () => {}, + interestsDisplayNames: INTEREST_TO_DISPLAY_NAME_DEFAULTS, }) export function reducer( diff --git a/src/screens/Onboarding/util.ts b/src/screens/Onboarding/util.ts index 2a709a67b..eae661aa4 100644 --- a/src/screens/Onboarding/util.ts +++ b/src/screens/Onboarding/util.ts @@ -31,7 +31,15 @@ export function aggregateInterestItems( const selected = interests.length const all = interests .map(i => { - const suggestions = shuffle(map[i]) + // suggestions from server + const rawSuggestions = map[i] + + // safeguard against a missing interest->suggestion mapping + if (!rawSuggestions || !rawSuggestions.length) { + return [] + } + + const suggestions = shuffle(rawSuggestions) if (selected === 1) { return suggestions // return all diff --git a/src/state/modals/index.tsx b/src/state/modals/index.tsx index 45856e108..ab710a3d0 100644 --- a/src/state/modals/index.tsx +++ b/src/state/modals/index.tsx @@ -6,7 +6,7 @@ import {Image as RNImage} from 'react-native-image-crop-picker' import {ImageModel} from '#/state/models/media/image' import {GalleryModel} from '#/state/models/media/gallery' import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' -import {EmbedPlayerSource} from '#/lib/strings/embed-player.ts' +import {EmbedPlayerSource} from '#/lib/strings/embed-player' import {ThreadgateSetting} from '../queries/threadgate' export interface ConfirmModal { diff --git a/src/state/queries/feed.ts b/src/state/queries/feed.ts index 4acc7179a..67294ece2 100644 --- a/src/state/queries/feed.ts +++ b/src/state/queries/feed.ts @@ -136,6 +136,10 @@ export function getFeedTypeFromUri(uri: string) { return pathname.includes(feedSourceNSIDs.feed) ? 'feed' : 'list' } +export function getAvatarTypeFromUri(uri: string) { + return getFeedTypeFromUri(uri) === 'feed' ? 'algo' : 'list' +} + export function useFeedSourceInfoQuery({uri}: {uri: string}) { const type = getFeedTypeFromUri(uri) diff --git a/src/state/queries/notifications/util.ts b/src/state/queries/notifications/util.ts index 411a0f791..e53a07258 100644 --- a/src/state/queries/notifications/util.ts +++ b/src/state/queries/notifications/util.ts @@ -6,6 +6,7 @@ import { AppBskyFeedPost, AppBskyFeedRepost, AppBskyFeedLike, + AppBskyEmbedRecord, } from '@atproto/api' import {moderatePost_wrapped as moderatePost} from '#/lib/moderatePost_wrapped' import chunk from 'lodash.chunk' @@ -110,8 +111,6 @@ function shouldFilterNotif( return true } } - // TODO: thread muting is not being applied - // (this requires fetching the post) return false } @@ -221,10 +220,44 @@ function getSubjectUri( } } -function isThreadMuted(notif: FeedNotification, mutes: string[]): boolean { - if (!notif.subject) { - return false +export function isThreadMuted(notif: FeedNotification, threadMutes: string[]) { + // If there's a subject we want to use that. This will always work on the notifications tab + if (notif.subject) { + const record = notif.subject.record as AppBskyFeedPost.Record + // Check for a quote record + if ( + (record.reply && threadMutes.includes(record.reply.root.uri)) || + (notif.subject.uri && threadMutes.includes(notif.subject.uri)) + ) { + return true + } else if ( + AppBskyEmbedRecord.isMain(record.embed) && + threadMutes.includes(record.embed.record.uri) + ) { + return true + } + } else { + // Otherwise we just do the best that we can + const record = notif.notification.record + if (AppBskyFeedPost.isRecord(record)) { + if (record.reply && threadMutes.includes(record.reply.root.uri)) { + // We can always filter replies + return true + } else if ( + AppBskyEmbedRecord.isMain(record.embed) && + threadMutes.includes(record.embed.record.uri) + ) { + // We can also filter quotes if the quoted post is the root + return true + } + } else if ( + AppBskyFeedRepost.isRecord(record) && + threadMutes.includes(record.subject.uri) + ) { + // Finally we can filter reposts, again if the post is the root + return true + } } - const record = notif.subject.record as AppBskyFeedPost.Record // assured in fetchSubjects() - return mutes.includes(record.reply?.root.uri || notif.subject.uri) + + return false } diff --git a/src/state/queries/post-feed.ts b/src/state/queries/post-feed.ts index 82acf3974..b422fa8fe 100644 --- a/src/state/queries/post-feed.ts +++ b/src/state/queries/post-feed.ts @@ -28,6 +28,7 @@ import {getModerationOpts} from '#/state/queries/preferences/moderation' import {KnownError} from '#/view/com/posts/FeedErrorMessage' import {embedViewRecordToPostView, getEmbeddedPost} from './util' import {useModerationOpts} from './preferences' +import {queryClient} from 'lib/react-query' type ActorDid = string type AuthorFilter = @@ -444,3 +445,15 @@ function assertSomePostsPassModeration(feed: AppBskyFeedDefs.FeedViewPost[]) { throw new Error(KnownError.FeedNSFPublic) } } + +export function resetProfilePostsQueries(did: string, timeout = 0) { + setTimeout(() => { + queryClient.resetQueries({ + predicate: query => + !!( + query.queryKey[0] === 'post-feed' && + (query.queryKey[1] as string)?.includes(did) + ), + }) + }, timeout) +} diff --git a/src/state/queries/profile.ts b/src/state/queries/profile.ts index 74be99330..affb8295c 100644 --- a/src/state/queries/profile.ts +++ b/src/state/queries/profile.ts @@ -17,6 +17,7 @@ import {updateProfileShadow} from '../cache/profile-shadow' import {uploadBlob} from '#/lib/api' import {until} from '#/lib/async/until' import {Shadow} from '#/state/cache/types' +import {resetProfilePostsQueries} from '#/state/queries/post-feed' import {useToggleMutationQueue} from '#/lib/hooks/useToggleMutationQueue' import {RQKEY as RQKEY_MY_MUTED} from './my-muted-accounts' import {RQKEY as RQKEY_MY_BLOCKED} from './my-blocked-accounts' @@ -26,16 +27,19 @@ import {track} from '#/lib/analytics/analytics' export const RQKEY = (did: string) => ['profile', did] export const profilesQueryKey = (handles: string[]) => ['profiles', handles] -export function useProfileQuery({did}: {did: string | undefined}) { - const {currentAccount} = useSession() - const isCurrentAccount = did === currentAccount?.did - +export function useProfileQuery({ + did, + staleTime = STALE.SECONDS.FIFTEEN, +}: { + did: string | undefined + staleTime?: number +}) { return useQuery({ // WARNING // this staleTime is load-bearing // if you remove it, the UI infinite-loops // -prf - staleTime: isCurrentAccount ? STALE.SECONDS.THIRTY : STALE.MINUTES.FIVE, + staleTime, refetchOnWindowFocus: true, queryKey: RQKEY(did || ''), queryFn: async () => { @@ -375,8 +379,9 @@ function useProfileBlockMutation() { {subject: did, createdAt: new Date().toISOString()}, ) }, - onSuccess() { + onSuccess(_, {did}) { queryClient.invalidateQueries({queryKey: RQKEY_MY_BLOCKED()}) + resetProfilePostsQueries(did, 1000) }, }) } @@ -394,6 +399,9 @@ function useProfileUnblockMutation() { rkey, }) }, + onSuccess(_, {did}) { + resetProfilePostsQueries(did, 1000) + }, }) } diff --git a/src/view/com/modals/AltImage.tsx b/src/view/com/modals/AltImage.tsx index 5156511d6..7671c29c8 100644 --- a/src/view/com/modals/AltImage.tsx +++ b/src/view/com/modals/AltImage.tsx @@ -4,7 +4,9 @@ import { StyleSheet, TouchableOpacity, View, + TextInput as RNTextInput, useWindowDimensions, + ScrollView as RNScrollView, } from 'react-native' import {ScrollView, TextInput} from './util' import {Image} from 'expo-image' @@ -13,6 +15,7 @@ import {gradients, s} from 'lib/styles' import {enforceLen} from 'lib/strings/helpers' import {MAX_ALT_TEXT} from 'lib/constants' import {useTheme} from 'lib/ThemeContext' +import {useIsKeyboardVisible} from 'lib/hooks/useIsKeyboardVisible' import {Text} from '../util/text/Text' import LinearGradient from 'react-native-linear-gradient' import {isWeb} from 'platform/detection' @@ -34,6 +37,24 @@ export function Component({image}: Props) { const [altText, setAltText] = useState(image.altText) const windim = useWindowDimensions() const {closeModal} = useModalControls() + const inputRef = React.useRef<RNTextInput>(null) + const scrollViewRef = React.useRef<RNScrollView>(null) + const keyboardShown = useIsKeyboardVisible() + + // Autofocus hack when we open the modal. We have to wait for the animation to complete first + React.useEffect(() => { + setTimeout(() => { + inputRef.current?.focus() + }, 500) + }, []) + + // We'd rather be at the bottom here so that we can easily dismiss the modal instead of having to scroll + // (especially on android, it acts weird) + React.useEffect(() => { + if (keyboardShown[0]) { + scrollViewRef.current?.scrollToEnd() + } + }, [keyboardShown]) const imageStyles = useMemo<ImageStyle>(() => { const maxWidth = isWeb ? 450 : windim.width @@ -71,6 +92,7 @@ export function Component({image}: Props) { testID="altTextImageModal" style={[pal.view, styles.scrollContainer]} keyboardShouldPersistTaps="always" + ref={scrollViewRef} nativeID="imageAltText"> <View style={styles.scrollInner}> <View style={[pal.viewLight, styles.imageContainer]}> @@ -97,7 +119,8 @@ export function Component({image}: Props) { accessibilityLabel={_(msg`Image alt text`)} accessibilityHint="" accessibilityLabelledBy="imageAltText" - autoFocus + // @ts-ignore This is fine, type is weird on the BottomSheetTextInput + ref={inputRef} /> <View style={styles.buttonControls}> <TouchableOpacity diff --git a/src/view/com/modals/ProfilePreview.tsx b/src/view/com/modals/ProfilePreview.tsx index 77e68db70..88b0df71d 100644 --- a/src/view/com/modals/ProfilePreview.tsx +++ b/src/view/com/modals/ProfilePreview.tsx @@ -27,12 +27,12 @@ export function Component({did}: {did: string}) { data: profile, error: profileError, refetch: refetchProfile, - isFetching: isFetchingProfile, + isLoading: isLoadingProfile, } = useProfileQuery({ did: did, }) - if (isFetchingProfile || !moderationOpts) { + if (isLoadingProfile || !moderationOpts) { return ( <CenteredView style={[pal.view, s.flex1]}> <ProfileHeader diff --git a/src/view/com/notifications/FeedItem.tsx b/src/view/com/notifications/FeedItem.tsx index 0dfac2a83..f037097df 100644 --- a/src/view/com/notifications/FeedItem.tsx +++ b/src/view/com/notifications/FeedItem.tsx @@ -167,9 +167,7 @@ let FeedItem = ({ icon = 'user-plus' iconStyle = [s.blue3 as FontAwesomeIconStyle] } else if (item.type === 'feedgen-like') { - action = item.subjectUri - ? _(msg`liked your custom feed '${new AtUri(item.subjectUri).rkey}'`) - : _(msg`liked your custom feed`) + action = _(msg`liked your custom feed`) icon = 'HeartIconSolid' iconStyle = [ s.likeColor as FontAwesomeIconStyle, diff --git a/src/view/com/post-thread/PostThreadItem.tsx b/src/view/com/post-thread/PostThreadItem.tsx index 1cc2bb824..c5912376e 100644 --- a/src/view/com/post-thread/PostThreadItem.tsx +++ b/src/view/com/post-thread/PostThreadItem.tsx @@ -775,6 +775,7 @@ const useStyles = () => { }, postTextLargeContainer: { paddingHorizontal: 0, + paddingRight: 0, paddingBottom: 10, }, translateLink: { diff --git a/src/view/com/util/UserInfoText.tsx b/src/view/com/util/UserInfoText.tsx index e5d2ceb03..9cb9997f6 100644 --- a/src/view/com/util/UserInfoText.tsx +++ b/src/view/com/util/UserInfoText.tsx @@ -9,6 +9,7 @@ import {sanitizeDisplayName} from 'lib/strings/display-names' import {sanitizeHandle} from 'lib/strings/handles' import {makeProfileLink} from 'lib/routes/links' import {useProfileQuery} from '#/state/queries/profile' +import {STALE} from '#/state/queries' export function UserInfoText({ type = 'md', @@ -29,7 +30,10 @@ export function UserInfoText({ attr = attr || 'handle' failed = failed || 'user' - const {data: profile, isError} = useProfileQuery({did}) + const {data: profile, isError} = useProfileQuery({ + did, + staleTime: STALE.INFINITY, + }) let inner if (isError) { diff --git a/src/view/screens/Feeds.tsx b/src/view/screens/Feeds.tsx index 9b3fd6181..6651084bd 100644 --- a/src/view/screens/Feeds.tsx +++ b/src/view/screens/Feeds.tsx @@ -30,6 +30,7 @@ import { useFeedSourceInfoQuery, useGetPopularFeedsQuery, useSearchPopularFeedsMutation, + getAvatarTypeFromUri, } from '#/state/queries/feed' import {cleanError} from 'lib/strings/errors' import {useComposerControls} from '#/state/shell/composer' @@ -555,6 +556,7 @@ function SavedFeed({feedUri}: {feedUri: string}) { const pal = usePalette('default') const {isMobile} = useWebMediaQueries() const {data: info, error} = useFeedSourceInfoQuery({uri: feedUri}) + const typeAvatar = getAvatarTypeFromUri(feedUri) if (!info) return ( @@ -582,7 +584,7 @@ function SavedFeed({feedUri}: {feedUri: string}) { /> </View> ) : ( - <UserAvatar type="algo" size={28} avatar={info.avatar} /> + <UserAvatar type={typeAvatar} size={28} avatar={info.avatar} /> )} <View style={{flex: 1, flexDirection: 'row', gap: 8, alignItems: 'center'}}> diff --git a/src/view/screens/Profile.tsx b/src/view/screens/Profile.tsx index 7fc4d7a20..6d0f15d81 100644 --- a/src/view/screens/Profile.tsx +++ b/src/view/screens/Profile.tsx @@ -21,7 +21,10 @@ import {useAnalytics} from 'lib/analytics/analytics' import {ComposeIcon2} from 'lib/icons' import {useSetTitle} from 'lib/hooks/useSetTitle' import {combinedDisplayName} from 'lib/strings/display-names' -import {FeedDescriptor} from '#/state/queries/post-feed' +import { + FeedDescriptor, + resetProfilePostsQueries, +} from '#/state/queries/post-feed' import {useResolveDidQuery} from '#/state/queries/resolve-uri' import {useProfileQuery} from '#/state/queries/profile' import {useProfileShadow} from '#/state/cache/profile-shadow' @@ -55,13 +58,13 @@ export function ProfileScreen({route}: Props) { data: resolvedDid, error: resolveError, refetch: refetchDid, - isInitialLoading: isInitialLoadingDid, + isLoading: isLoadingDid, } = useResolveDidQuery(name) const { data: profile, error: profileError, refetch: refetchProfile, - isInitialLoading: isInitialLoadingProfile, + isLoading: isLoadingProfile, } = useProfileQuery({ did: resolvedDid, }) @@ -74,7 +77,14 @@ export function ProfileScreen({route}: Props) { } }, [resolveError, refetchDid, refetchProfile]) - if (isInitialLoadingDid || isInitialLoadingProfile || !moderationOpts) { + // When we open the profile, we want to reset the posts query if we are blocked. + React.useEffect(() => { + if (resolvedDid && profile?.viewer?.blockedBy) { + resetProfilePostsQueries(resolvedDid) + } + }, [profile?.viewer?.blockedBy, resolvedDid]) + + if (isLoadingDid || isLoadingProfile || !moderationOpts) { return ( <CenteredView> <ProfileHeader |