about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/state/models/feed-view.ts7
-rw-r--r--src/state/models/notifications-view.ts3
-rw-r--r--src/view/com/notifications/Feed.tsx12
-rw-r--r--src/view/com/post-thread/PostRepostedBy.tsx21
-rw-r--r--src/view/com/post-thread/PostThread.tsx8
-rw-r--r--src/view/com/post-thread/PostVotedBy.tsx12
-rw-r--r--src/view/com/posts/Feed.tsx18
-rw-r--r--src/view/com/profile/ProfileFollowers.tsx12
-rw-r--r--src/view/com/profile/ProfileFollows.tsx12
-rw-r--r--src/view/com/profile/ProfileMembers.tsx14
-rw-r--r--src/view/com/util/ErrorMessage.tsx51
-rw-r--r--src/view/lib/strings.ts7
-rw-r--r--src/view/lib/styles.ts1
-rw-r--r--src/view/screens/Home.tsx4
-rw-r--r--src/view/screens/Notifications.tsx6
15 files changed, 156 insertions, 32 deletions
diff --git a/src/state/models/feed-view.ts b/src/state/models/feed-view.ts
index 905f7af7e..1c58a44d3 100644
--- a/src/state/models/feed-view.ts
+++ b/src/state/models/feed-view.ts
@@ -3,6 +3,7 @@ import * as GetTimeline from '../../third-party/api/src/client/types/app/bsky/fe
 import * as GetAuthorFeed from '../../third-party/api/src/client/types/app/bsky/feed/getAuthorFeed'
 import {RootStoreModel} from './root-store'
 import * as apilib from '../lib/api'
+import {cleanError} from '../../view/lib/strings'
 
 export class FeedItemMyStateModel {
   repost?: string
@@ -254,7 +255,7 @@ export class FeedModel {
     this.isLoading = false
     this.isRefreshing = false
     this.hasLoaded = true
-    this.error = err
+    this.error = cleanError(err)
   }
 
   // loader functions
@@ -282,7 +283,7 @@ export class FeedModel {
       this._replaceAll(res)
       this._xIdle()
     } catch (e: any) {
-      this._xIdle(`Failed to load feed: ${e.toString()}`)
+      this._xIdle(e.toString())
     }
   }
 
@@ -293,7 +294,7 @@ export class FeedModel {
       this._prependAll(res)
       this._xIdle()
     } catch (e: any) {
-      this._xIdle(`Failed to load feed: ${e.toString()}`)
+      this._xIdle(e.toString())
     }
   }
 
diff --git a/src/state/models/notifications-view.ts b/src/state/models/notifications-view.ts
index 72cc17f6b..3a91eb322 100644
--- a/src/state/models/notifications-view.ts
+++ b/src/state/models/notifications-view.ts
@@ -4,6 +4,7 @@ import {RootStoreModel} from './root-store'
 import {Declaration} from './_common'
 import {hasProp} from '../lib/type-guards'
 import {APP_BSKY_GRAPH} from '../../third-party/api'
+import {cleanError} from '../../view/lib/strings'
 
 const UNGROUPABLE_REASONS = ['trend', 'assertion']
 
@@ -215,7 +216,7 @@ export class NotificationsViewModel {
     this.isLoading = false
     this.isRefreshing = false
     this.hasLoaded = true
-    this.error = err
+    this.error = cleanError(err)
   }
 
   // loader functions
diff --git a/src/view/com/notifications/Feed.tsx b/src/view/com/notifications/Feed.tsx
index a3cac0cdf..2b7bb878f 100644
--- a/src/view/com/notifications/Feed.tsx
+++ b/src/view/com/notifications/Feed.tsx
@@ -6,11 +6,14 @@ import {
   NotificationsViewItemModel,
 } from '../../../state/models/notifications-view'
 import {FeedItem} from './FeedItem'
+import {ErrorMessage} from '../util/ErrorMessage'
 
 export const Feed = observer(function Feed({
   view,
+  onPressTryAgain,
 }: {
   view: NotificationsViewModel
+  onPressTryAgain?: () => void
 }) {
   // TODO optimize renderItem or FeedItem, we're getting this notice from RN: -prf
   //   VirtualizedList: You have a large list that is slow to update - make sure your
@@ -30,7 +33,14 @@ export const Feed = observer(function Feed({
       {view.isLoading && !view.isRefreshing && !view.hasContent && (
         <Text>Loading...</Text>
       )}
-      {view.hasError && <Text>{view.error}</Text>}
+      {view.hasError && (
+        <ErrorMessage
+          dark
+          message={view.error}
+          style={{margin: 6}}
+          onPressTryAgain={onPressTryAgain}
+        />
+      )}
       {view.hasContent && (
         <FlatList
           data={view.notifications}
diff --git a/src/view/com/post-thread/PostRepostedBy.tsx b/src/view/com/post-thread/PostRepostedBy.tsx
index 53f0e0b35..ca5cc476a 100644
--- a/src/view/com/post-thread/PostRepostedBy.tsx
+++ b/src/view/com/post-thread/PostRepostedBy.tsx
@@ -1,18 +1,12 @@
 import React, {useState, useEffect} from 'react'
 import {observer} from 'mobx-react-lite'
-import {
-  ActivityIndicator,
-  FlatList,
-  Image,
-  StyleSheet,
-  Text,
-  View,
-} from 'react-native'
+import {ActivityIndicator, FlatList, StyleSheet, Text, View} from 'react-native'
 import {
   RepostedByViewModel,
   RepostedByViewItemModel,
 } from '../../../state/models/reposted-by-view'
 import {UserAvatar} from '../util/UserAvatar'
+import {ErrorMessage} from '../util/ErrorMessage'
 import {Link} from '../util/Link'
 import {useStores} from '../../../state'
 import {s, colors} from '../../lib/styles'
@@ -38,6 +32,10 @@ export const PostRepostedBy = observer(function PostRepostedBy({
       .catch(err => console.error('Failed to fetch reposted by', err))
   }, [uri, view?.params.uri, store])
 
+  const onRefresh = () => {
+    view?.refresh()
+  }
+
   // loading
   // =
   if (
@@ -57,7 +55,12 @@ export const PostRepostedBy = observer(function PostRepostedBy({
   if (view.hasError) {
     return (
       <View>
-        <Text>{view.error}</Text>
+        <ErrorMessage
+          dark
+          message={view.error}
+          style={{margin: 6}}
+          onPressTryAgain={onRefresh}
+        />
       </View>
     )
   }
diff --git a/src/view/com/post-thread/PostThread.tsx b/src/view/com/post-thread/PostThread.tsx
index 0349d3428..ee87a6bff 100644
--- a/src/view/com/post-thread/PostThread.tsx
+++ b/src/view/com/post-thread/PostThread.tsx
@@ -8,6 +8,7 @@ import {
 import {useStores} from '../../../state'
 import {SharePostModel} from '../../../state/models/shell-ui'
 import {PostThreadItem} from './PostThreadItem'
+import {ErrorMessage} from '../util/ErrorMessage'
 
 export const PostThread = observer(function PostThread({uri}: {uri: string}) {
   const store = useStores()
@@ -50,7 +51,12 @@ export const PostThread = observer(function PostThread({uri}: {uri: string}) {
   if (view.hasError) {
     return (
       <View>
-        <Text>{view.error}</Text>
+        <ErrorMessage
+          dark
+          message={view.error}
+          style={{margin: 6}}
+          onPressTryAgain={onRefresh}
+        />
       </View>
     )
   }
diff --git a/src/view/com/post-thread/PostVotedBy.tsx b/src/view/com/post-thread/PostVotedBy.tsx
index 596a6a1db..ad85d077f 100644
--- a/src/view/com/post-thread/PostVotedBy.tsx
+++ b/src/view/com/post-thread/PostVotedBy.tsx
@@ -6,6 +6,7 @@ import {
   VotesViewItemModel,
 } from '../../../state/models/votes-view'
 import {Link} from '../util/Link'
+import {ErrorMessage} from '../util/ErrorMessage'
 import {UserAvatar} from '../util/UserAvatar'
 import {useStores} from '../../../state'
 import {s, colors} from '../../lib/styles'
@@ -31,6 +32,10 @@ export const PostVotedBy = observer(function PostVotedBy({
     newView.setup().catch(err => console.error('Failed to fetch voted by', err))
   }, [uri, view?.params.uri, store])
 
+  const onRefresh = () => {
+    view?.refresh()
+  }
+
   // loading
   // =
   if (
@@ -50,7 +55,12 @@ export const PostVotedBy = observer(function PostVotedBy({
   if (view.hasError) {
     return (
       <View>
-        <Text>{view.error}</Text>
+        <ErrorMessage
+          dark
+          message={view.error}
+          style={{margin: 6}}
+          onPressTryAgain={onRefresh}
+        />
       </View>
     )
   }
diff --git a/src/view/com/posts/Feed.tsx b/src/view/com/posts/Feed.tsx
index dc341ddd5..75b48fad8 100644
--- a/src/view/com/posts/Feed.tsx
+++ b/src/view/com/posts/Feed.tsx
@@ -1,6 +1,7 @@
 import React, {MutableRefObject} from 'react'
 import {observer} from 'mobx-react-lite'
 import {Text, View, FlatList, StyleProp, ViewStyle} from 'react-native'
+import {ErrorMessage} from '../util/ErrorMessage'
 import {FeedModel, FeedItemModel} from '../../../state/models/feed-view'
 import {FeedItem} from './FeedItem'
 
@@ -8,10 +9,12 @@ export const Feed = observer(function Feed({
   feed,
   style,
   scrollElRef,
+  onPressTryAgain,
 }: {
   feed: FeedModel
   style?: StyleProp<ViewStyle>
   scrollElRef?: MutableRefObject<FlatList<any> | null>
+  onPressTryAgain?: () => void
 }) {
   // TODO optimize renderItem or FeedItem, we're getting this notice from RN: -prf
   //   VirtualizedList: You have a large list that is slow to update - make sure your
@@ -29,7 +32,14 @@ export const Feed = observer(function Feed({
       {feed.isLoading && !feed.isRefreshing && !feed.hasContent && (
         <Text>Loading...</Text>
       )}
-      {feed.hasError && <Text>{feed.error}</Text>}
+      {feed.hasError && (
+        <ErrorMessage
+          dark
+          message={feed.error}
+          style={{margin: 6}}
+          onPressTryAgain={onPressTryAgain}
+        />
+      )}
       {feed.hasContent && (
         <FlatList
           ref={scrollElRef}
@@ -41,7 +51,11 @@ export const Feed = observer(function Feed({
           onEndReached={onEndReached}
         />
       )}
-      {feed.isEmpty && <Text>This feed is empty!</Text>}
+      {feed.isEmpty && !feed.hasError && (
+        <View>
+          <Text>This feed is empty!</Text>
+        </View>
+      )}
     </View>
   )
 })
diff --git a/src/view/com/profile/ProfileFollowers.tsx b/src/view/com/profile/ProfileFollowers.tsx
index 65297ccc6..c4e9435d6 100644
--- a/src/view/com/profile/ProfileFollowers.tsx
+++ b/src/view/com/profile/ProfileFollowers.tsx
@@ -6,6 +6,7 @@ import {
   FollowerItem,
 } from '../../../state/models/user-followers-view'
 import {Link} from '../util/Link'
+import {ErrorMessage} from '../util/ErrorMessage'
 import {UserAvatar} from '../util/UserAvatar'
 import {useStores} from '../../../state'
 import {s, colors} from '../../lib/styles'
@@ -31,6 +32,10 @@ export const ProfileFollowers = observer(function ProfileFollowers({
       .catch(err => console.error('Failed to fetch user followers', err))
   }, [name, view?.params.user, store])
 
+  const onRefresh = () => {
+    view?.refresh()
+  }
+
   // loading
   // =
   if (
@@ -50,7 +55,12 @@ export const ProfileFollowers = observer(function ProfileFollowers({
   if (view.hasError) {
     return (
       <View>
-        <Text>{view.error}</Text>
+        <ErrorMessage
+          dark
+          message={view.error}
+          style={{margin: 6}}
+          onPressTryAgain={onRefresh}
+        />
       </View>
     )
   }
diff --git a/src/view/com/profile/ProfileFollows.tsx b/src/view/com/profile/ProfileFollows.tsx
index 1bdd18f66..9efd58f8c 100644
--- a/src/view/com/profile/ProfileFollows.tsx
+++ b/src/view/com/profile/ProfileFollows.tsx
@@ -7,6 +7,7 @@ import {
 } from '../../../state/models/user-follows-view'
 import {useStores} from '../../../state'
 import {Link} from '../util/Link'
+import {ErrorMessage} from '../util/ErrorMessage'
 import {UserAvatar} from '../util/UserAvatar'
 import {s, colors} from '../../lib/styles'
 
@@ -31,6 +32,10 @@ export const ProfileFollows = observer(function ProfileFollows({
       .catch(err => console.error('Failed to fetch user follows', err))
   }, [name, view?.params.user, store])
 
+  const onRefresh = () => {
+    view?.refresh()
+  }
+
   // loading
   // =
   if (
@@ -50,7 +55,12 @@ export const ProfileFollows = observer(function ProfileFollows({
   if (view.hasError) {
     return (
       <View>
-        <Text>{view.error}</Text>
+        <ErrorMessage
+          dark
+          message={view.error}
+          style={{margin: 6}}
+          onPressTryAgain={onRefresh}
+        />
       </View>
     )
   }
diff --git a/src/view/com/profile/ProfileMembers.tsx b/src/view/com/profile/ProfileMembers.tsx
index 11db02054..75ef18caa 100644
--- a/src/view/com/profile/ProfileMembers.tsx
+++ b/src/view/com/profile/ProfileMembers.tsx
@@ -1,8 +1,9 @@
 import React, {useState, useEffect} from 'react'
 import {observer} from 'mobx-react-lite'
-import {ActivityIndicator, FlatList, Text, View} from 'react-native'
+import {ActivityIndicator, FlatList, View} from 'react-native'
 import {MembersViewModel, MemberItem} from '../../../state/models/members-view'
 import {ProfileCard} from './ProfileCard'
+import {ErrorMessage} from '../util/ErrorMessage'
 import {useStores} from '../../../state'
 
 export const ProfileMembers = observer(function ProfileMembers({
@@ -24,6 +25,10 @@ export const ProfileMembers = observer(function ProfileMembers({
     newView.setup().catch(err => console.error('Failed to fetch members', err))
   }, [name, view?.params.actor, store])
 
+  const onRefresh = () => {
+    view?.refresh()
+  }
+
   // loading
   // =
   if (
@@ -43,7 +48,12 @@ export const ProfileMembers = observer(function ProfileMembers({
   if (view.hasError) {
     return (
       <View>
-        <Text>{view.error}</Text>
+        <ErrorMessage
+          dark
+          message={view.error}
+          style={{margin: 6}}
+          onPressTryAgain={onRefresh}
+        />
       </View>
     )
   }
diff --git a/src/view/com/util/ErrorMessage.tsx b/src/view/com/util/ErrorMessage.tsx
index 834cd598f..3f6522b86 100644
--- a/src/view/com/util/ErrorMessage.tsx
+++ b/src/view/com/util/ErrorMessage.tsx
@@ -1,40 +1,66 @@
 import React from 'react'
-import {StyleSheet, Text, TouchableOpacity, View} from 'react-native'
+import {
+  StyleSheet,
+  Text,
+  TouchableOpacity,
+  StyleProp,
+  View,
+  ViewStyle,
+} from 'react-native'
 import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
-import {colors} from '../../lib/styles'
+import LinearGradient from 'react-native-linear-gradient'
+import {colors, gradients} from '../../lib/styles'
 
 export function ErrorMessage({
   message,
   numberOfLines,
+  dark,
+  style,
   onPressTryAgain,
 }: {
   message: string
   numberOfLines?: number
+  dark?: boolean
+  style?: StyleProp<ViewStyle>
   onPressTryAgain?: () => void
 }) {
-  return (
-    <View style={styles.outer}>
-      <View style={styles.errorIcon}>
+  const inner = (
+    <>
+      <View style={[styles.errorIcon, dark ? styles.darkErrorIcon : undefined]}>
         <FontAwesomeIcon
           icon="exclamation"
-          style={{color: colors.white}}
+          style={{color: dark ? colors.red3 : colors.white}}
           size={16}
         />
       </View>
-      <Text style={styles.message} numberOfLines={numberOfLines}>
+      <Text
+        style={[styles.message, dark ? styles.darkMessage : undefined]}
+        numberOfLines={numberOfLines}>
         {message}
       </Text>
       {onPressTryAgain && (
         <TouchableOpacity style={styles.btn} onPress={onPressTryAgain}>
           <FontAwesomeIcon
             icon="arrows-rotate"
-            style={{color: colors.red4}}
+            style={{color: dark ? colors.white : colors.red4}}
             size={16}
           />
         </TouchableOpacity>
       )}
-    </View>
+    </>
   )
+  if (dark) {
+    return (
+      <LinearGradient
+        colors={[gradients.error.start, gradients.error.end]}
+        start={{x: 0.5, y: 0}}
+        end={{x: 1, y: 1}}
+        style={[styles.outer, style]}>
+        {inner}
+      </LinearGradient>
+    )
+  }
+  return <View style={[styles.outer, style]}>{inner}</View>
 }
 
 const styles = StyleSheet.create({
@@ -57,11 +83,18 @@ const styles = StyleSheet.create({
     justifyContent: 'center',
     marginRight: 8,
   },
+  darkErrorIcon: {
+    backgroundColor: colors.white,
+  },
   message: {
     flex: 1,
     color: colors.red4,
     paddingRight: 10,
   },
+  darkMessage: {
+    color: colors.white,
+    fontWeight: '600',
+  },
   btn: {
     paddingHorizontal: 4,
     paddingVertical: 4,
diff --git a/src/view/lib/strings.ts b/src/view/lib/strings.ts
index 19bd5c473..e47863d50 100644
--- a/src/view/lib/strings.ts
+++ b/src/view/lib/strings.ts
@@ -96,3 +96,10 @@ export function enforceLen(str: string, len: number): string {
   }
   return str
 }
+
+export function cleanError(str: string): string {
+  if (str.startsWith('Error: ')) {
+    return str.slice('Error: '.length)
+  }
+  return str
+}
diff --git a/src/view/lib/styles.ts b/src/view/lib/styles.ts
index dfcaeef92..4bea6443c 100644
--- a/src/view/lib/styles.ts
+++ b/src/view/lib/styles.ts
@@ -45,6 +45,7 @@ export const colors = {
 
 export const gradients = {
   primary: {start: '#db00ff', end: '#ff007a'},
+  error: {start: '#ff007a', end: '#ed0d78'},
   purple: {start: colors.pink3, end: colors.purple3},
   blue: {start: colors.purple3, end: colors.blue3},
   green: {start: colors.blue3, end: colors.green3},
diff --git a/src/view/screens/Home.tsx b/src/view/screens/Home.tsx
index c16eff805..036f7d148 100644
--- a/src/view/screens/Home.tsx
+++ b/src/view/screens/Home.tsx
@@ -51,6 +51,9 @@ export const Home = observer(function Home({
   const onCreatePost = () => {
     defaultFeedView.loadLatest()
   }
+  const onPressTryAgain = () => {
+    defaultFeedView.refresh()
+  }
 
   return (
     <View style={s.flex1}>
@@ -63,6 +66,7 @@ export const Home = observer(function Home({
         feed={defaultFeedView}
         scrollElRef={scrollElRef}
         style={{flex: 1}}
+        onPressTryAgain={onPressTryAgain}
       />
       <FAB icon="pen-nib" onPress={onComposePress} />
     </View>
diff --git a/src/view/screens/Notifications.tsx b/src/view/screens/Notifications.tsx
index c82e7cc7c..f1084540c 100644
--- a/src/view/screens/Notifications.tsx
+++ b/src/view/screens/Notifications.tsx
@@ -36,10 +36,14 @@ export const Notifications = ({visible}: ScreenParams) => {
     }
   }, [visible, store])
 
+  const onPressTryAgain = () => {
+    notesView?.refresh()
+  }
+
   return (
     <View style={{flex: 1}}>
       <ViewHeader title="Notifications" />
-      {notesView && <Feed view={notesView} />}
+      {notesView && <Feed view={notesView} onPressTryAgain={onPressTryAgain} />}
     </View>
   )
 }