diff --git a/src/state/models/content/list.ts b/src/state/models/content/list.ts index 0331f58b..8fb9f4b5 100644 --- a/src/state/models/content/list.ts +++ b/src/state/models/content/list.ts @@ -290,6 +290,7 @@ export class ListModel { }) } + /* dont await */ this.rootStore.preferences.removeSavedFeed(this.uri) this.rootStore.emitListDeleted(this.uri) } diff --git a/src/state/models/feeds/posts.ts b/src/state/models/feeds/posts.ts index 169eedac..3c580aca 100644 --- a/src/state/models/feeds/posts.ts +++ b/src/state/models/feeds/posts.ts @@ -25,6 +25,17 @@ import {MergeFeedAPI} from 'lib/api/feed/merge' const PAGE_SIZE = 30 +type FeedType = 'home' | 'following' | 'author' | 'custom' | 'likes' | 'list' + +export enum KnownError { + FeedgenDoesNotExist, + FeedgenMisconfigured, + FeedgenBadResponse, + FeedgenOffline, + FeedgenUnknown, + Unknown, +} + type Options = { /** * Formats the feed in a flat array with no threading of replies, just @@ -49,6 +60,7 @@ export class PostsFeedModel { isBlocking = false isBlockedBy = false error = '' + knownError: KnownError | undefined loadMoreError = '' params: QueryParams hasMore = true @@ -69,13 +81,7 @@ export class PostsFeedModel { constructor( public rootStore: RootStoreModel, - public feedType: - | 'home' - | 'following' - | 'author' - | 'custom' - | 'likes' - | 'list', + public feedType: FeedType, params: QueryParams, options?: Options, ) { @@ -305,6 +311,7 @@ export class PostsFeedModel { this.isLoading = true this.isRefreshing = isRefreshing this.error = '' + this.knownError = undefined } _xIdle(error?: any, loadMoreError?: any) { @@ -314,6 +321,7 @@ export class PostsFeedModel { this.isBlocking = error instanceof GetAuthorFeed.BlockedActorError this.isBlockedBy = error instanceof GetAuthorFeed.BlockedByActorError this.error = cleanError(error) + this.knownError = detectKnownError(this.feedType, error) this.loadMoreError = cleanError(loadMoreError) if (error) { this.rootStore.log.error('Posts feed request failed', error) @@ -383,3 +391,39 @@ export class PostsFeedModel { }) } } + +function detectKnownError( + feedType: FeedType, + error: any, +): KnownError | undefined { + if (!error) { + return undefined + } + if (typeof error !== 'string') { + error = error.toString() + } + if (feedType !== 'custom') { + return KnownError.Unknown + } + if (error.includes('could not find feed')) { + return KnownError.FeedgenDoesNotExist + } + if (error.includes('feed unavailable')) { + return KnownError.FeedgenOffline + } + if (error.includes('invalid did document')) { + return KnownError.FeedgenMisconfigured + } + if (error.includes('could not resolve did document')) { + return KnownError.FeedgenMisconfigured + } + if ( + error.includes('invalid feed generator service details in did document') + ) { + return KnownError.FeedgenMisconfigured + } + if (error.includes('feed provided an invalid response')) { + return KnownError.FeedgenBadResponse + } + return KnownError.FeedgenUnknown +} diff --git a/src/view/com/posts/Feed.tsx b/src/view/com/posts/Feed.tsx index 591afe3a..0578036d 100644 --- a/src/view/com/posts/Feed.tsx +++ b/src/view/com/posts/Feed.tsx @@ -10,7 +10,7 @@ import { } from 'react-native' import {FlatList} from '../util/Views' import {PostFeedLoadingPlaceholder} from '../util/LoadingPlaceholder' -import {ErrorMessage} from '../util/error/ErrorMessage' +import {FeedErrorMessage} from './FeedErrorMessage' import {PostsFeedModel} from 'state/models/feeds/posts' import {FeedSlice} from './FeedSlice' import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn' @@ -125,10 +125,7 @@ export const Feed = observer(function Feed({ return renderEmptyState() } else if (item === ERROR_ITEM) { return ( - + ) } else if (item === LOAD_MORE_ERROR_ITEM) { return ( diff --git a/src/view/com/posts/FeedErrorMessage.tsx b/src/view/com/posts/FeedErrorMessage.tsx new file mode 100644 index 00000000..51c735e3 --- /dev/null +++ b/src/view/com/posts/FeedErrorMessage.tsx @@ -0,0 +1,119 @@ +import React from 'react' +import {View} from 'react-native' +import {AtUri, AppBskyFeedGetFeed as GetCustomFeed} from '@atproto/api' +import {PostsFeedModel, KnownError} from 'state/models/feeds/posts' +import {Text} from '../util/text/Text' +import {Button} from '../util/forms/Button' +import * as Toast from '../util/Toast' +import {ErrorMessage} from '../util/error/ErrorMessage' +import {usePalette} from 'lib/hooks/usePalette' +import {useNavigation} from '@react-navigation/native' +import {NavigationProp} from 'lib/routes/types' +import {useStores} from 'state/index' + +const MESSAGES = { + [KnownError.Unknown]: '', + [KnownError.FeedgenDoesNotExist]: `Hmmm, we're having trouble finding this feed. It may have been deleted.`, + [KnownError.FeedgenMisconfigured]: + 'Hmm, the feed server appears to be misconfigured. Please let the feed owner know about this issue.', + [KnownError.FeedgenBadResponse]: + 'Hmm, the feed server gave a bad response. Please let the feed owner know about this issue.', + [KnownError.FeedgenOffline]: + 'Hmm, the feed server appears to be offline. Please let the feed owner know about this issue.', + [KnownError.FeedgenUnknown]: + 'Hmm, some kind of issue occured when contacting the feed server. Please let the feed owner know about this issue.', +} + +export function FeedErrorMessage({ + feed, + onPressTryAgain, +}: { + feed: PostsFeedModel + onPressTryAgain: () => void +}) { + if ( + typeof feed.knownError === 'undefined' || + feed.knownError === KnownError.Unknown + ) { + return ( + + ) + } + + return +} + +function FeedgenErrorMessage({ + feed, + knownError, +}: { + feed: PostsFeedModel + knownError: KnownError +}) { + const pal = usePalette('default') + const store = useStores() + const navigation = useNavigation() + const msg = MESSAGES[knownError] + const uri = (feed.params as GetCustomFeed.QueryParams).feed + const [ownerDid] = safeParseFeedgenUri(uri) + + const onViewProfile = React.useCallback(() => { + navigation.navigate('Profile', {name: ownerDid}) + }, [navigation, ownerDid]) + + const onRemoveFeed = React.useCallback(async () => { + store.shell.openModal({ + name: 'confirm', + title: 'Remove feed', + message: 'Remove this feed from your saved feeds?', + async onPressConfirm() { + try { + await store.preferences.removeSavedFeed(uri) + } catch (err) { + Toast.show( + 'There was an an issue removing this feed. Please check your internet connection and try again.', + ) + store.log.error('Failed to remove feed', {err}) + } + }, + onPressCancel() { + store.shell.closeModal() + }, + }) + }, [store, uri]) + + return ( + + {msg} + + {knownError === KnownError.FeedgenDoesNotExist && ( + - - + ) } @@ -289,7 +247,12 @@ export const ProfileListScreenInner = observer( ) } - return
+ return ( + +
+ {list.error && } + + ) }, ) @@ -532,7 +495,7 @@ const Header = observer(function HeaderImpl({ isOwner={list.isOwner} creator={list.data?.creator} avatarType="list"> - {list.isCuratelist ? ( + {list.isCuratelist || list.isPinned ? ( + + + ) +} + const styles = StyleSheet.create({ btn: { flexDirection: 'row',