+20
src/lib/api/feed/demo.ts
+20
src/lib/api/feed/demo.ts
···
1
+
import {type AppBskyFeedDefs, type BskyAgent} from '@atproto/api'
2
+
3
+
import {DEMO_FEED} from '#/lib/demo'
4
+
import {type FeedAPI, type FeedAPIResponse} from './types'
5
+
6
+
export class DemoFeedAPI implements FeedAPI {
7
+
agent: BskyAgent
8
+
9
+
constructor({agent}: {agent: BskyAgent}) {
10
+
this.agent = agent
11
+
}
12
+
13
+
async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> {
14
+
return DEMO_FEED.feed[0]
15
+
}
16
+
17
+
async fetch(): Promise<FeedAPIResponse> {
18
+
return DEMO_FEED
19
+
}
20
+
}
+202
src/lib/demo.ts
+202
src/lib/demo.ts
···
1
+
import {type AppBskyFeedGetFeed} from '@atproto/api'
2
+
import {subDays, subMinutes} from 'date-fns'
3
+
4
+
const DID = `did:plc:z72i7hdynmk6r22z27h6tvur`
5
+
const NOW = new Date()
6
+
const POST_1_DATE = subMinutes(NOW, 2).toISOString()
7
+
const POST_2_DATE = subMinutes(NOW, 4).toISOString()
8
+
const POST_3_DATE = subMinutes(NOW, 5).toISOString()
9
+
10
+
export const DEMO_FEED = {
11
+
feed: [
12
+
{
13
+
post: {
14
+
uri: 'at://did:plc:pvooorihapc2lf2pijehgrdf/app.bsky.feed.post/3lniysofyll2d',
15
+
cid: 'bafyreihwh3wxxme732ylbylhhdyz7ex6t4jtu6s3gjxxvnnh4feddhg3ku',
16
+
author: {
17
+
did: 'did:plc:pvooorihapc2lf2pijehgrdf',
18
+
handle: 'forkedriverband.bsky.social',
19
+
displayName: 'Forked River Band',
20
+
avatar: 'https://bsky.social/about/adi/post_1_avi.jpg',
21
+
viewer: {
22
+
muted: false,
23
+
blockedBy: false,
24
+
following: `at://${DID}/app.bsky.graph.follow/post1`,
25
+
},
26
+
labels: [],
27
+
createdAt: POST_1_DATE,
28
+
verification: {
29
+
verifications: [
30
+
{
31
+
issuer: DID,
32
+
uri: `at://${DID}/app.bsky.graph.verification/post1`,
33
+
isValid: true,
34
+
createdAt: subDays(NOW, 11).toISOString(),
35
+
},
36
+
],
37
+
verifiedStatus: 'valid',
38
+
trustedVerifierStatus: 'none',
39
+
},
40
+
},
41
+
record: {
42
+
$type: 'app.bsky.feed.post',
43
+
createdAt: POST_1_DATE,
44
+
// embed: {
45
+
// $type: 'app.bsky.embed.images',
46
+
// images: [
47
+
// {
48
+
// alt: 'Fake flier for Sebastapol Bluegrass Fest',
49
+
// aspectRatio: {
50
+
// height: 1350,
51
+
// width: 900,
52
+
// },
53
+
// image: {
54
+
// $type: 'blob',
55
+
// ref: {
56
+
// $link:
57
+
// 'bafkreig7gnirmz5guhhjutf3mqbjjzxzi3w4wvs5qy2gnxma5g3brbaidi',
58
+
// },
59
+
// mimeType: 'image/jpeg',
60
+
// size: 562871,
61
+
// },
62
+
// },
63
+
// ],
64
+
// },
65
+
langs: ['en'],
66
+
text: 'Sonoma County folks: Come tip your hats our way and see us play new and old bluegrass tunes at Sebastopol Solstice Fest on June 20th.',
67
+
},
68
+
embed: {
69
+
$type: 'app.bsky.embed.images#view',
70
+
images: [
71
+
{
72
+
thumb: 'https://bsky.social/about/adi/post_1_image.jpg',
73
+
fullsize: 'https://bsky.social/about/adi/post_1_image.jpg',
74
+
alt: 'Fake flier for Sebastapol Bluegrass Fest',
75
+
aspectRatio: {
76
+
height: 1350,
77
+
width: 900,
78
+
},
79
+
},
80
+
],
81
+
},
82
+
replyCount: 1,
83
+
repostCount: 4,
84
+
likeCount: 18,
85
+
quoteCount: 0,
86
+
indexedAt: POST_1_DATE,
87
+
viewer: {
88
+
threadMuted: false,
89
+
embeddingDisabled: false,
90
+
},
91
+
labels: [],
92
+
},
93
+
},
94
+
{
95
+
post: {
96
+
uri: 'at://did:plc:fhhqii56ppgyh5qcm2b3mokf/app.bsky.feed.post/3lnizc7fug52c',
97
+
cid: 'bafyreienuabsr55rycirdf4ewue5tjcseg5lzqompcsh2brqzag6hvxllm',
98
+
author: {
99
+
did: 'did:plc:fhhqii56ppgyh5qcm2b3mokf',
100
+
handle: 'dinh-designs.bsky.social',
101
+
displayName: 'Rich Dinh Designs',
102
+
avatar: 'https://bsky.social/about/adi/post_2_avi.jpg',
103
+
viewer: {
104
+
muted: false,
105
+
blockedBy: false,
106
+
following: `at://${DID}/app.bsky.graph.follow/post2`,
107
+
},
108
+
labels: [],
109
+
createdAt: POST_2_DATE,
110
+
},
111
+
record: {
112
+
$type: 'app.bsky.feed.post',
113
+
createdAt: POST_2_DATE,
114
+
// embed: {
115
+
// $type: 'app.bsky.embed.images',
116
+
// images: [
117
+
// {
118
+
// alt: 'Placeholder image of interior design',
119
+
// aspectRatio: {
120
+
// height: 872,
121
+
// width: 598,
122
+
// },
123
+
// image: {
124
+
// $type: 'blob',
125
+
// ref: {
126
+
// $link:
127
+
// 'bafkreidcjc6bjb4jjjejruin5cldhj5zovsuu4tydulenyprneziq5rfeu',
128
+
// },
129
+
// mimeType: 'image/jpeg',
130
+
// size: 296003,
131
+
// },
132
+
// },
133
+
// ],
134
+
// },
135
+
langs: ['en'],
136
+
text: 'Details from our install at the Lucas residence in Joshua Tree. We populated the space with rich, earthy tones and locally-sourced materials to suit the landscape.',
137
+
},
138
+
embed: {
139
+
$type: 'app.bsky.embed.images#view',
140
+
images: [
141
+
{
142
+
thumb: 'https://bsky.social/about/adi/post_2_image.jpg',
143
+
fullsize: 'https://bsky.social/about/adi/post_2_image.jpg',
144
+
alt: 'Placeholder image of interior design',
145
+
aspectRatio: {
146
+
height: 872,
147
+
width: 598,
148
+
},
149
+
},
150
+
],
151
+
},
152
+
replyCount: 3,
153
+
repostCount: 1,
154
+
likeCount: 4,
155
+
quoteCount: 0,
156
+
indexedAt: POST_2_DATE,
157
+
viewer: {
158
+
threadMuted: false,
159
+
embeddingDisabled: false,
160
+
},
161
+
labels: [],
162
+
},
163
+
},
164
+
{
165
+
post: {
166
+
uri: 'at://did:plc:h7fwnfejmmifveeea5eyxgkc/app.bsky.feed.post/3lnizna3g4f2t',
167
+
cid: 'bafyreiepn7obmlshliori4j34texpaukrqkyyu7cq6nmpzk4lkis7nqeae',
168
+
author: {
169
+
did: 'did:plc:h7fwnfejmmifveeea5eyxgkc',
170
+
handle: 'rodyalbuerne.bsky.social',
171
+
displayName: 'Rody Albuerne',
172
+
avatar: 'https://bsky.social/about/adi/post_3_avi.jpg',
173
+
viewer: {
174
+
muted: false,
175
+
blockedBy: false,
176
+
following: `at://${DID}/app.bsky.graph.follow/post3`,
177
+
},
178
+
labels: [],
179
+
createdAt: POST_3_DATE,
180
+
},
181
+
record: {
182
+
$type: 'app.bsky.feed.post',
183
+
createdAt: POST_3_DATE,
184
+
langs: ['en'],
185
+
text: 'Tinkering with the basics of traditional wooden joinery in my shop lately. Starting small with this ox, made using simple mortise and tenon joints.',
186
+
},
187
+
replyCount: 11,
188
+
repostCount: 97,
189
+
likeCount: 399,
190
+
quoteCount: 0,
191
+
indexedAt: POST_3_DATE,
192
+
viewer: {
193
+
threadMuted: false,
194
+
embeddingDisabled: false,
195
+
},
196
+
labels: [],
197
+
},
198
+
},
199
+
],
200
+
} satisfies AppBskyFeedGetFeed.OutputSchema
201
+
202
+
export const BOTTOM_BAR_AVI = 'https://bsky.social/about/adi/user_avi.jpg'
+29
-2
src/screens/Settings/AboutSettings.tsx
+29
-2
src/screens/Settings/AboutSettings.tsx
···
12
12
import {appVersion, BUNDLE_DATE, bundleInfo} from '#/lib/app-info'
13
13
import {STATUS_PAGE_URL} from '#/lib/constants'
14
14
import {type CommonNavigatorParams} from '#/lib/routes/types'
15
-
import {isAndroid, isNative} from '#/platform/detection'
15
+
import {isAndroid, isIOS, isNative} from '#/platform/detection'
16
16
import * as Toast from '#/view/com/util/Toast'
17
17
import * as SettingsList from '#/screens/Settings/components/SettingsList'
18
+
import {Atom_Stroke2_Corner0_Rounded as AtomIcon} from '#/components/icons/Atom'
18
19
import {BroomSparkle_Stroke2_Corner2_Rounded as BroomSparkleIcon} from '#/components/icons/BroomSparkle'
19
20
import {CodeLines_Stroke2_Corner2_Rounded as CodeLinesIcon} from '#/components/icons/CodeLines'
20
21
import {Globe_Stroke2_Corner0_Rounded as GlobeIcon} from '#/components/icons/Globe'
···
22
23
import {Wrench_Stroke2_Corner2_Rounded as WrenchIcon} from '#/components/icons/Wrench'
23
24
import * as Layout from '#/components/Layout'
24
25
import {Loader} from '#/components/Loader'
26
+
import {useDemoMode} from '#/storage/hooks/demo-mode'
25
27
import {useDevMode} from '#/storage/hooks/dev-mode'
26
28
import {OTAInfo} from './components/OTAInfo'
27
29
···
29
31
export function AboutSettingsScreen({}: Props) {
30
32
const {_, i18n} = useLingui()
31
33
const [devModeEnabled, setDevModeEnabled] = useDevMode()
34
+
const [demoModeEnabled, setDemoModeEnabled] = useDemoMode()
32
35
const stableID = useMemo(() => Statsig.getStableID(), [])
33
36
34
37
const {mutate: onClearImageCache, isPending: isClearingImageCache} =
···
153
156
</SettingsList.ItemText>
154
157
<SettingsList.BadgeText>{bundleInfo}</SettingsList.BadgeText>
155
158
</SettingsList.PressableItem>
156
-
{devModeEnabled && <OTAInfo />}
159
+
{devModeEnabled && (
160
+
<>
161
+
<OTAInfo />
162
+
{isIOS && (
163
+
<SettingsList.PressableItem
164
+
onPress={() => {
165
+
const newDemoModeEnabled = !demoModeEnabled
166
+
setDemoModeEnabled(newDemoModeEnabled)
167
+
Toast.show(
168
+
'Demo mode ' +
169
+
(newDemoModeEnabled ? 'enabled' : 'disabled'),
170
+
)
171
+
}}
172
+
label={
173
+
demoModeEnabled ? 'Disable demo mode' : 'Enable demo mode'
174
+
}
175
+
disabled={isClearingImageCache}>
176
+
<SettingsList.ItemIcon icon={AtomIcon} />
177
+
<SettingsList.ItemText>
178
+
{demoModeEnabled ? 'Disable demo mode' : 'Enable demo mode'}
179
+
</SettingsList.ItemText>
180
+
</SettingsList.PressableItem>
181
+
)}
182
+
</>
183
+
)}
157
184
</SettingsList.Container>
158
185
</Layout.Content>
159
186
</Layout.Screen>
+13
-9
src/state/queries/post-feed.ts
+13
-9
src/state/queries/post-feed.ts
···
1
1
import React, {useCallback, useEffect, useRef} from 'react'
2
2
import {AppState} from 'react-native'
3
3
import {
4
-
AppBskyActorDefs,
4
+
type AppBskyActorDefs,
5
5
AppBskyFeedDefs,
6
-
AppBskyFeedPost,
6
+
type AppBskyFeedPost,
7
7
AtUri,
8
-
BskyAgent,
8
+
type BskyAgent,
9
9
moderatePost,
10
-
ModerationDecision,
10
+
type ModerationDecision,
11
11
} from '@atproto/api'
12
12
import {
13
-
InfiniteData,
14
-
QueryClient,
15
-
QueryKey,
13
+
type InfiniteData,
14
+
type QueryClient,
15
+
type QueryKey,
16
16
useInfiniteQuery,
17
17
} from '@tanstack/react-query'
18
18
19
19
import {AuthorFeedAPI} from '#/lib/api/feed/author'
20
20
import {CustomFeedAPI} from '#/lib/api/feed/custom'
21
+
import {DemoFeedAPI} from '#/lib/api/feed/demo'
21
22
import {FollowingFeedAPI} from '#/lib/api/feed/following'
22
23
import {HomeFeedAPI} from '#/lib/api/feed/home'
23
24
import {LikesFeedAPI} from '#/lib/api/feed/likes'
24
25
import {ListFeedAPI} from '#/lib/api/feed/list'
25
26
import {MergeFeedAPI} from '#/lib/api/feed/merge'
26
-
import {FeedAPI, ReasonFeedSource} from '#/lib/api/feed/types'
27
+
import {type FeedAPI, type ReasonFeedSource} from '#/lib/api/feed/types'
27
28
import {aggregateUserInterests} from '#/lib/api/feed/utils'
28
-
import {FeedTuner, FeedTunerFn} from '#/lib/api/feed-manip'
29
+
import {FeedTuner, type FeedTunerFn} from '#/lib/api/feed-manip'
29
30
import {DISCOVER_FEED_URI} from '#/lib/constants'
30
31
import {BSKY_FEED_OWNER_DIDS} from '#/lib/constants'
31
32
import {logger} from '#/logger'
···
59
60
| `feedgen|${FeedUri}`
60
61
| `likes|${ActorDid}`
61
62
| `list|${ListUri}`
63
+
| 'demo'
62
64
export interface FeedParams {
63
65
mergeFeedEnabled?: boolean
64
66
mergeFeedSources?: string[]
···
483
485
} else if (feedDesc.startsWith('list')) {
484
486
const [_, list] = feedDesc.split('|')
485
487
return new ListFeedAPI({agent, feedParams: {list}})
488
+
} else if (feedDesc === 'demo') {
489
+
return new DemoFeedAPI({agent})
486
490
} else {
487
491
// shouldnt happen
488
492
return new FollowingFeedAPI({agent})
+7
src/storage/hooks/demo-mode.ts
+7
src/storage/hooks/demo-mode.ts
+1
src/storage/schema.ts
+1
src/storage/schema.ts
+10
-6
src/view/com/util/post-embeds/index.tsx
+10
-6
src/view/com/util/post-embeds/index.tsx
···
1
1
import React from 'react'
2
2
import {
3
3
InteractionManager,
4
-
StyleProp,
4
+
type StyleProp,
5
5
StyleSheet,
6
6
View,
7
-
ViewStyle,
7
+
type ViewStyle,
8
8
} from 'react-native'
9
-
import {MeasuredDimensions, runOnJS, runOnUI} from 'react-native-reanimated'
9
+
import {
10
+
type MeasuredDimensions,
11
+
runOnJS,
12
+
runOnUI,
13
+
} from 'react-native-reanimated'
10
14
import {Image} from 'expo-image'
11
15
import {
12
16
AppBskyEmbedExternal,
···
18
22
AppBskyGraphDefs,
19
23
moderateFeedGenerator,
20
24
moderateUserList,
21
-
ModerationDecision,
25
+
type ModerationDecision,
22
26
} from '@atproto/api'
23
27
24
-
import {HandleRef, measureHandle} from '#/lib/hooks/useHandleRef'
28
+
import {type HandleRef, measureHandle} from '#/lib/hooks/useHandleRef'
25
29
import {usePalette} from '#/lib/hooks/usePalette'
26
30
import {useLightboxControls} from '#/state/lightbox'
27
31
import {useModerationOpts} from '#/state/preferences/moderation-opts'
···
30
34
import * as ListCard from '#/components/ListCard'
31
35
import {Embed as StarterPackCard} from '#/components/StarterPack/StarterPackCard'
32
36
import {ContentHider} from '../../../../components/moderation/ContentHider'
33
-
import {Dimensions} from '../../lightbox/ImageViewing/@types'
37
+
import {type Dimensions} from '../../lightbox/ImageViewing/@types'
34
38
import {AutoSizedImage} from '../images/AutoSizedImage'
35
39
import {ImageLayoutGrid} from '../images/ImageLayoutGrid'
36
40
import {ExternalLinkEmbed} from './ExternalLinkEmbed'
+58
-7
src/view/screens/Home.tsx
+58
-7
src/view/screens/Home.tsx
···
8
8
import {useSetTitle} from '#/lib/hooks/useSetTitle'
9
9
import {useRequestNotificationsPermission} from '#/lib/notifications/notifications'
10
10
import {
11
-
HomeTabNavigatorParams,
12
-
NativeStackScreenProps,
11
+
type HomeTabNavigatorParams,
12
+
type NativeStackScreenProps,
13
13
} from '#/lib/routes/types'
14
14
import {logEvent} from '#/lib/statsig/statsig'
15
15
import {isWeb} from '#/platform/detection'
16
16
import {emitSoftReset} from '#/state/events'
17
-
import {SavedFeedSourceInfo, usePinnedFeedsInfos} from '#/state/queries/feed'
18
-
import {FeedDescriptor, FeedParams} from '#/state/queries/post-feed'
17
+
import {
18
+
type SavedFeedSourceInfo,
19
+
usePinnedFeedsInfos,
20
+
} from '#/state/queries/feed'
21
+
import {type FeedDescriptor, type FeedParams} from '#/state/queries/post-feed'
19
22
import {usePreferencesQuery} from '#/state/queries/preferences'
20
-
import {UsePreferencesQueryResponse} from '#/state/queries/preferences/types'
23
+
import {type UsePreferencesQueryResponse} from '#/state/queries/preferences/types'
21
24
import {useSession} from '#/state/session'
22
25
import {useSetMinimalShellMode} from '#/state/shell'
23
26
import {useLoggedOutViewControls} from '#/state/shell/logged-out'
24
27
import {useSelectedFeed, useSetSelectedFeed} from '#/state/shell/selected-feed'
25
28
import {FeedPage} from '#/view/com/feeds/FeedPage'
26
29
import {HomeHeader} from '#/view/com/home/HomeHeader'
27
-
import {Pager, PagerRef, RenderTabBarFnProps} from '#/view/com/pager/Pager'
30
+
import {
31
+
Pager,
32
+
type PagerRef,
33
+
type RenderTabBarFnProps,
34
+
} from '#/view/com/pager/Pager'
28
35
import {CustomFeedEmptyState} from '#/view/com/posts/CustomFeedEmptyState'
29
36
import {FollowingEmptyState} from '#/view/com/posts/FollowingEmptyState'
30
37
import {FollowingEndOfFeed} from '#/view/com/posts/FollowingEndOfFeed'
31
38
import {NoFeedsPinned} from '#/screens/Home/NoFeedsPinned'
32
39
import * as Layout from '#/components/Layout'
40
+
import {useDemoMode} from '#/storage/hooks/demo-mode'
33
41
34
42
type Props = NativeStackScreenProps<HomeTabNavigatorParams, 'Home' | 'Start'>
35
43
export function HomeScreen(props: Props) {
···
184
192
[setMinimalShellMode],
185
193
)
186
194
195
+
const [demoMode] = useDemoMode()
196
+
187
197
const renderTabBar = React.useCallback(
188
198
(props: RenderTabBarFnProps) => {
199
+
if (demoMode) {
200
+
return (
201
+
<HomeHeader
202
+
key="FEEDS_TAB_BAR"
203
+
{...props}
204
+
testID="homeScreenFeedTabs"
205
+
onPressSelected={onPressSelected}
206
+
// @ts-ignore
207
+
feeds={[{displayName: 'Following'}, {displayName: 'Discover'}]}
208
+
/>
209
+
)
210
+
}
189
211
return (
190
212
<HomeHeader
191
213
key="FEEDS_TAB_BAR"
···
196
218
/>
197
219
)
198
220
},
199
-
[onPressSelected, pinnedFeedInfos],
221
+
[onPressSelected, pinnedFeedInfos, demoMode],
200
222
)
201
223
202
224
const renderFollowingEmptyState = React.useCallback(() => {
···
217
239
: [],
218
240
}
219
241
}, [preferences])
242
+
243
+
if (demoMode) {
244
+
return (
245
+
<Pager
246
+
ref={pagerRef}
247
+
testID="homeScreen"
248
+
onPageSelected={onPageSelected}
249
+
onPageScrollStateChanged={onPageScrollStateChanged}
250
+
renderTabBar={renderTabBar}
251
+
initialPage={selectedIndex}>
252
+
<FeedPage
253
+
testID="demoFeedPage"
254
+
isPageFocused
255
+
isPageAdjacent={false}
256
+
feed="demo"
257
+
renderEmptyState={renderCustomFeedEmptyState}
258
+
feedInfo={pinnedFeedInfos[0]}
259
+
/>
260
+
<FeedPage
261
+
testID="customFeedPage"
262
+
isPageFocused
263
+
isPageAdjacent={false}
264
+
feed={`feedgen|${PROD_DEFAULT_FEED('whats-hot')}`}
265
+
renderEmptyState={renderCustomFeedEmptyState}
266
+
feedInfo={pinnedFeedInfos[0]}
267
+
/>
268
+
</Pager>
269
+
)
270
+
}
220
271
221
272
return hasSession ? (
222
273
<Pager
+9
-5
src/view/shell/bottom-bar/BottomBar.tsx
+9
-5
src/view/shell/bottom-bar/BottomBar.tsx
···
1
-
import React, {ComponentProps} from 'react'
2
-
import {GestureResponderEvent, View} from 'react-native'
1
+
import React, {type ComponentProps} from 'react'
2
+
import {type GestureResponderEvent, View} from 'react-native'
3
3
import Animated from 'react-native-reanimated'
4
4
import {useSafeAreaInsets} from 'react-native-safe-area-context'
5
5
import {msg, plural, Trans} from '@lingui/macro'
6
6
import {useLingui} from '@lingui/react'
7
-
import {BottomTabBarProps} from '@react-navigation/bottom-tabs'
7
+
import {type BottomTabBarProps} from '@react-navigation/bottom-tabs'
8
8
import {StackActions} from '@react-navigation/native'
9
9
10
10
import {PressableScale} from '#/lib/custom-animations/PressableScale'
11
+
import {BOTTOM_BAR_AVI} from '#/lib/demo'
11
12
import {useHaptics} from '#/lib/haptics'
12
13
import {useDedupe} from '#/lib/hooks/useDedupe'
13
14
import {useMinimalShellFooterTransform} from '#/lib/hooks/useMinimalShellTransform'
···
47
48
Message_Stroke2_Corner0_Rounded as Message,
48
49
Message_Stroke2_Corner0_Rounded_Filled as MessageFilled,
49
50
} from '#/components/icons/Message'
51
+
import {useDemoMode} from '#/storage/hooks/demo-mode'
50
52
import {styles} from './BottomBarStyles'
51
53
52
54
type TabOptions =
···
123
125
playHaptic()
124
126
accountSwitchControl.open()
125
127
}, [accountSwitchControl, playHaptic])
128
+
129
+
const [demoMode] = useDemoMode()
126
130
127
131
return (
128
132
<>
···
259
263
{borderColor: pal.text.color},
260
264
]}>
261
265
<UserAvatar
262
-
avatar={profile?.avatar}
266
+
avatar={demoMode ? BOTTOM_BAR_AVI : profile?.avatar}
263
267
size={iconWidth - 3}
264
268
// See https://github.com/bluesky-social/social-app/pull/1801:
265
269
usePlainRNImage={true}
···
270
274
<View
271
275
style={[styles.ctrlIcon, pal.text, styles.profileIcon]}>
272
276
<UserAvatar
273
-
avatar={profile?.avatar}
277
+
avatar={demoMode ? BOTTOM_BAR_AVI : profile?.avatar}
274
278
size={iconWidth - 3}
275
279
// See https://github.com/bluesky-social/social-app/pull/1801:
276
280
usePlainRNImage={true}