Merge remote-tracking branch 'origin/main' into eric/nova

* origin/main: (22 commits)
  basic public RSS feed for profiles (#2229)
  use `s.likeColor` everywhere (#2234)
  Add credits to localization doc (#2233)
  Fix `Logotype` fill for dark mode (#2230)
  Hide label appeal on self-labeled posts (#2232)
  Fix & Add: Japanese Translation (3) (#2226)
  tweak social card meta yet again (#2228)
  bskyweb: update golang indigo dep (from May!), and some small devex tweaks (#2227)
  Fix: Some display issues (#2219)
  Update splash screen tagline, update translations, bump ios build number
  Bump ios build number
  Tweaks (#2225)
  1.60
  Traffic reduction and tuned caching strats (#2215)
  Super secret changes don't look (#2218)
  Fix android icon dims (#2213)
  Log post creation failures (#2205)
  Some brand, some pwi (#2212)
  🤫  (#2211)
  PWI behavior updates (#2207)
  ...
This commit is contained in:
Eric Bailey
2023-12-18 16:04:43 -06:00
92 changed files with 2590 additions and 1732 deletions
+26 -20
View File
@@ -6,6 +6,10 @@ import {RootSiblingParent} from 'react-native-root-siblings'
import * as SplashScreen from 'expo-splash-screen'
import {GestureHandlerRootView} from 'react-native-gesture-handler'
import {QueryClientProvider} from '@tanstack/react-query'
import {
SafeAreaProvider,
initialWindowMetrics,
} from 'react-native-safe-area-context'
import 'view/icons'
@@ -34,6 +38,7 @@ import {
} from 'state/session'
import {Provider as UnreadNotifsProvider} from 'state/queries/notifications/unread'
import * as persisted from '#/state/persisted'
import {Splash} from '#/Splash'
SplashScreen.preventAutoHideAsync()
@@ -53,27 +58,28 @@ function InnerApp() {
resumeSession(account)
}, [resumeSession])
// wait for session to resume
if (isInitialLoad) return null
return (
<React.Fragment
// Resets the entire tree below when it changes:
key={currentAccount?.did}>
<LoggedOutViewProvider>
<UnreadNotifsProvider>
<ThemeProvider theme={colorMode}>
{/* All components should be within this provider */}
<RootSiblingParent>
<GestureHandlerRootView style={s.h100pct}>
<TestCtrls />
<Shell />
</GestureHandlerRootView>
</RootSiblingParent>
</ThemeProvider>
</UnreadNotifsProvider>
</LoggedOutViewProvider>
</React.Fragment>
<SafeAreaProvider initialMetrics={initialWindowMetrics}>
<Splash isReady={!isInitialLoad}>
<React.Fragment
// Resets the entire tree below when it changes:
key={currentAccount?.did}>
<LoggedOutViewProvider>
<UnreadNotifsProvider>
<ThemeProvider theme={colorMode}>
{/* All components should be within this provider */}
<RootSiblingParent>
<GestureHandlerRootView style={s.h100pct}>
<TestCtrls />
<Shell />
</GestureHandlerRootView>
</RootSiblingParent>
</ThemeProvider>
</UnreadNotifsProvider>
</LoggedOutViewProvider>
</React.Fragment>
</Splash>
</SafeAreaProvider>
)
}
+13 -7
View File
@@ -1,6 +1,5 @@
import * as React from 'react'
import {StyleSheet} from 'react-native'
import * as SplashScreen from 'expo-splash-screen'
import {
NavigationContainer,
createNavigationContainerRef,
@@ -182,7 +181,7 @@ function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) {
<Stack.Screen
name="ProfileFeed"
getComponent={() => ProfileFeedScreen}
options={{title: title('Feed')}}
options={{title: title('Feed'), requireAuth: true}}
/>
<Stack.Screen
name="ProfileFeedLikedBy"
@@ -293,7 +292,11 @@ function HomeTabNavigator() {
animationDuration: 250,
contentStyle,
}}>
<HomeTab.Screen name="Home" getComponent={() => HomeScreen} />
<HomeTab.Screen
name="Home"
getComponent={() => HomeScreen}
options={{requireAuth: true}}
/>
{commonScreens(HomeTab)}
</HomeTab.Navigator>
)
@@ -327,7 +330,11 @@ function FeedsTabNavigator() {
animationDuration: 250,
contentStyle,
}}>
<FeedsTab.Screen name="Feeds" getComponent={() => FeedsScreen} />
<FeedsTab.Screen
name="Feeds"
getComponent={() => FeedsScreen}
options={{requireAuth: true}}
/>
{commonScreens(FeedsTab as typeof HomeTab)}
</FeedsTab.Navigator>
)
@@ -399,7 +406,7 @@ const FlatNavigator = () => {
<Flat.Screen
name="Home"
getComponent={() => HomeScreen}
options={{title: title('Home')}}
options={{title: title('Home'), requireAuth: true}}
/>
<Flat.Screen
name="Search"
@@ -409,7 +416,7 @@ const FlatNavigator = () => {
<Flat.Screen
name="Feeds"
getComponent={() => FeedsScreen}
options={{title: title('Feeds')}}
options={{title: title('Feeds'), requireAuth: true}}
/>
<Flat.Screen
name="Notifications"
@@ -489,7 +496,6 @@ function RoutesContainer({children}: React.PropsWithChildren<{}>) {
linking={LINKING}
theme={theme}
onReady={() => {
SplashScreen.hideAsync()
logModuleInitTime()
onReady()
}}>
+164
View File
@@ -0,0 +1,164 @@
import React, {useCallback, useEffect} from 'react'
import {View, StyleSheet, Image as RNImage} from 'react-native'
import * as SplashScreen from 'expo-splash-screen'
import {Image} from 'expo-image'
import Animated, {
interpolate,
runOnJS,
useAnimatedStyle,
useSharedValue,
withTiming,
Easing,
} from 'react-native-reanimated'
import MaskedView from '@react-native-masked-view/masked-view'
import {useSafeAreaInsets} from 'react-native-safe-area-context'
import Svg, {Path, SvgProps} from 'react-native-svg'
// @ts-ignore
import splashImagePointer from '../assets/splash.png'
const splashImageUri = RNImage.resolveAssetSource(splashImagePointer).uri
export const Logo = React.forwardRef(function LogoImpl(props: SvgProps, ref) {
const width = 1000
const height = width * (67 / 64)
return (
<Svg
fill="none"
// @ts-ignore it's fiiiiine
ref={ref}
viewBox="0 0 64 66"
style={{width, height}}>
<Path
fill="#fff"
d="M13.873 3.77C21.21 9.243 29.103 20.342 32 26.3v15.732c0-.335-.13.043-.41.858-1.512 4.414-7.418 21.642-20.923 7.87-7.111-7.252-3.819-14.503 9.125-16.692-7.405 1.252-15.73-.817-18.014-8.93C1.12 22.804 0 8.431 0 6.488 0-3.237 8.579-.18 13.873 3.77ZM50.127 3.77C42.79 9.243 34.897 20.342 32 26.3v15.732c0-.335.13.043.41.858 1.512 4.414 7.418 21.642 20.923 7.87 7.111-7.252 3.819-14.503-9.125-16.692 7.405 1.252 15.73-.817 18.014-8.93C62.88 22.804 64 8.431 64 6.488 64-3.237 55.422-.18 50.127 3.77Z"
/>
</Svg>
)
})
type Props = {
isReady: boolean
}
SplashScreen.preventAutoHideAsync().catch(() => {})
const AnimatedLogo = Animated.createAnimatedComponent(Logo)
export function Splash(props: React.PropsWithChildren<Props>) {
const insets = useSafeAreaInsets()
const intro = useSharedValue(0)
const outroLogo = useSharedValue(0)
const outroApp = useSharedValue(0)
const [isAnimationComplete, setIsAnimationComplete] = React.useState(false)
const [isImageLoaded, setIsImageLoaded] = React.useState(false)
const isReady = props.isReady && isImageLoaded
const logoAnimations = useAnimatedStyle(() => {
return {
transform: [
{
scale: interpolate(intro.value, [0, 1], [0.8, 1], 'clamp'),
},
{
scale: interpolate(
outroLogo.value,
[0, 0.06, 0.08, 1],
[1, 0.8, 0.8, 400],
'clamp',
),
},
],
opacity: interpolate(intro.value, [0, 1], [0, 1], 'clamp'),
}
})
const appAnimation = useAnimatedStyle(() => {
return {
transform: [
{
scale: interpolate(outroApp.value, [0, 1], [1.1, 1], 'clamp'),
},
],
opacity: interpolate(outroApp.value, [0, 0.9, 1], [0, 1, 1], 'clamp'),
}
})
const onFinish = useCallback(() => setIsAnimationComplete(true), [])
useEffect(() => {
if (isReady) {
// hide on mount
SplashScreen.hideAsync().catch(() => {})
intro.value = withTiming(
1,
{duration: 200, easing: Easing.out(Easing.cubic)},
async () => {
// set these values to check animation at specific point
// outroLogo.value = 0.1
// outroApp.value = 0.1
outroLogo.value = withTiming(
1,
{duration: 1000, easing: Easing.in(Easing.cubic)},
() => {
runOnJS(onFinish)()
},
)
outroApp.value = withTiming(
1,
{duration: 1000, easing: Easing.inOut(Easing.cubic)},
() => {
runOnJS(onFinish)()
},
)
},
)
}
}, [onFinish, intro, outroLogo, outroApp, isReady])
const onLoadEnd = useCallback(() => {
setIsImageLoaded(true)
}, [setIsImageLoaded])
return (
<View style={{flex: 1}}>
{!isAnimationComplete && (
<Image
accessibilityIgnoresInvertColors
onLoadEnd={onLoadEnd}
source={{uri: splashImageUri}}
style={StyleSheet.absoluteFillObject}
/>
)}
<MaskedView
style={[StyleSheet.absoluteFillObject]}
maskElement={
<Animated.View
style={[
StyleSheet.absoluteFillObject,
{
// Transparent background because mask is based off alpha channel.
backgroundColor: 'transparent',
flex: 1,
justifyContent: 'center',
alignItems: 'center',
transform: [{translateY: -(insets.top / 2)}, {scale: 0.1}], // scale from 1000px to 100px
},
]}>
<AnimatedLogo style={[logoAnimations]} />
</Animated.View>
}>
{!isAnimationComplete && (
<View
style={[StyleSheet.absoluteFillObject, {backgroundColor: 'white'}]}
/>
)}
<Animated.View style={[{flex: 1}, appAnimation]}>
{props.children}
</Animated.View>
</MaskedView>
</View>
)
}
+35
View File
@@ -0,0 +1,35 @@
import React, {createContext, useContext, useMemo} from 'react'
import {ScrollHandlers} from 'react-native-reanimated'
const ScrollContext = createContext<ScrollHandlers<any>>({
onBeginDrag: undefined,
onEndDrag: undefined,
onScroll: undefined,
})
export function useScrollHandlers(): ScrollHandlers<any> {
return useContext(ScrollContext)
}
type ProviderProps = {children: React.ReactNode} & ScrollHandlers<any>
// Note: this completely *overrides* the parent handlers.
// It's up to you to compose them with the parent ones via useScrollHandlers() if needed.
export function ScrollProvider({
children,
onBeginDrag,
onEndDrag,
onScroll,
}: ProviderProps) {
const handlers = useMemo(
() => ({
onBeginDrag,
onEndDrag,
onScroll,
}),
[onBeginDrag, onEndDrag, onScroll],
)
return (
<ScrollContext.Provider value={handlers}>{children}</ScrollContext.Provider>
)
}
+6
View File
@@ -104,12 +104,18 @@ export async function post(agent: BskyAgent, opts: PostOpts) {
// add image embed if present
if (opts.images?.length) {
logger.info(`Uploading images`, {
count: opts.images.length,
})
const images: AppBskyEmbedImages.Image[] = []
for (const image of opts.images) {
opts.onStateChange?.(`Uploading image #${images.length + 1}...`)
logger.info(`Compressing image`)
await image.compress()
const path = image.compressed?.path ?? image.path
const {width, height} = image.compressed || image
logger.info(`Uploading image`)
const res = await uploadBlob(agent, path, 'image/jpeg')
images.push({
image: res.data.blob,
+2 -3
View File
@@ -1,5 +1,4 @@
import {ImageRequireSource} from 'react-native'
export const DEF_AVATAR: ImageRequireSource = require('../../assets/default-avatar.jpg')
export const TABS_EXPLAINER: ImageRequireSource = require('../../assets/tabs-explainer.jpg')
export const CLOUD_SPLASH: ImageRequireSource = require('../../assets/cloud-splash.png')
export const DEF_AVATAR: ImageRequireSource = require('../../assets/default-avatar.png')
export const CLOUD_SPLASH: ImageRequireSource = require('../../assets/splash.png')
+2 -6
View File
@@ -1,10 +1,6 @@
import {ImageRequireSource} from 'react-native'
// @ts-ignore we need to pretend -prf
export const DEF_AVATAR: ImageRequireSource = {uri: '/img/default-avatar.jpg'}
export const DEF_AVATAR: ImageRequireSource = {uri: '/img/default-avatar.png'}
// @ts-ignore we need to pretend -prf
export const TABS_EXPLAINER: ImageRequireSource = {
uri: '/img/tabs-explainer.jpg',
}
// @ts-ignore we need to pretend -prf
export const CLOUD_SPLASH: ImageRequireSource = {uri: '/img/cloud-splash.png'}
export const CLOUD_SPLASH: ImageRequireSource = {uri: '/img/splash.png'}
+1 -1
View File
@@ -1,2 +1,2 @@
export const LOGIN_INCLUDE_DEV_SERVERS = true
export const PWI_ENABLED = false
export const PWI_ENABLED = true
+181 -127
View File
@@ -47,7 +47,7 @@ msgstr ""
msgid "{invitesAvailable} invite codes available"
msgstr ""
#: src/view/screens/Search/Search.tsx:87
#: src/view/screens/Search/Search.tsx:88
msgid "{message}"
msgstr ""
@@ -95,7 +95,7 @@ msgstr ""
#: src/view/com/modals/ListAddRemoveUsers.tsx:264
#: src/view/com/modals/UserAddRemoveLists.tsx:193
#: src/view/screens/ProfileList.tsx:783
#: src/view/screens/ProfileList.tsx:754
msgid "Add"
msgstr ""
@@ -103,7 +103,7 @@ msgstr ""
msgid "Add a content warning"
msgstr ""
#: src/view/screens/ProfileList.tsx:773
#: src/view/screens/ProfileList.tsx:744
msgid "Add a user to this list"
msgstr ""
@@ -126,11 +126,11 @@ msgstr ""
msgid "Add details to report"
msgstr ""
#: src/view/com/composer/Composer.tsx:432
#: src/view/com/composer/Composer.tsx:438
msgid "Add link card"
msgstr ""
#: src/view/com/composer/Composer.tsx:435
#: src/view/com/composer/Composer.tsx:441
msgid "Add link card:"
msgstr ""
@@ -142,7 +142,7 @@ msgstr ""
msgid "Add to Lists"
msgstr ""
#: src/view/screens/ProfileFeed.tsx:280
#: src/view/screens/ProfileFeed.tsx:270
msgid "Add to my feeds"
msgstr ""
@@ -224,11 +224,11 @@ msgstr ""
msgid "Are you sure you want to delete the app password \"{name}\"?"
msgstr ""
#: src/view/com/composer/Composer.tsx:141
#: src/view/com/composer/Composer.tsx:142
msgid "Are you sure you'd like to discard this draft?"
msgstr ""
#: src/view/screens/ProfileList.tsx:375
#: src/view/screens/ProfileList.tsx:352
msgid "Are you sure?"
msgstr ""
@@ -250,9 +250,9 @@ msgstr ""
#: src/view/com/auth/login/LoginForm.tsx:249
#: src/view/com/auth/login/SetNewPasswordForm.tsx:148
#: src/view/com/modals/report/InputIssueDetails.tsx:45
#: src/view/com/post-thread/PostThread.tsx:392
#: src/view/com/post-thread/PostThread.tsx:442
#: src/view/com/post-thread/PostThread.tsx:450
#: src/view/com/post-thread/PostThread.tsx:395
#: src/view/com/post-thread/PostThread.tsx:445
#: src/view/com/post-thread/PostThread.tsx:453
#: src/view/com/profile/ProfileHeader.tsx:672
msgid "Back"
msgstr ""
@@ -275,15 +275,15 @@ msgstr ""
msgid "Block Account"
msgstr ""
#: src/view/screens/ProfileList.tsx:545
#: src/view/screens/ProfileList.tsx:522
msgid "Block accounts"
msgstr ""
#: src/view/screens/ProfileList.tsx:495
#: src/view/screens/ProfileList.tsx:472
msgid "Block list"
msgstr ""
#: src/view/screens/ProfileList.tsx:330
#: src/view/screens/ProfileList.tsx:307
msgid "Block these accounts?"
msgstr ""
@@ -303,15 +303,19 @@ msgstr ""
msgid "Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you. You will not see their content and they will be prevented from seeing yours."
msgstr ""
#: src/view/com/post-thread/PostThread.tsx:248
#: src/view/com/post-thread/PostThread.tsx:251
msgid "Blocked post."
msgstr ""
#: src/view/screens/ProfileList.tsx:332
#: src/view/screens/ProfileList.tsx:309
msgid "Blocking is public. Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you."
msgstr ""
#: src/view/com/auth/SplashScreen.tsx:26
#: src/view/com/auth/HomeLoggedOutCTA.tsx:93
msgid "Blog"
msgstr ""
#: src/view/com/auth/HomeLoggedOutCTA.tsx:31
msgid "Bluesky"
msgstr ""
@@ -343,6 +347,10 @@ msgstr ""
msgid "Build version {0} {1}"
msgstr ""
#: src/view/com/auth/HomeLoggedOutCTA.tsx:87
msgid "Business"
msgstr ""
#: src/view/com/composer/photos/OpenCameraBtn.tsx:60
#: src/view/com/util/UserAvatar.tsx:221
#: src/view/com/util/UserBanner.tsx:38
@@ -353,8 +361,8 @@ msgstr ""
msgid "Can only contain letters, numbers, spaces, dashes, and underscores. Must be at least 4 characters long, but no more than 32 characters long."
msgstr ""
#: src/view/com/composer/Composer.tsx:279
#: src/view/com/composer/Composer.tsx:282
#: src/view/com/composer/Composer.tsx:285
#: src/view/com/composer/Composer.tsx:288
#: src/view/com/modals/AltImage.tsx:127
#: src/view/com/modals/ChangeEmail.tsx:218
#: src/view/com/modals/ChangeEmail.tsx:220
@@ -368,7 +376,7 @@ msgstr ""
#: src/view/com/modals/LinkWarning.tsx:85
#: src/view/com/modals/Repost.tsx:73
#: src/view/com/modals/Waitlist.tsx:136
#: src/view/screens/Search/Search.tsx:558
#: src/view/screens/Search/Search.tsx:592
#: src/view/shell/desktop/Search.tsx:182
msgid "Cancel"
msgstr ""
@@ -473,7 +481,7 @@ msgid "Clear all storage data (restart after this)"
msgstr ""
#: src/view/com/util/forms/SearchInput.tsx:73
#: src/view/screens/Search/Search.tsx:543
#: src/view/screens/Search/Search.tsx:577
msgid "Clear search query"
msgstr ""
@@ -551,7 +559,7 @@ msgstr ""
msgid "Content Languages"
msgstr ""
#: src/view/com/util/moderation/ScreenHider.tsx:69
#: src/view/com/util/moderation/ScreenHider.tsx:78
msgid "Content Warning"
msgstr ""
@@ -573,7 +581,7 @@ msgstr ""
msgid "Copy"
msgstr ""
#: src/view/screens/ProfileList.tsx:407
#: src/view/screens/ProfileList.tsx:384
msgid "Copy link to list"
msgstr ""
@@ -593,15 +601,16 @@ msgstr ""
msgid "Copyright Policy"
msgstr ""
#: src/view/screens/ProfileFeed.tsx:103
#: src/view/screens/ProfileFeed.tsx:94
msgid "Could not load feed"
msgstr ""
#: src/view/screens/ProfileList.tsx:860
#: src/view/screens/ProfileList.tsx:830
msgid "Could not load list"
msgstr ""
#: src/view/com/auth/SplashScreen.tsx:41
#: src/view/com/auth/HomeLoggedOutCTA.tsx:62
#: src/view/com/auth/SplashScreen.tsx:46
msgid "Create a new account"
msgstr ""
@@ -609,7 +618,8 @@ msgstr ""
msgid "Create Account"
msgstr ""
#: src/view/com/auth/SplashScreen.tsx:38
#: src/view/com/auth/HomeLoggedOutCTA.tsx:54
#: src/view/com/auth/SplashScreen.tsx:43
msgid "Create new account"
msgstr ""
@@ -643,8 +653,8 @@ msgstr ""
msgid "Delete app password"
msgstr ""
#: src/view/screens/ProfileList.tsx:374
#: src/view/screens/ProfileList.tsx:434
#: src/view/screens/ProfileList.tsx:351
#: src/view/screens/ProfileList.tsx:411
msgid "Delete List"
msgstr ""
@@ -664,7 +674,7 @@ msgstr ""
msgid "Delete this post?"
msgstr ""
#: src/view/com/post-thread/PostThread.tsx:240
#: src/view/com/post-thread/PostThread.tsx:243
msgid "Deleted post."
msgstr ""
@@ -683,11 +693,11 @@ msgstr ""
msgid "Developer Tools"
msgstr ""
#: src/view/com/composer/Composer.tsx:142
#: src/view/com/composer/Composer.tsx:143
msgid "Discard"
msgstr ""
#: src/view/com/composer/Composer.tsx:136
#: src/view/com/composer/Composer.tsx:137
msgid "Discard draft"
msgstr ""
@@ -739,7 +749,7 @@ msgstr ""
msgid "Edit image"
msgstr ""
#: src/view/screens/ProfileList.tsx:422
#: src/view/screens/ProfileList.tsx:399
msgid "Edit list details"
msgstr ""
@@ -787,7 +797,7 @@ msgstr ""
msgid "Enable this setting to only see replies between people you follow."
msgstr ""
#: src/view/screens/Profile.tsx:471
#: src/view/screens/Profile.tsx:425
msgid "End of feed"
msgstr ""
@@ -815,7 +825,7 @@ msgstr ""
msgid "Enter your username and password"
msgstr ""
#: src/view/screens/Search/Search.tsx:105
#: src/view/screens/Search/Search.tsx:106
msgid "Error:"
msgstr ""
@@ -836,7 +846,7 @@ msgstr ""
msgid "Feed offline"
msgstr ""
#: src/view/com/feeds/FeedPage.tsx:140
#: src/view/com/feeds/FeedPage.tsx:143
msgid "Feed Preferences"
msgstr ""
@@ -847,8 +857,8 @@ msgstr ""
#: src/view/screens/Feeds.tsx:475
#: src/view/screens/Profile.tsx:164
#: src/view/shell/bottom-bar/BottomBar.tsx:160
#: src/view/shell/desktop/LeftNav.tsx:333
#: src/view/shell/bottom-bar/BottomBar.tsx:181
#: src/view/shell/desktop/LeftNav.tsx:339
#: src/view/shell/Drawer.tsx:455
#: src/view/shell/Drawer.tsx:456
msgid "Feeds"
@@ -862,6 +872,14 @@ msgstr ""
msgid "Feeds are custom algorithms that users build with a little coding expertise. <0/> for more information."
msgstr ""
#: src/view/screens/Search/Search.tsx:422
msgid "Find users on Bluesky"
msgstr ""
#: src/view/screens/Search/Search.tsx:420
msgid "Find users with the search tool on the right"
msgstr ""
#: src/view/com/auth/onboarding/RecommendedFollowsItem.tsx:150
msgid "Finding similar accounts..."
msgstr ""
@@ -940,17 +958,17 @@ msgstr ""
msgid "Get Started"
msgstr ""
#: src/view/com/auth/LoggedOut.tsx:68
#: src/view/com/auth/LoggedOut.tsx:69
#: src/view/com/util/moderation/ScreenHider.tsx:105
#: src/view/com/auth/LoggedOut.tsx:70
#: src/view/com/auth/LoggedOut.tsx:71
#: src/view/com/util/moderation/ScreenHider.tsx:123
#: src/view/shell/desktop/LeftNav.tsx:103
msgid "Go back"
msgstr ""
#: src/view/screens/ProfileFeed.tsx:112
#: src/view/screens/ProfileFeed.tsx:117
#: src/view/screens/ProfileList.tsx:869
#: src/view/screens/ProfileList.tsx:874
#: src/view/screens/ProfileFeed.tsx:103
#: src/view/screens/ProfileFeed.tsx:108
#: src/view/screens/ProfileList.tsx:839
#: src/view/screens/ProfileList.tsx:844
msgid "Go Back"
msgstr ""
@@ -974,6 +992,7 @@ msgid "Here is your app password."
msgstr ""
#: src/view/com/notifications/FeedItem.tsx:316
#: src/view/com/util/moderation/ContentHider.tsx:103
msgid "Hide"
msgstr ""
@@ -1009,14 +1028,14 @@ msgstr ""
#~ msgid "Hmmm, we're having trouble finding this feed. It may have been deleted."
#~ msgstr ""
#: src/view/shell/bottom-bar/BottomBar.tsx:116
#: src/view/shell/desktop/LeftNav.tsx:297
#: src/view/shell/bottom-bar/BottomBar.tsx:137
#: src/view/shell/desktop/LeftNav.tsx:303
#: src/view/shell/Drawer.tsx:379
#: src/view/shell/Drawer.tsx:380
msgid "Home"
msgstr ""
#: src/view/com/pager/FeedsTabBarMobile.tsx:99
#: src/view/com/pager/FeedsTabBarMobile.tsx:96
#: src/view/screens/PreferencesHomeFeed.tsx:95
#: src/view/screens/Settings.tsx:481
msgid "Home Feed Preferences"
@@ -1082,6 +1101,10 @@ msgstr ""
msgid "Invite codes: {invitesAvailable} available"
msgstr ""
#: src/view/com/auth/HomeLoggedOutCTA.tsx:99
msgid "Jobs"
msgstr ""
#: src/view/com/modals/Waitlist.tsx:67
msgid "Join the waitlist"
msgstr ""
@@ -1107,9 +1130,13 @@ msgstr ""
msgid "Languages"
msgstr ""
#: src/view/com/util/moderation/ContentHider.tsx:101
msgid "Learn more"
msgstr ""
#: src/view/com/util/moderation/PostAlerts.tsx:47
#: src/view/com/util/moderation/ProfileHeaderAlerts.tsx:65
#: src/view/com/util/moderation/ScreenHider.tsx:88
#: src/view/com/util/moderation/ScreenHider.tsx:104
msgid "Learn More"
msgstr ""
@@ -1117,7 +1144,7 @@ msgstr ""
#: src/view/com/util/moderation/PostAlerts.tsx:40
#: src/view/com/util/moderation/PostHider.tsx:76
#: src/view/com/util/moderation/ProfileHeaderAlerts.tsx:49
#: src/view/com/util/moderation/ScreenHider.tsx:85
#: src/view/com/util/moderation/ScreenHider.tsx:101
msgid "Learn more about this warning"
msgstr ""
@@ -1147,7 +1174,7 @@ msgstr ""
#~ msgid "Light"
#~ msgstr ""
#: src/view/screens/ProfileFeed.tsx:643
#: src/view/screens/ProfileFeed.tsx:577
msgid "Like this feed"
msgstr ""
@@ -1177,22 +1204,22 @@ msgid "List Name"
msgstr ""
#: src/view/screens/Profile.tsx:165
#: src/view/shell/desktop/LeftNav.tsx:373
#: src/view/shell/desktop/LeftNav.tsx:376
#: src/view/shell/Drawer.tsx:471
#: src/view/shell/Drawer.tsx:472
msgid "Lists"
msgstr ""
#: src/view/com/post-thread/PostThread.tsx:257
#: src/view/com/post-thread/PostThread.tsx:265
#: src/view/com/post-thread/PostThread.tsx:260
#: src/view/com/post-thread/PostThread.tsx:268
msgid "Load more posts"
msgstr ""
#: src/view/screens/Notifications.tsx:141
#: src/view/screens/Notifications.tsx:144
msgid "Load new notifications"
msgstr ""
#: src/view/com/feeds/FeedPage.tsx:185
#: src/view/com/feeds/FeedPage.tsx:189
msgid "Load new posts"
msgstr ""
@@ -1216,9 +1243,9 @@ msgstr ""
msgid "Login to account that is not listed"
msgstr ""
#: src/view/screens/ProfileFeed.tsx:482
msgid "Looks like this feed is only available to users with a Bluesky account. Please sign up or sign in to view this feed!"
msgstr ""
#: src/view/screens/ProfileFeed.tsx:472
#~ msgid "Looks like this feed is only available to users with a Bluesky account. Please sign up or sign in to view this feed!"
#~ msgstr ""
#: src/view/com/modals/LinkWarning.tsx:63
msgid "Make sure this is where you intend to go!"
@@ -1236,18 +1263,17 @@ msgstr ""
msgid "Mentioned users"
msgstr ""
#: src/view/screens/Search/Search.tsx:503
#: src/view/screens/Search/Search.tsx:537
msgid "Menu"
msgstr ""
#: src/view/com/posts/FeedErrorMessage.tsx:194
#: src/view/screens/ProfileFeed.tsx:490
msgid "Message from server"
msgstr ""
#: src/view/screens/Moderation.tsx:64
#: src/view/screens/Settings.tsx:563
#: src/view/shell/desktop/LeftNav.tsx:391
#: src/view/shell/desktop/LeftNav.tsx:394
#: src/view/shell/Drawer.tsx:490
#: src/view/shell/Drawer.tsx:491
msgid "Moderation"
@@ -1266,8 +1292,8 @@ msgid "More feeds"
msgstr ""
#: src/view/com/profile/ProfileHeader.tsx:548
#: src/view/screens/ProfileFeed.tsx:370
#: src/view/screens/ProfileList.tsx:606
#: src/view/screens/ProfileFeed.tsx:360
#: src/view/screens/ProfileList.tsx:583
msgid "More options"
msgstr ""
@@ -1279,15 +1305,15 @@ msgstr ""
msgid "Mute Account"
msgstr ""
#: src/view/screens/ProfileList.tsx:533
#: src/view/screens/ProfileList.tsx:510
msgid "Mute accounts"
msgstr ""
#: src/view/screens/ProfileList.tsx:480
#: src/view/screens/ProfileList.tsx:457
msgid "Mute list"
msgstr ""
#: src/view/screens/ProfileList.tsx:293
#: src/view/screens/ProfileList.tsx:270
msgid "Mute these accounts?"
msgstr ""
@@ -1307,7 +1333,7 @@ msgstr ""
msgid "Muted accounts have their posts removed from your feed and from your notifications. Mutes are completely private."
msgstr ""
#: src/view/screens/ProfileList.tsx:295
#: src/view/screens/ProfileList.tsx:272
msgid "Muting is private. Muted accounts can interact with you, but you will not see their posts or receive notifications from them."
msgstr ""
@@ -1345,12 +1371,12 @@ msgstr ""
msgid "New"
msgstr ""
#: src/view/com/feeds/FeedPage.tsx:196
#: src/view/com/feeds/FeedPage.tsx:200
#: src/view/screens/Feeds.tsx:510
#: src/view/screens/Profile.tsx:389
#: src/view/screens/ProfileFeed.tsx:451
#: src/view/screens/ProfileList.tsx:212
#: src/view/screens/ProfileList.tsx:244
#: src/view/screens/Profile.tsx:353
#: src/view/screens/ProfileFeed.tsx:430
#: src/view/screens/ProfileList.tsx:193
#: src/view/screens/ProfileList.tsx:221
#: src/view/shell/desktop/LeftNav.tsx:246
msgid "New post"
msgstr ""
@@ -1379,8 +1405,8 @@ msgstr ""
msgid "No"
msgstr ""
#: src/view/screens/ProfileFeed.tsx:636
#: src/view/screens/ProfileList.tsx:740
#: src/view/screens/ProfileFeed.tsx:570
#: src/view/screens/ProfileList.tsx:711
msgid "No description"
msgstr ""
@@ -1398,9 +1424,9 @@ msgstr ""
#~ msgstr ""
#: src/view/com/modals/ListAddRemoveUsers.tsx:127
#: src/view/screens/Search/Search.tsx:270
#: src/view/screens/Search/Search.tsx:298
#: src/view/screens/Search/Search.tsx:581
#: src/view/screens/Search/Search.tsx:271
#: src/view/screens/Search/Search.tsx:299
#: src/view/screens/Search/Search.tsx:615
#: src/view/shell/desktop/Search.tsx:210
msgid "No results found for {query}"
msgstr ""
@@ -1429,10 +1455,10 @@ msgstr ""
#~ msgid "Note: Third-party apps that display Bluesky content may not respect this setting."
#~ msgstr ""
#: src/view/screens/Notifications.tsx:108
#: src/view/screens/Notifications.tsx:132
#: src/view/shell/bottom-bar/BottomBar.tsx:187
#: src/view/shell/desktop/LeftNav.tsx:355
#: src/view/screens/Notifications.tsx:109
#: src/view/screens/Notifications.tsx:133
#: src/view/shell/bottom-bar/BottomBar.tsx:205
#: src/view/shell/desktop/LeftNav.tsx:358
#: src/view/shell/Drawer.tsx:416
#: src/view/shell/Drawer.tsx:417
msgid "Notifications"
@@ -1446,7 +1472,7 @@ msgstr ""
msgid "Okay"
msgstr ""
#: src/view/com/composer/Composer.tsx:348
#: src/view/com/composer/Composer.tsx:354
msgid "One or more images is missing alt text."
msgstr ""
@@ -1454,7 +1480,7 @@ msgstr ""
msgid "Only {0} can reply."
msgstr ""
#: src/view/com/pager/FeedsTabBarMobile.tsx:79
#: src/view/com/pager/FeedsTabBarMobile.tsx:76
msgid "Open navigation"
msgstr ""
@@ -1569,13 +1595,13 @@ msgstr ""
msgid "Please tell us why you think this decision was incorrect."
msgstr ""
#: src/view/com/composer/Composer.tsx:331
#: src/view/com/post-thread/PostThread.tsx:223
#: src/view/screens/PostThread.tsx:78
#: src/view/com/composer/Composer.tsx:337
#: src/view/com/post-thread/PostThread.tsx:226
#: src/view/screens/PostThread.tsx:80
msgid "Post"
msgstr ""
#: src/view/com/post-thread/PostThread.tsx:382
#: src/view/com/post-thread/PostThread.tsx:385
msgid "Post hidden"
msgstr ""
@@ -1587,7 +1613,7 @@ msgstr ""
msgid "Post Languages"
msgstr ""
#: src/view/com/post-thread/PostThread.tsx:434
#: src/view/com/post-thread/PostThread.tsx:437
msgid "Post not found"
msgstr ""
@@ -1623,7 +1649,8 @@ msgstr ""
msgid "Processing..."
msgstr ""
#: src/view/shell/bottom-bar/BottomBar.tsx:229
#: src/view/shell/bottom-bar/BottomBar.tsx:247
#: src/view/shell/desktop/LeftNav.tsx:412
#: src/view/shell/Drawer.tsx:69
#: src/view/shell/Drawer.tsx:525
#: src/view/shell/Drawer.tsx:526
@@ -1690,7 +1717,7 @@ msgstr ""
#: src/view/com/feeds/FeedSourceCard.tsx:105
#: src/view/com/feeds/FeedSourceCard.tsx:172
#: src/view/screens/ProfileFeed.tsx:280
#: src/view/screens/ProfileFeed.tsx:270
msgid "Remove from my feeds"
msgstr ""
@@ -1735,11 +1762,11 @@ msgstr ""
msgid "Report Account"
msgstr ""
#: src/view/screens/ProfileFeed.tsx:300
#: src/view/screens/ProfileFeed.tsx:290
msgid "Report feed"
msgstr ""
#: src/view/screens/ProfileList.tsx:448
#: src/view/screens/ProfileList.tsx:425
msgid "Report List"
msgstr ""
@@ -1851,10 +1878,10 @@ msgstr ""
#: src/view/com/modals/ListAddRemoveUsers.tsx:75
#: src/view/com/util/forms/SearchInput.tsx:64
#: src/view/screens/Search/Search.tsx:381
#: src/view/screens/Search/Search.tsx:533
#: src/view/shell/bottom-bar/BottomBar.tsx:138
#: src/view/shell/desktop/LeftNav.tsx:315
#: src/view/screens/Search/Search.tsx:401
#: src/view/screens/Search/Search.tsx:567
#: src/view/shell/bottom-bar/BottomBar.tsx:159
#: src/view/shell/desktop/LeftNav.tsx:321
#: src/view/shell/desktop/Search.tsx:161
#: src/view/shell/desktop/Search.tsx:170
#: src/view/shell/Drawer.tsx:343
@@ -1863,14 +1890,14 @@ msgid "Search"
msgstr ""
#: src/view/screens/Search/Search.tsx:390
msgid "Search for posts and users."
msgstr ""
#~ msgid "Search for posts and users."
#~ msgstr ""
#: src/view/com/modals/ChangeEmail.tsx:110
msgid "Security Step Required"
msgstr ""
#: src/view/com/auth/SplashScreen.tsx:29
#: src/view/com/auth/HomeLoggedOutCTA.tsx:39
msgid "See what's next"
msgstr ""
@@ -1944,7 +1971,7 @@ msgid "Set this setting to \"Yes\" to show samples of your saved feeds in your f
msgstr ""
#: src/view/screens/Settings.tsx:277
#: src/view/shell/desktop/LeftNav.tsx:427
#: src/view/shell/desktop/LeftNav.tsx:430
#: src/view/shell/Drawer.tsx:546
#: src/view/shell/Drawer.tsx:547
msgid "Settings"
@@ -1956,11 +1983,11 @@ msgstr ""
#: src/view/com/profile/ProfileHeader.tsx:338
#: src/view/com/util/forms/PostDropdownBtn.tsx:129
#: src/view/screens/ProfileList.tsx:407
#: src/view/screens/ProfileList.tsx:384
msgid "Share"
msgstr ""
#: src/view/screens/ProfileFeed.tsx:312
#: src/view/screens/ProfileFeed.tsx:302
msgid "Share feed"
msgstr ""
@@ -1968,11 +1995,12 @@ msgstr ""
#~ msgid "Share link"
#~ msgstr ""
#: src/view/com/util/moderation/ContentHider.tsx:105
#: src/view/screens/Settings.tsx:316
msgid "Show"
msgstr ""
#: src/view/com/util/moderation/ScreenHider.tsx:114
#: src/view/com/util/moderation/ScreenHider.tsx:132
msgid "Show anyway"
msgstr ""
@@ -2000,15 +2028,23 @@ msgstr ""
msgid "Show users"
msgstr ""
#: src/view/com/auth/HomeLoggedOutCTA.tsx:70
#: src/view/com/auth/login/Login.tsx:98
#: src/view/com/auth/SplashScreen.tsx:49
#: src/view/shell/NavSignupCard.tsx:52
#: src/view/shell/NavSignupCard.tsx:53
#: src/view/com/auth/SplashScreen.tsx:54
#: src/view/shell/bottom-bar/BottomBar.tsx:285
#: src/view/shell/bottom-bar/BottomBar.tsx:286
#: src/view/shell/bottom-bar/BottomBar.tsx:288
#: src/view/shell/bottom-bar/BottomBarWeb.tsx:177
#: src/view/shell/bottom-bar/BottomBarWeb.tsx:178
#: src/view/shell/bottom-bar/BottomBarWeb.tsx:180
#: src/view/shell/NavSignupCard.tsx:58
#: src/view/shell/NavSignupCard.tsx:59
msgid "Sign in"
msgstr ""
#: src/view/com/auth/SplashScreen.tsx:52
#: src/view/com/auth/SplashScreen.web.tsx:84
#: src/view/com/auth/HomeLoggedOutCTA.tsx:78
#: src/view/com/auth/SplashScreen.tsx:57
#: src/view/com/auth/SplashScreen.web.tsx:87
msgid "Sign In"
msgstr ""
@@ -2030,16 +2066,26 @@ msgstr ""
msgid "Sign out"
msgstr ""
#: src/view/shell/NavSignupCard.tsx:43
#: src/view/shell/NavSignupCard.tsx:44
#: src/view/shell/NavSignupCard.tsx:46
#: src/view/shell/bottom-bar/BottomBar.tsx:275
#: src/view/shell/bottom-bar/BottomBar.tsx:276
#: src/view/shell/bottom-bar/BottomBar.tsx:278
#: src/view/shell/bottom-bar/BottomBarWeb.tsx:167
#: src/view/shell/bottom-bar/BottomBarWeb.tsx:168
#: src/view/shell/bottom-bar/BottomBarWeb.tsx:170
#: src/view/shell/NavSignupCard.tsx:49
#: src/view/shell/NavSignupCard.tsx:50
#: src/view/shell/NavSignupCard.tsx:52
msgid "Sign up"
msgstr ""
#: src/view/shell/NavSignupCard.tsx:36
#: src/view/shell/NavSignupCard.tsx:42
msgid "Sign up or sign in to join the conversation"
msgstr ""
#: src/view/com/util/moderation/ScreenHider.tsx:76
msgid "Sign-in Required"
msgstr ""
#: src/view/screens/Settings.tsx:327
msgid "Signed in as"
msgstr ""
@@ -2077,15 +2123,15 @@ msgstr ""
msgid "Submit"
msgstr "Submit"
#: src/view/screens/ProfileList.tsx:597
#: src/view/screens/ProfileList.tsx:574
msgid "Subscribe"
msgstr ""
#: src/view/screens/ProfileList.tsx:593
#: src/view/screens/ProfileList.tsx:570
msgid "Subscribe to this list"
msgstr ""
#: src/view/screens/Search/Search.tsx:354
#: src/view/screens/Search/Search.tsx:357
msgid "Suggested Follows"
msgstr ""
@@ -2135,7 +2181,7 @@ msgstr ""
msgid "The Copyright Policy has been moved to <0/>"
msgstr ""
#: src/view/com/post-thread/PostThread.tsx:437
#: src/view/com/post-thread/PostThread.tsx:440
msgid "The post may have been deleted."
msgstr ""
@@ -2159,10 +2205,14 @@ msgstr ""
msgid "This {0} has been labeled."
msgstr ""
#: src/view/com/util/moderation/ScreenHider.tsx:72
#: src/view/com/util/moderation/ScreenHider.tsx:88
msgid "This {screenDescription} has been flagged:"
msgstr ""
#: src/view/com/util/moderation/ScreenHider.tsx:83
msgid "This account has requested that users sign in to view their profile."
msgstr ""
#: src/view/com/posts/FeedErrorMessage.tsx:107
msgid "This content is not viewable without a Bluesky account."
msgstr ""
@@ -2222,11 +2272,11 @@ msgstr ""
msgid "Try again"
msgstr ""
#: src/view/screens/ProfileList.tsx:495
#: src/view/screens/ProfileList.tsx:472
msgid "Un-block list"
msgstr ""
#: src/view/screens/ProfileList.tsx:480
#: src/view/screens/ProfileList.tsx:457
msgid "Un-mute list"
msgstr ""
@@ -2262,7 +2312,7 @@ msgstr ""
msgid "Unmute thread"
msgstr ""
#: src/view/screens/ProfileList.tsx:463
#: src/view/screens/ProfileList.tsx:440
msgid "Unpin moderation list"
msgstr ""
@@ -2311,7 +2361,7 @@ msgstr ""
msgid "Username or email address"
msgstr ""
#: src/view/screens/ProfileList.tsx:767
#: src/view/screens/ProfileList.tsx:738
msgid "Users"
msgstr ""
@@ -2368,7 +2418,7 @@ msgstr ""
#~ msgid "We're sorry, but this feed is currently receiving high traffic and is temporarily unavailable. Please try again later."
#~ msgstr ""
#: src/view/screens/Search/Search.tsx:237
#: src/view/screens/Search/Search.tsx:238
msgid "We're sorry, but your search could not be completed. Please try again in a few minutes."
msgstr ""
@@ -2384,6 +2434,10 @@ msgstr ""
msgid "What is the issue with this {collectionName}?"
msgstr ""
#: src/view/com/auth/SplashScreen.tsx:34
msgid "What's up?"
msgstr ""
#: src/view/com/modals/lang-settings/PostLanguagesSettings.tsx:78
msgid "Which languages are used in this post?"
msgstr ""
@@ -2405,7 +2459,7 @@ msgstr ""
msgid "Wide"
msgstr ""
#: src/view/com/composer/Composer.tsx:403
#: src/view/com/composer/Composer.tsx:409
msgid "Write post"
msgstr ""
@@ -2444,16 +2498,16 @@ msgstr ""
msgid "You don't have any saved feeds."
msgstr ""
#: src/view/com/post-thread/PostThread.tsx:385
#: src/view/com/post-thread/PostThread.tsx:388
msgid "You have blocked the author or you have been blocked by the author."
msgstr ""
#: src/view/com/feeds/ProfileFeedgens.tsx:154
#: src/view/com/feeds/ProfileFeedgens.tsx:141
msgid "You have no feeds."
msgstr ""
#: src/view/com/lists/MyLists.tsx:89
#: src/view/com/lists/ProfileLists.tsx:158
#: src/view/com/lists/ProfileLists.tsx:145
msgid "You have no lists."
msgstr ""
+181 -127
View File
@@ -47,7 +47,7 @@ msgstr ""
msgid "{invitesAvailable} invite codes available"
msgstr ""
#: src/view/screens/Search/Search.tsx:87
#: src/view/screens/Search/Search.tsx:88
msgid "{message}"
msgstr ""
@@ -95,7 +95,7 @@ msgstr "अकाउंट के विकल्प"
#: src/view/com/modals/ListAddRemoveUsers.tsx:264
#: src/view/com/modals/UserAddRemoveLists.tsx:193
#: src/view/screens/ProfileList.tsx:783
#: src/view/screens/ProfileList.tsx:754
msgid "Add"
msgstr "ऐड करो"
@@ -103,7 +103,7 @@ msgstr "ऐड करो"
msgid "Add a content warning"
msgstr "सामग्री चेतावनी जोड़ें"
#: src/view/screens/ProfileList.tsx:773
#: src/view/screens/ProfileList.tsx:744
msgid "Add a user to this list"
msgstr "इस सूची में किसी को जोड़ें"
@@ -126,11 +126,11 @@ msgstr "विवरण जोड़ें"
msgid "Add details to report"
msgstr "रिपोर्ट करने के लिए विवरण जोड़ें"
#: src/view/com/composer/Composer.tsx:432
#: src/view/com/composer/Composer.tsx:438
msgid "Add link card"
msgstr "लिंक कार्ड जोड़ें"
#: src/view/com/composer/Composer.tsx:435
#: src/view/com/composer/Composer.tsx:441
msgid "Add link card:"
msgstr "लिंक कार्ड जोड़ें:"
@@ -142,7 +142,7 @@ msgstr "अपने डोमेन में निम्नलिखित DN
msgid "Add to Lists"
msgstr "सूचियों में जोड़ें"
#: src/view/screens/ProfileFeed.tsx:280
#: src/view/screens/ProfileFeed.tsx:270
msgid "Add to my feeds"
msgstr "इस फ़ीड को सहेजें"
@@ -224,11 +224,11 @@ msgstr "दिखावट"
msgid "Are you sure you want to delete the app password \"{name}\"?"
msgstr "क्या आप वाकई ऐप पासवर्ड \"{name}\" हटाना चाहते हैं?"
#: src/view/com/composer/Composer.tsx:141
#: src/view/com/composer/Composer.tsx:142
msgid "Are you sure you'd like to discard this draft?"
msgstr "क्या आप वाकई इस ड्राफ्ट को हटाना करना चाहेंगे?"
#: src/view/screens/ProfileList.tsx:375
#: src/view/screens/ProfileList.tsx:352
msgid "Are you sure?"
msgstr "क्या आप वास्तव में इसे करना चाहते हैं?"
@@ -250,9 +250,9 @@ msgstr "कलात्मक या गैर-कामुक नग्नत
#: src/view/com/auth/login/LoginForm.tsx:249
#: src/view/com/auth/login/SetNewPasswordForm.tsx:148
#: src/view/com/modals/report/InputIssueDetails.tsx:45
#: src/view/com/post-thread/PostThread.tsx:392
#: src/view/com/post-thread/PostThread.tsx:442
#: src/view/com/post-thread/PostThread.tsx:450
#: src/view/com/post-thread/PostThread.tsx:395
#: src/view/com/post-thread/PostThread.tsx:445
#: src/view/com/post-thread/PostThread.tsx:453
#: src/view/com/profile/ProfileHeader.tsx:672
msgid "Back"
msgstr "वापस"
@@ -275,15 +275,15 @@ msgstr "जन्मदिन:"
msgid "Block Account"
msgstr "खाता ब्लॉक करें"
#: src/view/screens/ProfileList.tsx:545
#: src/view/screens/ProfileList.tsx:522
msgid "Block accounts"
msgstr "खाता ब्लॉक करें"
#: src/view/screens/ProfileList.tsx:495
#: src/view/screens/ProfileList.tsx:472
msgid "Block list"
msgstr ""
#: src/view/screens/ProfileList.tsx:330
#: src/view/screens/ProfileList.tsx:307
msgid "Block these accounts?"
msgstr "खाता ब्लॉक करें?"
@@ -303,15 +303,19 @@ msgstr "अवरुद्ध खाते आपके थ्रेड्स
msgid "Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you. You will not see their content and they will be prevented from seeing yours."
msgstr "अवरुद्ध खाते आपके थ्रेड्स में उत्तर नहीं दे सकते, आपका उल्लेख नहीं कर सकते, या अन्यथा आपके साथ बातचीत नहीं कर सकते। आप उनकी सामग्री नहीं देख पाएंगे और उन्हें आपकी सामग्री देखने से रोका जाएगा।"
#: src/view/com/post-thread/PostThread.tsx:248
#: src/view/com/post-thread/PostThread.tsx:251
msgid "Blocked post."
msgstr "ब्लॉक पोस्ट।"
#: src/view/screens/ProfileList.tsx:332
#: src/view/screens/ProfileList.tsx:309
msgid "Blocking is public. Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you."
msgstr "अवरोधन सार्वजनिक है. अवरुद्ध खाते आपके थ्रेड्स में उत्तर नहीं दे सकते, आपका उल्लेख नहीं कर सकते, या अन्यथा आपके साथ बातचीत नहीं कर सकते।"
#: src/view/com/auth/SplashScreen.tsx:26
#: src/view/com/auth/HomeLoggedOutCTA.tsx:93
msgid "Blog"
msgstr ""
#: src/view/com/auth/HomeLoggedOutCTA.tsx:31
msgid "Bluesky"
msgstr "Bluesky"
@@ -343,6 +347,10 @@ msgstr "Bluesky.Social"
msgid "Build version {0} {1}"
msgstr "Build version {0} {1}"
#: src/view/com/auth/HomeLoggedOutCTA.tsx:87
msgid "Business"
msgstr ""
#: src/view/com/composer/photos/OpenCameraBtn.tsx:60
#: src/view/com/util/UserAvatar.tsx:221
#: src/view/com/util/UserBanner.tsx:38
@@ -353,8 +361,8 @@ msgstr "कैमरा"
msgid "Can only contain letters, numbers, spaces, dashes, and underscores. Must be at least 4 characters long, but no more than 32 characters long."
msgstr "केवल अक्षर, संख्या, रिक्त स्थान, डैश और अंडरस्कोर हो सकते हैं। कम से कम 4 अक्षर लंबा होना चाहिए, लेकिन 32 अक्षरों से अधिक लंबा नहीं होना चाहिए।।"
#: src/view/com/composer/Composer.tsx:279
#: src/view/com/composer/Composer.tsx:282
#: src/view/com/composer/Composer.tsx:285
#: src/view/com/composer/Composer.tsx:288
#: src/view/com/modals/AltImage.tsx:127
#: src/view/com/modals/ChangeEmail.tsx:218
#: src/view/com/modals/ChangeEmail.tsx:220
@@ -368,7 +376,7 @@ msgstr "केवल अक्षर, संख्या, रिक्त स्
#: src/view/com/modals/LinkWarning.tsx:85
#: src/view/com/modals/Repost.tsx:73
#: src/view/com/modals/Waitlist.tsx:136
#: src/view/screens/Search/Search.tsx:558
#: src/view/screens/Search/Search.tsx:592
#: src/view/shell/desktop/Search.tsx:182
msgid "Cancel"
msgstr "कैंसिल"
@@ -469,7 +477,7 @@ msgid "Clear all storage data (restart after this)"
msgstr ""
#: src/view/com/util/forms/SearchInput.tsx:73
#: src/view/screens/Search/Search.tsx:543
#: src/view/screens/Search/Search.tsx:577
msgid "Clear search query"
msgstr "खोज क्वेरी साफ़ करें"
@@ -547,7 +555,7 @@ msgstr "सामग्री फ़िल्टरिंग"
msgid "Content Languages"
msgstr "सामग्री भाषा"
#: src/view/com/util/moderation/ScreenHider.tsx:69
#: src/view/com/util/moderation/ScreenHider.tsx:78
msgid "Content Warning"
msgstr "सामग्री चेतावनी"
@@ -569,7 +577,7 @@ msgstr "कॉपी कर ली"
msgid "Copy"
msgstr "कॉपी"
#: src/view/screens/ProfileList.tsx:407
#: src/view/screens/ProfileList.tsx:384
msgid "Copy link to list"
msgstr ""
@@ -589,15 +597,16 @@ msgstr "पोस्ट टेक्स्ट कॉपी करें"
msgid "Copyright Policy"
msgstr "कॉपीराइट नीति"
#: src/view/screens/ProfileFeed.tsx:103
#: src/view/screens/ProfileFeed.tsx:94
msgid "Could not load feed"
msgstr "फ़ीड लोड नहीं कर सकता"
#: src/view/screens/ProfileList.tsx:860
#: src/view/screens/ProfileList.tsx:830
msgid "Could not load list"
msgstr "सूची लोड नहीं कर सकता"
#: src/view/com/auth/SplashScreen.tsx:41
#: src/view/com/auth/HomeLoggedOutCTA.tsx:62
#: src/view/com/auth/SplashScreen.tsx:46
msgid "Create a new account"
msgstr "नया खाता बनाएं"
@@ -605,7 +614,8 @@ msgstr "नया खाता बनाएं"
msgid "Create Account"
msgstr "खाता बनाएँ"
#: src/view/com/auth/SplashScreen.tsx:38
#: src/view/com/auth/HomeLoggedOutCTA.tsx:54
#: src/view/com/auth/SplashScreen.tsx:43
msgid "Create new account"
msgstr "नया खाता बनाएं"
@@ -639,8 +649,8 @@ msgstr "खाता हटाएं"
msgid "Delete app password"
msgstr "अप्प पासवर्ड हटाएं"
#: src/view/screens/ProfileList.tsx:374
#: src/view/screens/ProfileList.tsx:434
#: src/view/screens/ProfileList.tsx:351
#: src/view/screens/ProfileList.tsx:411
msgid "Delete List"
msgstr "सूची हटाएँ"
@@ -660,7 +670,7 @@ msgstr "पोस्ट को हटाएं"
msgid "Delete this post?"
msgstr "इस पोस्ट को डीलीट करें?"
#: src/view/com/post-thread/PostThread.tsx:240
#: src/view/com/post-thread/PostThread.tsx:243
msgid "Deleted post."
msgstr "यह पोस्ट मिटाई जा चुकी है"
@@ -679,11 +689,11 @@ msgstr "देव सर्वर"
msgid "Developer Tools"
msgstr "डेवलपर उपकरण"
#: src/view/com/composer/Composer.tsx:142
#: src/view/com/composer/Composer.tsx:143
msgid "Discard"
msgstr ""
#: src/view/com/composer/Composer.tsx:136
#: src/view/com/composer/Composer.tsx:137
msgid "Discard draft"
msgstr "ड्राफ्ट हटाएं"
@@ -735,7 +745,7 @@ msgstr "प्रत्येक कोड एक बार काम करत
msgid "Edit image"
msgstr "छवि संपादित करें"
#: src/view/screens/ProfileList.tsx:422
#: src/view/screens/ProfileList.tsx:399
msgid "Edit list details"
msgstr "सूची विवरण संपादित करें"
@@ -783,7 +793,7 @@ msgstr "ईमेल:"
msgid "Enable this setting to only see replies between people you follow."
msgstr "इस सेटिंग को केवल उन लोगों के बीच जवाब देखने में सक्षम करें जिन्हें आप फॉलो करते हैं।।"
#: src/view/screens/Profile.tsx:471
#: src/view/screens/Profile.tsx:425
msgid "End of feed"
msgstr ""
@@ -811,7 +821,7 @@ msgstr "नीचे अपना नया ईमेल पता दर्ज
msgid "Enter your username and password"
msgstr "अपने यूज़रनेम और पासवर्ड दर्ज करें"
#: src/view/screens/Search/Search.tsx:105
#: src/view/screens/Search/Search.tsx:106
msgid "Error:"
msgstr ""
@@ -832,7 +842,7 @@ msgstr "अनुशंसित फ़ीड लोड करने में
msgid "Feed offline"
msgstr "फ़ीड ऑफ़लाइन है"
#: src/view/com/feeds/FeedPage.tsx:140
#: src/view/com/feeds/FeedPage.tsx:143
msgid "Feed Preferences"
msgstr "फ़ीड प्राथमिकता"
@@ -843,8 +853,8 @@ msgstr "प्रतिक्रिया"
#: src/view/screens/Feeds.tsx:475
#: src/view/screens/Profile.tsx:164
#: src/view/shell/bottom-bar/BottomBar.tsx:160
#: src/view/shell/desktop/LeftNav.tsx:333
#: src/view/shell/bottom-bar/BottomBar.tsx:181
#: src/view/shell/desktop/LeftNav.tsx:339
#: src/view/shell/Drawer.tsx:455
#: src/view/shell/Drawer.tsx:456
msgid "Feeds"
@@ -858,6 +868,14 @@ msgstr "सामग्री को व्यवस्थित करने
msgid "Feeds are custom algorithms that users build with a little coding expertise. <0/> for more information."
msgstr "फ़ीड कस्टम एल्गोरिदम हैं जिन्हें उपयोगकर्ता थोड़ी कोडिंग विशेषज्ञता के साथ बनाते हैं। <0/> अधिक जानकारी के लिए."
#: src/view/screens/Search/Search.tsx:422
msgid "Find users on Bluesky"
msgstr ""
#: src/view/screens/Search/Search.tsx:420
msgid "Find users with the search tool on the right"
msgstr ""
#: src/view/com/auth/onboarding/RecommendedFollowsItem.tsx:150
msgid "Finding similar accounts..."
msgstr "मिलते-जुलते खाते ढूँढना"
@@ -932,17 +950,17 @@ msgstr "गैलरी"
msgid "Get Started"
msgstr "प्रारंभ करें"
#: src/view/com/auth/LoggedOut.tsx:68
#: src/view/com/auth/LoggedOut.tsx:69
#: src/view/com/util/moderation/ScreenHider.tsx:105
#: src/view/com/auth/LoggedOut.tsx:70
#: src/view/com/auth/LoggedOut.tsx:71
#: src/view/com/util/moderation/ScreenHider.tsx:123
#: src/view/shell/desktop/LeftNav.tsx:103
msgid "Go back"
msgstr "वापस जाओ"
#: src/view/screens/ProfileFeed.tsx:112
#: src/view/screens/ProfileFeed.tsx:117
#: src/view/screens/ProfileList.tsx:869
#: src/view/screens/ProfileList.tsx:874
#: src/view/screens/ProfileFeed.tsx:103
#: src/view/screens/ProfileFeed.tsx:108
#: src/view/screens/ProfileList.tsx:839
#: src/view/screens/ProfileList.tsx:844
msgid "Go Back"
msgstr "वापस जाओ"
@@ -966,6 +984,7 @@ msgid "Here is your app password."
msgstr "यहां आपका ऐप पासवर्ड है."
#: src/view/com/notifications/FeedItem.tsx:316
#: src/view/com/util/moderation/ContentHider.tsx:103
msgid "Hide"
msgstr "इसे छिपाएं"
@@ -1001,14 +1020,14 @@ msgstr ""
#~ msgid "Hmmm, we're having trouble finding this feed. It may have been deleted."
#~ msgstr ""
#: src/view/shell/bottom-bar/BottomBar.tsx:116
#: src/view/shell/desktop/LeftNav.tsx:297
#: src/view/shell/bottom-bar/BottomBar.tsx:137
#: src/view/shell/desktop/LeftNav.tsx:303
#: src/view/shell/Drawer.tsx:379
#: src/view/shell/Drawer.tsx:380
msgid "Home"
msgstr "होम फीड"
#: src/view/com/pager/FeedsTabBarMobile.tsx:99
#: src/view/com/pager/FeedsTabBarMobile.tsx:96
#: src/view/screens/PreferencesHomeFeed.tsx:95
#: src/view/screens/Settings.tsx:481
msgid "Home Feed Preferences"
@@ -1074,6 +1093,10 @@ msgstr ""
msgid "Invite codes: {invitesAvailable} available"
msgstr ""
#: src/view/com/auth/HomeLoggedOutCTA.tsx:99
msgid "Jobs"
msgstr ""
#: src/view/com/modals/Waitlist.tsx:67
msgid "Join the waitlist"
msgstr "प्रतीक्षा सूची में शामिल हों"
@@ -1099,9 +1122,13 @@ msgstr "भाषा सेटिंग्स"
msgid "Languages"
msgstr "भाषा"
#: src/view/com/util/moderation/ContentHider.tsx:101
msgid "Learn more"
msgstr ""
#: src/view/com/util/moderation/PostAlerts.tsx:47
#: src/view/com/util/moderation/ProfileHeaderAlerts.tsx:65
#: src/view/com/util/moderation/ScreenHider.tsx:88
#: src/view/com/util/moderation/ScreenHider.tsx:104
msgid "Learn More"
msgstr "अधिक जानें"
@@ -1109,7 +1136,7 @@ msgstr "अधिक जानें"
#: src/view/com/util/moderation/PostAlerts.tsx:40
#: src/view/com/util/moderation/PostHider.tsx:76
#: src/view/com/util/moderation/ProfileHeaderAlerts.tsx:49
#: src/view/com/util/moderation/ScreenHider.tsx:85
#: src/view/com/util/moderation/ScreenHider.tsx:101
msgid "Learn more about this warning"
msgstr "इस चेतावनी के बारे में अधिक जानें"
@@ -1139,7 +1166,7 @@ msgstr "चित्र पुस्तकालय"
#~ msgid "Light"
#~ msgstr "लाइट मोड"
#: src/view/screens/ProfileFeed.tsx:643
#: src/view/screens/ProfileFeed.tsx:577
msgid "Like this feed"
msgstr "इस फ़ीड को लाइक करो"
@@ -1169,22 +1196,22 @@ msgid "List Name"
msgstr "सूची का नाम"
#: src/view/screens/Profile.tsx:165
#: src/view/shell/desktop/LeftNav.tsx:373
#: src/view/shell/desktop/LeftNav.tsx:376
#: src/view/shell/Drawer.tsx:471
#: src/view/shell/Drawer.tsx:472
msgid "Lists"
msgstr "सूची"
#: src/view/com/post-thread/PostThread.tsx:257
#: src/view/com/post-thread/PostThread.tsx:265
#: src/view/com/post-thread/PostThread.tsx:260
#: src/view/com/post-thread/PostThread.tsx:268
msgid "Load more posts"
msgstr "अधिक पोस्ट लोड करें"
#: src/view/screens/Notifications.tsx:141
#: src/view/screens/Notifications.tsx:144
msgid "Load new notifications"
msgstr "नई सूचनाएं लोड करें"
#: src/view/com/feeds/FeedPage.tsx:185
#: src/view/com/feeds/FeedPage.tsx:189
msgid "Load new posts"
msgstr "नई पोस्ट लोड करें"
@@ -1208,9 +1235,9 @@ msgstr ""
msgid "Login to account that is not listed"
msgstr "उस खाते में लॉग इन करें जो सूचीबद्ध नहीं है"
#: src/view/screens/ProfileFeed.tsx:482
msgid "Looks like this feed is only available to users with a Bluesky account. Please sign up or sign in to view this feed!"
msgstr ""
#: src/view/screens/ProfileFeed.tsx:472
#~ msgid "Looks like this feed is only available to users with a Bluesky account. Please sign up or sign in to view this feed!"
#~ msgstr ""
#: src/view/com/modals/LinkWarning.tsx:63
msgid "Make sure this is where you intend to go!"
@@ -1228,18 +1255,17 @@ msgstr ""
msgid "Mentioned users"
msgstr ""
#: src/view/screens/Search/Search.tsx:503
#: src/view/screens/Search/Search.tsx:537
msgid "Menu"
msgstr "मेनू"
#: src/view/com/posts/FeedErrorMessage.tsx:194
#: src/view/screens/ProfileFeed.tsx:490
msgid "Message from server"
msgstr ""
#: src/view/screens/Moderation.tsx:64
#: src/view/screens/Settings.tsx:563
#: src/view/shell/desktop/LeftNav.tsx:391
#: src/view/shell/desktop/LeftNav.tsx:394
#: src/view/shell/Drawer.tsx:490
#: src/view/shell/Drawer.tsx:491
msgid "Moderation"
@@ -1258,8 +1284,8 @@ msgid "More feeds"
msgstr "अधिक फ़ीड"
#: src/view/com/profile/ProfileHeader.tsx:548
#: src/view/screens/ProfileFeed.tsx:370
#: src/view/screens/ProfileList.tsx:606
#: src/view/screens/ProfileFeed.tsx:360
#: src/view/screens/ProfileList.tsx:583
msgid "More options"
msgstr "अधिक विकल्प"
@@ -1271,15 +1297,15 @@ msgstr "अधिक विकल्प"
msgid "Mute Account"
msgstr "खाता म्यूट करें"
#: src/view/screens/ProfileList.tsx:533
#: src/view/screens/ProfileList.tsx:510
msgid "Mute accounts"
msgstr "खातों को म्यूट करें"
#: src/view/screens/ProfileList.tsx:480
#: src/view/screens/ProfileList.tsx:457
msgid "Mute list"
msgstr ""
#: src/view/screens/ProfileList.tsx:293
#: src/view/screens/ProfileList.tsx:270
msgid "Mute these accounts?"
msgstr "इन खातों को म्यूट करें?"
@@ -1299,7 +1325,7 @@ msgstr "म्यूट किए गए खाते"
msgid "Muted accounts have their posts removed from your feed and from your notifications. Mutes are completely private."
msgstr "म्यूट किए गए खातों की पोस्ट आपके फ़ीड और आपकी सूचनाओं से हटा दी जाती हैं। म्यूट पूरी तरह से निजी हैं."
#: src/view/screens/ProfileList.tsx:295
#: src/view/screens/ProfileList.tsx:272
msgid "Muting is private. Muted accounts can interact with you, but you will not see their posts or receive notifications from them."
msgstr "म्यूट करना निजी है. म्यूट किए गए खाते आपके साथ इंटरैक्ट कर सकते हैं, लेकिन आप उनकी पोस्ट नहीं देखेंगे या उनसे सूचनाएं प्राप्त नहीं करेंगे।"
@@ -1337,12 +1363,12 @@ msgstr "अपने फ़ॉलोअर्स और डेटा तक प
msgid "New"
msgstr "नया"
#: src/view/com/feeds/FeedPage.tsx:196
#: src/view/com/feeds/FeedPage.tsx:200
#: src/view/screens/Feeds.tsx:510
#: src/view/screens/Profile.tsx:389
#: src/view/screens/ProfileFeed.tsx:451
#: src/view/screens/ProfileList.tsx:212
#: src/view/screens/ProfileList.tsx:244
#: src/view/screens/Profile.tsx:353
#: src/view/screens/ProfileFeed.tsx:430
#: src/view/screens/ProfileList.tsx:193
#: src/view/screens/ProfileList.tsx:221
#: src/view/shell/desktop/LeftNav.tsx:246
msgid "New post"
msgstr "नई पोस्ट"
@@ -1371,8 +1397,8 @@ msgstr "अगली फोटो"
msgid "No"
msgstr "नहीं"
#: src/view/screens/ProfileFeed.tsx:636
#: src/view/screens/ProfileList.tsx:740
#: src/view/screens/ProfileFeed.tsx:570
#: src/view/screens/ProfileList.tsx:711
msgid "No description"
msgstr "कोई विवरण नहीं"
@@ -1390,9 +1416,9 @@ msgstr "\"{query}\" के लिए कोई परिणाम नहीं
#~ msgstr "{0} के लिए कोई परिणाम नहीं मिला"
#: src/view/com/modals/ListAddRemoveUsers.tsx:127
#: src/view/screens/Search/Search.tsx:270
#: src/view/screens/Search/Search.tsx:298
#: src/view/screens/Search/Search.tsx:581
#: src/view/screens/Search/Search.tsx:271
#: src/view/screens/Search/Search.tsx:299
#: src/view/screens/Search/Search.tsx:615
#: src/view/shell/desktop/Search.tsx:210
msgid "No results found for {query}"
msgstr ""
@@ -1421,10 +1447,10 @@ msgstr ""
#~ msgid "Note: Third-party apps that display Bluesky content may not respect this setting."
#~ msgstr ""
#: src/view/screens/Notifications.tsx:108
#: src/view/screens/Notifications.tsx:132
#: src/view/shell/bottom-bar/BottomBar.tsx:187
#: src/view/shell/desktop/LeftNav.tsx:355
#: src/view/screens/Notifications.tsx:109
#: src/view/screens/Notifications.tsx:133
#: src/view/shell/bottom-bar/BottomBar.tsx:205
#: src/view/shell/desktop/LeftNav.tsx:358
#: src/view/shell/Drawer.tsx:416
#: src/view/shell/Drawer.tsx:417
msgid "Notifications"
@@ -1438,7 +1464,7 @@ msgstr "अरे नहीं!"
msgid "Okay"
msgstr "ठीक है"
#: src/view/com/composer/Composer.tsx:348
#: src/view/com/composer/Composer.tsx:354
msgid "One or more images is missing alt text."
msgstr "एक या अधिक छवियाँ alt पाठ याद आती हैं।।"
@@ -1446,7 +1472,7 @@ msgstr "एक या अधिक छवियाँ alt पाठ याद
msgid "Only {0} can reply."
msgstr ""
#: src/view/com/pager/FeedsTabBarMobile.tsx:79
#: src/view/com/pager/FeedsTabBarMobile.tsx:76
msgid "Open navigation"
msgstr "ओपन नेविगेशन"
@@ -1561,13 +1587,13 @@ msgstr "कृपया अपना पासवर्ड भी दर्ज
msgid "Please tell us why you think this decision was incorrect."
msgstr ""
#: src/view/com/composer/Composer.tsx:331
#: src/view/com/post-thread/PostThread.tsx:223
#: src/view/screens/PostThread.tsx:78
#: src/view/com/composer/Composer.tsx:337
#: src/view/com/post-thread/PostThread.tsx:226
#: src/view/screens/PostThread.tsx:80
msgid "Post"
msgstr "पोस्ट"
#: src/view/com/post-thread/PostThread.tsx:382
#: src/view/com/post-thread/PostThread.tsx:385
msgid "Post hidden"
msgstr "छुपा पोस्ट"
@@ -1579,7 +1605,7 @@ msgstr "पोस्ट भाषा"
msgid "Post Languages"
msgstr "पोस्ट भाषा"
#: src/view/com/post-thread/PostThread.tsx:434
#: src/view/com/post-thread/PostThread.tsx:437
msgid "Post not found"
msgstr "पोस्ट नहीं मिला"
@@ -1615,7 +1641,8 @@ msgstr "गोपनीयता नीति"
msgid "Processing..."
msgstr "प्रसंस्करण..."
#: src/view/shell/bottom-bar/BottomBar.tsx:229
#: src/view/shell/bottom-bar/BottomBar.tsx:247
#: src/view/shell/desktop/LeftNav.tsx:412
#: src/view/shell/Drawer.tsx:69
#: src/view/shell/Drawer.tsx:525
#: src/view/shell/Drawer.tsx:526
@@ -1682,7 +1709,7 @@ msgstr "फ़ीड हटाएँ"
#: src/view/com/feeds/FeedSourceCard.tsx:105
#: src/view/com/feeds/FeedSourceCard.tsx:172
#: src/view/screens/ProfileFeed.tsx:280
#: src/view/screens/ProfileFeed.tsx:270
msgid "Remove from my feeds"
msgstr "मेरे फ़ीड से हटाएँ"
@@ -1727,11 +1754,11 @@ msgstr "रिपोर्ट {collectionName}"
msgid "Report Account"
msgstr "रिपोर्ट"
#: src/view/screens/ProfileFeed.tsx:300
#: src/view/screens/ProfileFeed.tsx:290
msgid "Report feed"
msgstr "रिपोर्ट फ़ीड"
#: src/view/screens/ProfileList.tsx:448
#: src/view/screens/ProfileList.tsx:425
msgid "Report List"
msgstr "रिपोर्ट सूची"
@@ -1843,10 +1870,10 @@ msgstr "सहेजे गए फ़ीड"
#: src/view/com/modals/ListAddRemoveUsers.tsx:75
#: src/view/com/util/forms/SearchInput.tsx:64
#: src/view/screens/Search/Search.tsx:381
#: src/view/screens/Search/Search.tsx:533
#: src/view/shell/bottom-bar/BottomBar.tsx:138
#: src/view/shell/desktop/LeftNav.tsx:315
#: src/view/screens/Search/Search.tsx:401
#: src/view/screens/Search/Search.tsx:567
#: src/view/shell/bottom-bar/BottomBar.tsx:159
#: src/view/shell/desktop/LeftNav.tsx:321
#: src/view/shell/desktop/Search.tsx:161
#: src/view/shell/desktop/Search.tsx:170
#: src/view/shell/Drawer.tsx:343
@@ -1855,14 +1882,14 @@ msgid "Search"
msgstr "खोज"
#: src/view/screens/Search/Search.tsx:390
msgid "Search for posts and users."
msgstr ""
#~ msgid "Search for posts and users."
#~ msgstr ""
#: src/view/com/modals/ChangeEmail.tsx:110
msgid "Security Step Required"
msgstr "सुरक्षा चरण आवश्यक"
#: src/view/com/auth/SplashScreen.tsx:29
#: src/view/com/auth/HomeLoggedOutCTA.tsx:39
msgid "See what's next"
msgstr "आगे क्या है"
@@ -1936,7 +1963,7 @@ msgid "Set this setting to \"Yes\" to show samples of your saved feeds in your f
msgstr "इस सेटिंग को अपने निम्नलिखित फ़ीड में अपने सहेजे गए फ़ीड के नमूने दिखाने के लिए \"हाँ\" पर सेट करें। यह एक प्रयोगात्मक विशेषता है।।"
#: src/view/screens/Settings.tsx:277
#: src/view/shell/desktop/LeftNav.tsx:427
#: src/view/shell/desktop/LeftNav.tsx:430
#: src/view/shell/Drawer.tsx:546
#: src/view/shell/Drawer.tsx:547
msgid "Settings"
@@ -1948,11 +1975,11 @@ msgstr "यौन गतिविधि या कामुक नग्नत
#: src/view/com/profile/ProfileHeader.tsx:338
#: src/view/com/util/forms/PostDropdownBtn.tsx:129
#: src/view/screens/ProfileList.tsx:407
#: src/view/screens/ProfileList.tsx:384
msgid "Share"
msgstr "शेयर"
#: src/view/screens/ProfileFeed.tsx:312
#: src/view/screens/ProfileFeed.tsx:302
msgid "Share feed"
msgstr ""
@@ -1960,11 +1987,12 @@ msgstr ""
#~ msgid "Share link"
#~ msgstr "लिंक शेयर करें"
#: src/view/com/util/moderation/ContentHider.tsx:105
#: src/view/screens/Settings.tsx:316
msgid "Show"
msgstr "दिखाओ"
#: src/view/com/util/moderation/ScreenHider.tsx:114
#: src/view/com/util/moderation/ScreenHider.tsx:132
msgid "Show anyway"
msgstr "दिखाओ"
@@ -1992,15 +2020,23 @@ msgstr "रीपोस्ट दिखाएँ"
msgid "Show users"
msgstr "लोग दिखाएँ"
#: src/view/com/auth/HomeLoggedOutCTA.tsx:70
#: src/view/com/auth/login/Login.tsx:98
#: src/view/com/auth/SplashScreen.tsx:49
#: src/view/shell/NavSignupCard.tsx:52
#: src/view/shell/NavSignupCard.tsx:53
#: src/view/com/auth/SplashScreen.tsx:54
#: src/view/shell/bottom-bar/BottomBar.tsx:285
#: src/view/shell/bottom-bar/BottomBar.tsx:286
#: src/view/shell/bottom-bar/BottomBar.tsx:288
#: src/view/shell/bottom-bar/BottomBarWeb.tsx:177
#: src/view/shell/bottom-bar/BottomBarWeb.tsx:178
#: src/view/shell/bottom-bar/BottomBarWeb.tsx:180
#: src/view/shell/NavSignupCard.tsx:58
#: src/view/shell/NavSignupCard.tsx:59
msgid "Sign in"
msgstr "साइन इन करें"
#: src/view/com/auth/SplashScreen.tsx:52
#: src/view/com/auth/SplashScreen.web.tsx:84
#: src/view/com/auth/HomeLoggedOutCTA.tsx:78
#: src/view/com/auth/SplashScreen.tsx:57
#: src/view/com/auth/SplashScreen.web.tsx:87
msgid "Sign In"
msgstr "साइन इन करें"
@@ -2022,16 +2058,26 @@ msgstr "साइन इन करें"
msgid "Sign out"
msgstr "साइन आउट"
#: src/view/shell/NavSignupCard.tsx:43
#: src/view/shell/NavSignupCard.tsx:44
#: src/view/shell/NavSignupCard.tsx:46
#: src/view/shell/bottom-bar/BottomBar.tsx:275
#: src/view/shell/bottom-bar/BottomBar.tsx:276
#: src/view/shell/bottom-bar/BottomBar.tsx:278
#: src/view/shell/bottom-bar/BottomBarWeb.tsx:167
#: src/view/shell/bottom-bar/BottomBarWeb.tsx:168
#: src/view/shell/bottom-bar/BottomBarWeb.tsx:170
#: src/view/shell/NavSignupCard.tsx:49
#: src/view/shell/NavSignupCard.tsx:50
#: src/view/shell/NavSignupCard.tsx:52
msgid "Sign up"
msgstr ""
#: src/view/shell/NavSignupCard.tsx:36
#: src/view/shell/NavSignupCard.tsx:42
msgid "Sign up or sign in to join the conversation"
msgstr ""
#: src/view/com/util/moderation/ScreenHider.tsx:76
msgid "Sign-in Required"
msgstr ""
#: src/view/screens/Settings.tsx:327
msgid "Signed in as"
msgstr "आपने इस रूप में साइन इन करा है:"
@@ -2069,15 +2115,15 @@ msgstr "Storybook"
msgid "Submit"
msgstr ""
#: src/view/screens/ProfileList.tsx:597
#: src/view/screens/ProfileList.tsx:574
msgid "Subscribe"
msgstr "सब्सक्राइब"
#: src/view/screens/ProfileList.tsx:593
#: src/view/screens/ProfileList.tsx:570
msgid "Subscribe to this list"
msgstr "इस सूची को सब्सक्राइब करें"
#: src/view/screens/Search/Search.tsx:354
#: src/view/screens/Search/Search.tsx:357
msgid "Suggested Follows"
msgstr "अनुशंसित लोग"
@@ -2127,7 +2173,7 @@ msgstr "सामुदायिक दिशानिर्देशों क
msgid "The Copyright Policy has been moved to <0/>"
msgstr "कॉपीराइट नीति को <0/> पर स्थानांतरित कर दिया गया है"
#: src/view/com/post-thread/PostThread.tsx:437
#: src/view/com/post-thread/PostThread.tsx:440
msgid "The post may have been deleted."
msgstr "हो सकता है कि यह पोस्ट हटा दी गई हो।"
@@ -2151,10 +2197,14 @@ msgstr "एप्लिकेशन में एक अप्रत्याश
msgid "This {0} has been labeled."
msgstr ""
#: src/view/com/util/moderation/ScreenHider.tsx:72
#: src/view/com/util/moderation/ScreenHider.tsx:88
msgid "This {screenDescription} has been flagged:"
msgstr "यह {screenDescription} फ्लैग किया गया है:"
#: src/view/com/util/moderation/ScreenHider.tsx:83
msgid "This account has requested that users sign in to view their profile."
msgstr ""
#: src/view/com/posts/FeedErrorMessage.tsx:107
msgid "This content is not viewable without a Bluesky account."
msgstr ""
@@ -2214,11 +2264,11 @@ msgstr "अनुवाद"
msgid "Try again"
msgstr "फिर से कोशिश करो"
#: src/view/screens/ProfileList.tsx:495
#: src/view/screens/ProfileList.tsx:472
msgid "Un-block list"
msgstr ""
#: src/view/screens/ProfileList.tsx:480
#: src/view/screens/ProfileList.tsx:457
msgid "Un-mute list"
msgstr ""
@@ -2254,7 +2304,7 @@ msgstr "अनम्यूट खाता"
msgid "Unmute thread"
msgstr "थ्रेड को अनम्यूट करें"
#: src/view/screens/ProfileList.tsx:463
#: src/view/screens/ProfileList.tsx:440
msgid "Unpin moderation list"
msgstr ""
@@ -2303,7 +2353,7 @@ msgstr "लोग सूचियाँ"
msgid "Username or email address"
msgstr "यूजर नाम या ईमेल पता"
#: src/view/screens/ProfileList.tsx:767
#: src/view/screens/ProfileList.tsx:738
msgid "Users"
msgstr "यूजर लोग"
@@ -2360,7 +2410,7 @@ msgstr "हम आपके हमारी सेवा में शामि
#~ msgid "We're sorry, but this feed is currently receiving high traffic and is temporarily unavailable. Please try again later."
#~ msgstr ""
#: src/view/screens/Search/Search.tsx:237
#: src/view/screens/Search/Search.tsx:238
msgid "We're sorry, but your search could not be completed. Please try again in a few minutes."
msgstr ""
@@ -2376,6 +2426,10 @@ msgstr "<0>Bluesky</0> में आपका स्वागत है"
msgid "What is the issue with this {collectionName}?"
msgstr "इस {collectionName} के साथ क्या मुद्दा है?"
#: src/view/com/auth/SplashScreen.tsx:34
msgid "What's up?"
msgstr ""
#: src/view/com/modals/lang-settings/PostLanguagesSettings.tsx:78
msgid "Which languages are used in this post?"
msgstr "इस पोस्ट में किस भाषा का उपयोग किया जाता है?"
@@ -2397,7 +2451,7 @@ msgstr ""
msgid "Wide"
msgstr "चौड़ा"
#: src/view/com/composer/Composer.tsx:403
#: src/view/com/composer/Composer.tsx:409
msgid "Write post"
msgstr "पोस्ट लिखो"
@@ -2436,16 +2490,16 @@ msgstr ""
msgid "You don't have any saved feeds."
msgstr "आपके पास कोई सहेजी गई फ़ीड नहीं है."
#: src/view/com/post-thread/PostThread.tsx:385
#: src/view/com/post-thread/PostThread.tsx:388
msgid "You have blocked the author or you have been blocked by the author."
msgstr "आपने लेखक को अवरुद्ध किया है या आपने लेखक द्वारा अवरुद्ध किया है।।"
#: src/view/com/feeds/ProfileFeedgens.tsx:154
#: src/view/com/feeds/ProfileFeedgens.tsx:141
msgid "You have no feeds."
msgstr ""
#: src/view/com/lists/MyLists.tsx:89
#: src/view/com/lists/ProfileLists.tsx:158
#: src/view/com/lists/ProfileLists.tsx:145
msgid "You have no lists."
msgstr "आपके पास कोई सूची नहीं है।।"
File diff suppressed because it is too large Load Diff
+38 -5
View File
@@ -1,16 +1,27 @@
import React from 'react'
import {AppBskyActorDefs} from '@atproto/api'
import {AppBskyActorDefs, ModerationOpts, moderateProfile} from '@atproto/api'
import {useQuery, useQueryClient} from '@tanstack/react-query'
import {logger} from '#/logger'
import {getAgent} from '#/state/session'
import {useMyFollowsQuery} from '#/state/queries/my-follows'
import {STALE} from '#/state/queries'
import {
DEFAULT_LOGGED_OUT_PREFERENCES,
getModerationOpts,
useModerationOpts,
} from './preferences'
const DEFAULT_MOD_OPTS = getModerationOpts({
userDid: '',
preferences: DEFAULT_LOGGED_OUT_PREFERENCES,
})
export const RQKEY = (prefix: string) => ['actor-autocomplete', prefix]
export function useActorAutocompleteQuery(prefix: string) {
const {data: follows, isFetching} = useMyFollowsQuery()
const moderationOpts = useModerationOpts()
return useQuery<AppBskyActorDefs.ProfileViewBasic[]>({
staleTime: STALE.MINUTES.ONE,
@@ -22,9 +33,20 @@ export function useActorAutocompleteQuery(prefix: string) {
limit: 8,
})
: undefined
return computeSuggestions(prefix, follows, res?.data.actors)
return res?.data.actors || []
},
enabled: !isFetching,
select: React.useCallback(
(data: AppBskyActorDefs.ProfileViewBasic[]) => {
return computeSuggestions(
prefix,
follows,
data,
moderationOpts || DEFAULT_MOD_OPTS,
)
},
[prefix, follows, moderationOpts],
),
})
}
@@ -32,6 +54,7 @@ export type ActorAutocompleteFn = ReturnType<typeof useActorAutocompleteFn>
export function useActorAutocompleteFn() {
const queryClient = useQueryClient()
const {data: follows} = useMyFollowsQuery()
const moderationOpts = useModerationOpts()
return React.useCallback(
async ({query, limit = 8}: {query: string; limit?: number}) => {
@@ -54,9 +77,14 @@ export function useActorAutocompleteFn() {
}
}
return computeSuggestions(query, follows, res?.data.actors)
return computeSuggestions(
query,
follows,
res?.data.actors,
moderationOpts || DEFAULT_MOD_OPTS,
)
},
[follows, queryClient],
[follows, queryClient, moderationOpts],
)
}
@@ -64,6 +92,7 @@ function computeSuggestions(
prefix: string,
follows: AppBskyActorDefs.ProfileViewBasic[] | undefined,
searched: AppBskyActorDefs.ProfileViewBasic[] = [],
moderationOpts: ModerationOpts,
) {
let items: AppBskyActorDefs.ProfileViewBasic[] = []
if (follows) {
@@ -76,10 +105,14 @@ function computeSuggestions(
handle: item.handle,
displayName: item.displayName,
avatar: item.avatar,
labels: item.labels,
})
}
}
return items
return items.filter(profile => {
const mod = moderateProfile(profile, moderationOpts)
return !mod.account.filter
})
}
function prefixMatch(
-45
View File
@@ -161,51 +161,6 @@ export function useFeedSourceInfoQuery({uri}: {uri: string}) {
})
}
export const isFeedPublicQueryKey = ({uri}: {uri: string}) => [
'isFeedPublic',
uri,
]
export function useIsFeedPublicQuery({uri}: {uri: string}) {
return useQuery({
queryKey: isFeedPublicQueryKey({uri}),
queryFn: async ({queryKey}) => {
const [, uri] = queryKey
try {
const res = await getAgent().app.bsky.feed.getFeed({
feed: uri,
limit: 1,
})
return {
isPublic: Boolean(res.data.feed),
error: undefined,
}
} catch (e: any) {
/**
* This should be an `XRPCError`, but I can't safely import from
* `@atproto/xrpc` due to a depdency on node's `crypto` module.
*
* @see https://github.com/bluesky-social/atproto/blob/c17971a2d8e424cc7f10c071d97c07c08aa319cf/packages/xrpc/src/client.ts#L126
*/
if (e?.status === 401) {
return {
isPublic: false,
error: e,
}
}
/*
* Non-401 response means something else went wrong on the server
*/
return {
isPublic: true,
error: e,
}
}
},
})
}
export const useGetPopularFeedsQueryKey = ['getPopularFeeds']
export function useGetPopularFeedsQuery() {
+17 -14
View File
@@ -16,7 +16,7 @@
* 3. Don't call this query's `refetch()` if you're trying to sync latest; call `checkUnread()` instead.
*/
import {useEffect} from 'react'
import {useEffect, useRef} from 'react'
import {AppBskyFeedDefs} from '@atproto/api'
import {
useInfiniteQuery,
@@ -49,6 +49,7 @@ export function useNotificationFeedQuery(opts?: {enabled?: boolean}) {
const threadMutes = useMutedThreads()
const unreads = useUnreadNotificationsApi()
const enabled = opts?.enabled !== false
const lastPageCountRef = useRef(0)
const query = useInfiniteQuery<
FeedPage,
@@ -104,24 +105,26 @@ export function useNotificationFeedQuery(opts?: {enabled?: boolean}) {
useEffect(() => {
const {isFetching, hasNextPage, data} = query
let count = 0
let numEmpties = 0
for (const page of data?.pages || []) {
if (!page.items.length) {
numEmpties++
}
count += page.items.length
if (isFetching || !hasNextPage) {
return
}
// avoid double-fires of fetchNextPage()
if (
!isFetching &&
hasNextPage &&
count < PAGE_SIZE &&
numEmpties < 3 &&
(data?.pages.length || 0) < 6
lastPageCountRef.current !== 0 &&
lastPageCountRef.current === data?.pages?.length
) {
return
}
// fetch next page if we haven't gotten a full page of content
let count = 0
for (const page of data?.pages || []) {
count += page.items.length
}
if (count < PAGE_SIZE && (data?.pages.length || 0) < 6) {
query.fetchNextPage()
lastPageCountRef.current = data?.pages?.length || 0
}
}, [query])
+24 -14
View File
@@ -1,4 +1,4 @@
import React, {useCallback, useEffect} from 'react'
import React, {useCallback, useEffect, useRef} from 'react'
import {
AppBskyFeedDefs,
AppBskyFeedPost,
@@ -78,6 +78,7 @@ export interface FeedPageUnselected {
api: FeedAPI
cursor: string | undefined
feed: AppBskyFeedDefs.FeedViewPost[]
fetchedAt: number
}
export interface FeedPage {
@@ -85,6 +86,7 @@ export interface FeedPage {
tuner: FeedTuner | NoopFeedTuner
cursor: string | undefined
slices: FeedPostSlice[]
fetchedAt: number
}
const PAGE_SIZE = 30
@@ -98,11 +100,12 @@ export function usePostFeedQuery(
const feedTuners = useFeedTuners(feedDesc)
const moderationOpts = useModerationOpts()
const enabled = opts?.enabled !== false && Boolean(moderationOpts)
const lastRun = React.useRef<{
const lastRun = useRef<{
data: InfiniteData<FeedPageUnselected>
args: typeof selectArgs
result: InfiniteData<FeedPage>
} | null>(null)
const lastPageCountRef = useRef(0)
// Make sure this doesn't invalidate unless really needed.
const selectArgs = React.useMemo(
@@ -152,6 +155,7 @@ export function usePostFeedQuery(
api,
cursor: res.cursor,
feed: res.feed,
fetchedAt: Date.now(),
}
},
initialPageParam: undefined,
@@ -214,6 +218,7 @@ export function usePostFeedQuery(
api: page.api,
tuner,
cursor: page.cursor,
fetchedAt: page.fetchedAt,
slices: tuner
.tune(page.feed)
.map(slice => {
@@ -279,26 +284,28 @@ export function usePostFeedQuery(
useEffect(() => {
const {isFetching, hasNextPage, data} = query
if (isFetching || !hasNextPage) {
return
}
// avoid double-fires of fetchNextPage()
if (
lastPageCountRef.current !== 0 &&
lastPageCountRef.current === data?.pages?.length
) {
return
}
// fetch next page if we haven't gotten a full page of content
let count = 0
let numEmpties = 0
for (const page of data?.pages || []) {
if (page.slices.length === 0) {
numEmpties++
}
for (const slice of page.slices) {
count += slice.items.length
}
}
if (
!isFetching &&
hasNextPage &&
count < PAGE_SIZE &&
numEmpties < 3 &&
(data?.pages.length || 0) < 6
) {
if (count < PAGE_SIZE && (data?.pages.length || 0) < 6) {
query.fetchNextPage()
lastPageCountRef.current = data?.pages?.length || 0
}
}, [query])
@@ -409,6 +416,9 @@ export function* findAllPostsInQueryData(
}
function assertSomePostsPassModeration(feed: AppBskyFeedDefs.FeedViewPost[]) {
// no posts in this feed
if (feed.length === 0) return true
// assume false
let somePostsPassModeration = false
+1 -3
View File
@@ -35,9 +35,7 @@ export function useProfileQuery({did}: {did: string | undefined}) {
// if you remove it, the UI infinite-loops
// -prf
staleTime: isCurrentAccount ? STALE.SECONDS.THIRTY : STALE.MINUTES.FIVE,
refetchInterval: isCurrentAccount
? STALE.SECONDS.THIRTY
: STALE.MINUTES.FIVE,
refetchInterval: STALE.MINUTES.FIVE,
queryKey: RQKEY(did || ''),
queryFn: async () => {
const res = await getAgent().getProfile({actor: did || ''})
+1 -1
View File
@@ -22,7 +22,7 @@ type Controls = {
/**
* The did of the account to populate the login form with.
*/
requestedAccount?: string
requestedAccount?: string | 'none' | 'new'
}) => void
/**
* Clears the requested account so that next time the logged out view is
+165
View File
@@ -0,0 +1,165 @@
import React from 'react'
import {StyleSheet, TouchableOpacity, View} from 'react-native'
import {useLingui} from '@lingui/react'
import {Trans, msg} from '@lingui/macro'
import {ScrollView} from '../util/Views'
import {Text} from '../util/text/Text'
import {usePalette} from '#/lib/hooks/usePalette'
import {colors, s} from '#/lib/styles'
import {TextLink} from '../util/Link'
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
import {useLoggedOutViewControls} from '#/state/shell/logged-out'
export function HomeLoggedOutCTA() {
const pal = usePalette('default')
const {_} = useLingui()
const {isMobile} = useWebMediaQueries()
const {requestSwitchToAccount} = useLoggedOutViewControls()
const showCreateAccount = React.useCallback(() => {
requestSwitchToAccount({requestedAccount: 'new'})
}, [requestSwitchToAccount])
const showSignIn = React.useCallback(() => {
requestSwitchToAccount({requestedAccount: 'none'})
}, [requestSwitchToAccount])
return (
<ScrollView style={styles.container} testID="loggedOutCTA">
<View style={[styles.hero, isMobile && styles.heroMobile]}>
<Text style={[styles.title, pal.link]}>
<Trans>Bluesky</Trans>
</Text>
<Text
style={[
styles.subtitle,
isMobile && styles.subtitleMobile,
pal.textLight,
]}>
<Trans>See what's next</Trans>
</Text>
</View>
<View
testID="signinOrCreateAccount"
style={isMobile ? undefined : styles.btnsDesktop}>
<TouchableOpacity
testID="createAccountButton"
style={[
styles.btn,
isMobile && styles.btnMobile,
{backgroundColor: colors.blue3},
]}
onPress={showCreateAccount}
accessibilityRole="button"
accessibilityLabel={_(msg`Create new account`)}
accessibilityHint="Opens flow to create a new Bluesky account">
<Text
style={[
s.white,
styles.btnLabel,
isMobile && styles.btnLabelMobile,
]}>
<Trans>Create a new account</Trans>
</Text>
</TouchableOpacity>
<TouchableOpacity
testID="signInButton"
style={[styles.btn, isMobile && styles.btnMobile, pal.btn]}
onPress={showSignIn}
accessibilityRole="button"
accessibilityLabel={_(msg`Sign in`)}
accessibilityHint="Opens flow to sign into your existing Bluesky account">
<Text
style={[
pal.text,
styles.btnLabel,
isMobile && styles.btnLabelMobile,
]}>
<Trans>Sign In</Trans>
</Text>
</TouchableOpacity>
</View>
<View style={[styles.footer, pal.view, pal.border]}>
<TextLink
type="2xl"
href="https://blueskyweb.xyz"
text={_(msg`Business`)}
style={[styles.footerLink, pal.link]}
/>
<TextLink
type="2xl"
href="https://blueskyweb.xyz/blog"
text={_(msg`Blog`)}
style={[styles.footerLink, pal.link]}
/>
<TextLink
type="2xl"
href="https://blueskyweb.xyz/join"
text={_(msg`Jobs`)}
style={[styles.footerLink, pal.link]}
/>
</View>
</ScrollView>
)
}
const styles = StyleSheet.create({
container: {
height: '100%',
},
hero: {
justifyContent: 'center',
paddingTop: 100,
paddingBottom: 30,
},
heroMobile: {
paddingBottom: 50,
},
title: {
textAlign: 'center',
fontSize: 68,
fontWeight: 'bold',
},
subtitle: {
textAlign: 'center',
fontSize: 48,
fontWeight: 'bold',
},
subtitleMobile: {
fontSize: 42,
},
btnsDesktop: {
flexDirection: 'row',
justifyContent: 'center',
gap: 20,
marginHorizontal: 20,
},
btn: {
borderRadius: 32,
width: 230,
paddingVertical: 12,
marginBottom: 20,
},
btnMobile: {
flex: 1,
width: 'auto',
marginHorizontal: 20,
paddingVertical: 16,
},
btnLabel: {
textAlign: 'center',
fontSize: 18,
},
btnLabelMobile: {
textAlign: 'center',
fontSize: 21,
},
footer: {
flexDirection: 'row',
gap: 20,
justifyContent: 'center',
},
footerLink: {},
})
+3 -1
View File
@@ -33,7 +33,9 @@ export function LoggedOut({onDismiss}: {onDismiss?: () => void}) {
const {requestedAccountSwitchTo} = useLoggedOutView()
const [screenState, setScreenState] = React.useState<ScreenState>(
requestedAccountSwitchTo
? ScreenState.S_Login
? requestedAccountSwitchTo === 'new'
? ScreenState.S_CreateAccount
: ScreenState.S_Login
: ScreenState.S_LoginOrCreateAccount,
)
const {isMobile} = useWebMediaQueries()
+11 -5
View File
@@ -7,6 +7,8 @@ import {usePalette} from 'lib/hooks/usePalette'
import {CenteredView} from '../util/Views'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {Logo} from '#/view/icons/Logo'
import {Logotype} from '#/view/icons/Logotype'
export const SplashScreen = ({
onPressSignin,
@@ -22,11 +24,14 @@ export const SplashScreen = ({
<CenteredView style={[styles.container, pal.view]}>
<ErrorBoundary>
<View style={styles.hero}>
<Text style={[styles.title, pal.link]}>
<Trans>Bluesky</Trans>
</Text>
<Text style={[styles.subtitle, pal.textLight]}>
<Trans>See what's next</Trans>
<Logo width={92} fill="sky" />
<View style={{paddingTop: 40, paddingBottom: 6}}>
<Logotype width={161} fill={pal.text.color} />
</View>
<Text type="lg-medium" style={[pal.textLight]}>
<Trans>What's up?</Trans>
</Text>
</View>
<View testID="signinOrCreateAccount" style={styles.btns}>
@@ -65,6 +70,7 @@ const styles = StyleSheet.create({
hero: {
flex: 2,
justifyContent: 'center',
alignItems: 'center',
},
btns: {
paddingBottom: 40,
+10 -10
View File
@@ -10,6 +10,8 @@ import {CenteredView} from '../util/Views'
import {isWeb} from 'platform/detection'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {Trans} from '@lingui/macro'
import {Logo} from '#/view/icons/Logo'
import {Logotype} from '#/view/icons/Logotype'
export const SplashScreen = ({
onDismiss,
@@ -55,14 +57,15 @@ export const SplashScreen = ({
styles.containerInner,
isMobileWeb && styles.containerInnerMobile,
pal.border,
{alignItems: 'center'},
]}>
<ErrorBoundary>
<Text style={isMobileWeb ? styles.titleMobile : styles.title}>
Bluesky
</Text>
<Text style={isMobileWeb ? styles.subtitleMobile : styles.subtitle}>
See what's next
</Text>
<Logo width={92} fill="sky" />
<View style={{paddingTop: 40, paddingBottom: 20}}>
<Logotype width={161} fill={pal.text.color} />
</View>
<View testID="signinOrCreateAccount" style={styles.btns}>
<TouchableOpacity
testID="createAccountButton"
@@ -117,8 +120,6 @@ function Footer({styles}: {styles: ReturnType<typeof useStyles>}) {
)
}
const useStyles = () => {
const {isTabletOrMobile} = useWebMediaQueries()
const isMobileWeb = isWeb && isTabletOrMobile
return StyleSheet.create({
container: {
height: '100%',
@@ -161,8 +162,7 @@ const useStyles = () => {
paddingBottom: 30,
},
btns: {
flexDirection: isMobileWeb ? 'column' : 'row',
gap: 20,
gap: 10,
justifyContent: 'center',
paddingBottom: 40,
},
+6
View File
@@ -62,6 +62,7 @@ import {useProfileQuery} from '#/state/queries/profile'
import {useComposerControls} from '#/state/shell/composer'
import {emitPostCreated} from '#/state/events'
import {ThreadgateSetting} from '#/state/queries/threadgate'
import {logger} from '#/logger'
type Props = ComposerOpts
export const ComposePost = observer(function ComposePost({
@@ -228,6 +229,11 @@ export const ComposePost = observer(function ComposePost({
})
).uri
} catch (e: any) {
logger.error(e, {
message: `Composer: create post failed`,
hasImages: gallery.size > 0,
})
if (extLink) {
setExtLink({
...extLink,
+26 -22
View File
@@ -7,13 +7,15 @@ import {useNavigation} from '@react-navigation/native'
import {useAnalytics} from 'lib/analytics/analytics'
import {useQueryClient} from '@tanstack/react-query'
import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed'
import {useOnMainScroll} from 'lib/hooks/useOnMainScroll'
import {MainScrollProvider} from '../util/MainScrollProvider'
import {usePalette} from 'lib/hooks/usePalette'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {useSetMinimalShellMode} from '#/state/shell'
import {FeedDescriptor, FeedParams} from '#/state/queries/post-feed'
import {ComposeIcon2} from 'lib/icons'
import {colors, s} from 'lib/styles'
import {FlatList, View, useWindowDimensions} from 'react-native'
import {View, useWindowDimensions} from 'react-native'
import {ListMethods} from '../util/List'
import {Feed} from '../posts/Feed'
import {TextLink} from '../util/Link'
import {FAB} from '../util/fab/FAB'
@@ -27,7 +29,7 @@ import {truncateAndInvalidate} from '#/state/queries/util'
import {TabState, getTabState, getRootNavigation} from '#/lib/routes/helpers'
import {isNative} from '#/platform/detection'
const POLL_FREQ = 30e3 // 30sec
const POLL_FREQ = 60e3 // 60sec
export function FeedPage({
testID,
@@ -51,10 +53,11 @@ export function FeedPage({
const {isDesktop} = useWebMediaQueries()
const queryClient = useQueryClient()
const {openComposer} = useComposerControls()
const [onMainScroll, isScrolledDown, resetMainScroll] = useOnMainScroll()
const [isScrolledDown, setIsScrolledDown] = React.useState(false)
const setMinimalShellMode = useSetMinimalShellMode()
const {screen, track} = useAnalytics()
const headerOffset = useHeaderOffset()
const scrollElRef = React.useRef<FlatList>(null)
const scrollElRef = React.useRef<ListMethods>(null)
const [hasNew, setHasNew] = React.useState(false)
const scrollToTop = React.useCallback(() => {
@@ -62,8 +65,8 @@ export function FeedPage({
animated: isNative,
offset: -headerOffset,
})
resetMainScroll()
}, [headerOffset, resetMainScroll])
setMinimalShellMode(false)
}, [headerOffset, setMinimalShellMode])
const onSoftReset = React.useCallback(() => {
const isScreenFocused =
@@ -164,21 +167,22 @@ export function FeedPage({
return (
<View testID={testID} style={s.h100pct}>
<Feed
testID={testID ? `${testID}-feed` : undefined}
enabled={isPageFocused}
feed={feed}
feedParams={feedParams}
pollInterval={POLL_FREQ}
scrollElRef={scrollElRef}
onScroll={onMainScroll}
onHasNew={setHasNew}
scrollEventThrottle={1}
renderEmptyState={renderEmptyState}
renderEndOfFeed={renderEndOfFeed}
ListHeaderComponent={ListHeaderComponent}
headerOffset={headerOffset}
/>
<MainScrollProvider>
<Feed
testID={testID ? `${testID}-feed` : undefined}
enabled={isPageFocused}
feed={feed}
feedParams={feedParams}
pollInterval={POLL_FREQ}
scrollElRef={scrollElRef}
onScrolledDownChange={setIsScrolledDown}
onHasNew={setHasNew}
renderEmptyState={renderEmptyState}
renderEndOfFeed={renderEndOfFeed}
ListHeaderComponent={ListHeaderComponent}
headerOffset={headerOffset}
/>
</MainScrollProvider>
{(isScrolledDown || hasNew) && (
<LoadLatestBtn
onPress={onPressLoadLatest}
+5 -21
View File
@@ -1,4 +1,4 @@
import React, {MutableRefObject} from 'react'
import React from 'react'
import {
Dimensions,
RefreshControl,
@@ -8,18 +8,16 @@ import {
ViewStyle,
} from 'react-native'
import {useQueryClient} from '@tanstack/react-query'
import {FlatList} from '../util/Views'
import {List, ListRef} from '../util/List'
import {FeedSourceCardLoaded} from './FeedSourceCard'
import {ErrorMessage} from '../util/error/ErrorMessage'
import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn'
import {Text} from '../util/text/Text'
import {usePalette} from 'lib/hooks/usePalette'
import {useProfileFeedgensQuery, RQKEY} from '#/state/queries/profile-feedgens'
import {OnScrollHandler} from '#/lib/hooks/useOnMainScroll'
import {logger} from '#/logger'
import {Trans} from '@lingui/macro'
import {cleanError} from '#/lib/strings/errors'
import {useAnimatedScrollHandler} from '#/lib/hooks/useAnimatedScrollHandler_FIXED'
import {useTheme} from '#/lib/ThemeContext'
import {usePreferencesQuery} from '#/state/queries/preferences'
import {hydrateFeedGenerator} from '#/state/queries/feed'
@@ -37,9 +35,7 @@ interface SectionRef {
interface ProfileFeedgensProps {
did: string
scrollElRef: MutableRefObject<FlatList<any> | null>
onScroll?: OnScrollHandler
scrollEventThrottle?: number
scrollElRef: ListRef
headerOffset: number
enabled?: boolean
style?: StyleProp<ViewStyle>
@@ -50,16 +46,7 @@ export const ProfileFeedgens = React.forwardRef<
SectionRef,
ProfileFeedgensProps
>(function ProfileFeedgensImpl(
{
did,
scrollElRef,
onScroll,
scrollEventThrottle,
headerOffset,
enabled,
style,
testID,
},
{did, scrollElRef, headerOffset, enabled, style, testID},
ref,
) {
const pal = usePalette('default')
@@ -185,10 +172,9 @@ export const ProfileFeedgens = React.forwardRef<
[error, refetch, onPressRetryLoadMore, pal, preferences],
)
const scrollHandler = useAnimatedScrollHandler(onScroll || {})
return (
<View testID={testID} style={style}>
<FlatList
<List
testID={testID ? `${testID}-flatlist` : undefined}
ref={scrollElRef}
data={items}
@@ -207,8 +193,6 @@ export const ProfileFeedgens = React.forwardRef<
minHeight: Dimensions.get('window').height * 1.5,
}}
style={{paddingTop: headerOffset}}
onScroll={onScroll != null ? scrollHandler : undefined}
scrollEventThrottle={scrollEventThrottle}
indicatorStyle={theme.colorScheme === 'dark' ? 'white' : 'black'}
removeClippedSubviews={true}
contentOffset={{x: 0, y: headerOffset * -1}}
+7 -13
View File
@@ -1,4 +1,4 @@
import React, {MutableRefObject} from 'react'
import React from 'react'
import {
ActivityIndicator,
Dimensions,
@@ -8,7 +8,7 @@ import {
ViewStyle,
} from 'react-native'
import {AppBskyActorDefs, AppBskyGraphDefs} from '@atproto/api'
import {FlatList} from '../util/Views'
import {List, ListRef} from '../util/List'
import {ProfileCardFeedLoadingPlaceholder} from '../util/LoadingPlaceholder'
import {ErrorMessage} from '../util/error/ErrorMessage'
import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn'
@@ -18,10 +18,8 @@ import {useAnalytics} from 'lib/analytics/analytics'
import {usePalette} from 'lib/hooks/usePalette'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {useListMembersQuery} from '#/state/queries/list-members'
import {OnScrollHandler} from 'lib/hooks/useOnMainScroll'
import {logger} from '#/logger'
import {useModalControls} from '#/state/modals'
import {useAnimatedScrollHandler} from '#/lib/hooks/useAnimatedScrollHandler_FIXED'
import {useSession} from '#/state/session'
import {cleanError} from '#/lib/strings/errors'
@@ -34,24 +32,22 @@ export function ListMembers({
list,
style,
scrollElRef,
onScroll,
onScrolledDownChange,
onPressTryAgain,
renderHeader,
renderEmptyState,
testID,
scrollEventThrottle,
headerOffset = 0,
desktopFixedHeightOffset,
}: {
list: string
style?: StyleProp<ViewStyle>
scrollElRef?: MutableRefObject<FlatList<any> | null>
onScroll: OnScrollHandler
scrollElRef?: ListRef
onScrolledDownChange: (isScrolledDown: boolean) => void
onPressTryAgain?: () => void
renderHeader: () => JSX.Element
renderEmptyState: () => JSX.Element
testID?: string
scrollEventThrottle?: number
headerOffset?: number
desktopFixedHeightOffset?: number
}) {
@@ -209,10 +205,9 @@ export function ListMembers({
[isFetching],
)
const scrollHandler = useAnimatedScrollHandler(onScroll)
return (
<View testID={testID} style={style}>
<FlatList
<List
testID={testID ? `${testID}-flatlist` : undefined}
ref={scrollElRef}
data={items}
@@ -233,10 +228,9 @@ export function ListMembers({
minHeight: Dimensions.get('window').height * 1.5,
}}
style={{paddingTop: headerOffset}}
onScroll={scrollHandler}
onScrolledDownChange={onScrolledDownChange}
onEndReached={onEndReached}
onEndReachedThreshold={0.6}
scrollEventThrottle={scrollEventThrottle}
removeClippedSubviews={true}
contentOffset={{x: 0, y: headerOffset * -1}}
// @ts-ignore our .web version only -prf
+2 -2
View File
@@ -15,7 +15,7 @@ import {ErrorMessage} from '../util/error/ErrorMessage'
import {Text} from '../util/text/Text'
import {useAnalytics} from 'lib/analytics/analytics'
import {usePalette} from 'lib/hooks/usePalette'
import {FlatList} from '../util/Views'
import {List} from '../util/List'
import {s} from 'lib/styles'
import {logger} from '#/logger'
import {Trans} from '@lingui/macro'
@@ -119,7 +119,7 @@ export function MyLists({
[error, onRefresh, renderItem, pal],
)
const FlatListCom = inline ? RNFlatList : FlatList
const FlatListCom = inline ? RNFlatList : List
return (
<View testID={testID} style={style}>
{items.length > 0 && (
+5 -21
View File
@@ -1,4 +1,4 @@
import React, {MutableRefObject} from 'react'
import React from 'react'
import {
Dimensions,
RefreshControl,
@@ -8,7 +8,7 @@ import {
ViewStyle,
} from 'react-native'
import {useQueryClient} from '@tanstack/react-query'
import {FlatList} from '../util/Views'
import {List, ListRef} from '../util/List'
import {ListCard} from './ListCard'
import {ErrorMessage} from '../util/error/ErrorMessage'
import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn'
@@ -16,11 +16,9 @@ import {Text} from '../util/text/Text'
import {useAnalytics} from 'lib/analytics/analytics'
import {usePalette} from 'lib/hooks/usePalette'
import {useProfileListsQuery, RQKEY} from '#/state/queries/profile-lists'
import {OnScrollHandler} from '#/lib/hooks/useOnMainScroll'
import {logger} from '#/logger'
import {Trans} from '@lingui/macro'
import {cleanError} from '#/lib/strings/errors'
import {useAnimatedScrollHandler} from '#/lib/hooks/useAnimatedScrollHandler_FIXED'
import {useTheme} from '#/lib/ThemeContext'
import {FeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
import {isNative} from '#/platform/detection'
@@ -36,9 +34,7 @@ interface SectionRef {
interface ProfileListsProps {
did: string
scrollElRef: MutableRefObject<FlatList<any> | null>
onScroll?: OnScrollHandler
scrollEventThrottle?: number
scrollElRef: ListRef
headerOffset: number
enabled?: boolean
style?: StyleProp<ViewStyle>
@@ -47,16 +43,7 @@ interface ProfileListsProps {
export const ProfileLists = React.forwardRef<SectionRef, ProfileListsProps>(
function ProfileListsImpl(
{
did,
scrollElRef,
onScroll,
scrollEventThrottle,
headerOffset,
enabled,
style,
testID,
},
{did, scrollElRef, headerOffset, enabled, style, testID},
ref,
) {
const pal = usePalette('default')
@@ -187,10 +174,9 @@ export const ProfileLists = React.forwardRef<SectionRef, ProfileListsProps>(
[error, refetch, onPressRetryLoadMore, pal],
)
const scrollHandler = useAnimatedScrollHandler(onScroll || {})
return (
<View testID={testID} style={style}>
<FlatList
<List
testID={testID ? `${testID}-flatlist` : undefined}
ref={scrollElRef}
data={items}
@@ -209,8 +195,6 @@ export const ProfileLists = React.forwardRef<SectionRef, ProfileListsProps>(
minHeight: Dimensions.get('window').height * 1.5,
}}
style={{paddingTop: headerOffset}}
onScroll={onScroll != null ? scrollHandler : undefined}
scrollEventThrottle={scrollEventThrottle}
indicatorStyle={theme.colorScheme === 'dark' ? 'white' : 'black'}
removeClippedSubviews={true}
contentOffset={{x: 0, y: headerOffset * -1}}
+8 -11
View File
@@ -1,13 +1,11 @@
import React, {MutableRefObject} from 'react'
import {CenteredView, FlatList} from '../util/Views'
import React from 'react'
import {CenteredView} from '../util/Views'
import {ActivityIndicator, RefreshControl, StyleSheet, View} from 'react-native'
import {FeedItem} from './FeedItem'
import {NotificationFeedLoadingPlaceholder} from '../util/LoadingPlaceholder'
import {ErrorMessage} from '../util/error/ErrorMessage'
import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn'
import {EmptyState} from '../util/EmptyState'
import {OnScrollHandler} from 'lib/hooks/useOnMainScroll'
import {useAnimatedScrollHandler} from '#/lib/hooks/useAnimatedScrollHandler_FIXED'
import {s} from 'lib/styles'
import {usePalette} from 'lib/hooks/usePalette'
import {useNotificationFeedQuery} from '#/state/queries/notifications/feed'
@@ -15,6 +13,7 @@ import {useUnreadNotificationsApi} from '#/state/queries/notifications/unread'
import {logger} from '#/logger'
import {cleanError} from '#/lib/strings/errors'
import {useModerationOpts} from '#/state/queries/preferences'
import {List, ListRef} from '../util/List'
const EMPTY_FEED_ITEM = {_reactKey: '__empty__'}
const LOAD_MORE_ERROR_ITEM = {_reactKey: '__load_more_error__'}
@@ -23,12 +22,12 @@ const LOADING_ITEM = {_reactKey: '__loading__'}
export function Feed({
scrollElRef,
onPressTryAgain,
onScroll,
onScrolledDownChange,
ListHeaderComponent,
}: {
scrollElRef?: MutableRefObject<FlatList<any> | null>
scrollElRef?: ListRef
onPressTryAgain?: () => void
onScroll?: OnScrollHandler
onScrolledDownChange: (isScrolledDown: boolean) => void
ListHeaderComponent?: () => JSX.Element
}) {
const pal = usePalette('default')
@@ -135,7 +134,6 @@ export function Feed({
[isFetchingNextPage],
)
const scrollHandler = useAnimatedScrollHandler(onScroll || {})
return (
<View style={s.hContentRegion}>
{error && (
@@ -146,7 +144,7 @@ export function Feed({
/>
</CenteredView>
)}
<FlatList
<List
testID="notifsFeed"
ref={scrollElRef}
data={items}
@@ -164,8 +162,7 @@ export function Feed({
}
onEndReached={onEndReached}
onEndReachedThreshold={0.6}
onScroll={scrollHandler}
scrollEventThrottle={1}
onScrolledDownChange={onScrolledDownChange}
contentContainerStyle={s.contentContainer}
// @ts-ignore our .web version only -prf
desktopFixedHeight
+5 -8
View File
@@ -3,12 +3,9 @@ import {StyleSheet, TouchableOpacity, View} from 'react-native'
import {TabBar} from 'view/com/pager/TabBar'
import {RenderTabBarFnProps} from 'view/com/pager/Pager'
import {usePalette} from 'lib/hooks/usePalette'
import {useColorSchemeStyle} from 'lib/hooks/useColorSchemeStyle'
import {Link} from '../util/Link'
import {Text} from '../util/text/Text'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {FontAwesomeIconStyle} from '@fortawesome/react-native-fontawesome'
import {s} from 'lib/styles'
import {HITSLOP_10} from 'lib/constants'
import Animated from 'react-native-reanimated'
import {msg} from '@lingui/macro'
@@ -21,17 +18,17 @@ import {usePinnedFeedsInfos} from '#/state/queries/feed'
import {isWeb} from 'platform/detection'
import {useNavigation} from '@react-navigation/native'
import {NavigationProp} from 'lib/routes/types'
import {Logo} from '#/view/icons/Logo'
export function FeedsTabBar(
props: RenderTabBarFnProps & {testID?: string; onPressSelected: () => void},
) {
const pal = usePalette('default')
const {isSandbox, hasSession} = useSession()
const {hasSession} = useSession()
const {_} = useLingui()
const setDrawerOpen = useSetDrawerOpen()
const navigation = useNavigation<NavigationProp>()
const {feeds, hasPinnedCustom} = usePinnedFeedsInfos()
const brandBlue = useColorSchemeStyle(s.brandBlue, s.blue3)
const {headerHeight} = useShellLayout()
const {headerMinimalShellTransform} = useMinimalShellMode()
const pinnedDisplayNames = hasSession ? feeds.map(f => f.displayName) : []
@@ -86,9 +83,9 @@ export function FeedsTabBar(
/>
</TouchableOpacity>
</View>
<Text style={[brandBlue, s.bold, styles.title]}>
{isSandbox ? 'SANDBOX' : 'Bluesky'}
</Text>
<View>
<Logo width={30} />
</View>
<View style={[pal.view, {width: 18}]}>
{hasSession && (
<Link
+14 -33
View File
@@ -1,7 +1,6 @@
import * as React from 'react'
import {
LayoutChangeEvent,
FlatList,
ScrollView,
StyleSheet,
View,
@@ -20,17 +19,14 @@ import Animated, {
import {Pager, PagerRef, RenderTabBarFnProps} from 'view/com/pager/Pager'
import {TabBar} from './TabBar'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {OnScrollHandler} from 'lib/hooks/useOnMainScroll'
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
const SCROLLED_DOWN_LIMIT = 200
import {ListMethods} from '../util/List'
import {ScrollProvider} from '#/lib/ScrollContext'
export interface PagerWithHeaderChildParams {
headerHeight: number
isFocused: boolean
onScroll: OnScrollHandler
isScrolledDown: boolean
scrollElRef: React.MutableRefObject<FlatList<any> | ScrollView | null>
scrollElRef: React.MutableRefObject<ListMethods | ScrollView | null>
}
export interface PagerWithHeaderProps {
@@ -62,7 +58,6 @@ export const PagerWithHeader = React.forwardRef<PagerRef, PagerWithHeaderProps>(
const [currentPage, setCurrentPage] = React.useState(0)
const [tabBarHeight, setTabBarHeight] = React.useState(0)
const [headerOnlyHeight, setHeaderOnlyHeight] = React.useState(0)
const [isScrolledDown, setIsScrolledDown] = React.useState(false)
const scrollY = useSharedValue(0)
const headerHeight = headerOnlyHeight + tabBarHeight
@@ -155,15 +150,7 @@ export const PagerWithHeader = React.forwardRef<PagerRef, PagerWithHeaderProps>(
if (!throttleTimeout.current) {
throttleTimeout.current = setTimeout(() => {
throttleTimeout.current = null
runOnUI(adjustScrollForOtherPages)()
const nextIsScrolledDown = scrollY.value > SCROLLED_DOWN_LIMIT
if (isScrolledDown !== nextIsScrolledDown) {
React.startTransition(() => {
setIsScrolledDown(nextIsScrolledDown)
})
}
}, 80 /* Sync often enough you're unlikely to catch it unsynced */)
}
})
@@ -211,7 +198,6 @@ export const PagerWithHeader = React.forwardRef<PagerRef, PagerWithHeaderProps>(
index={i}
isReady={isReady}
isFocused={i === currentPage}
isScrolledDown={isScrolledDown}
onScrollWorklet={i === currentPage ? onScrollWorklet : noop}
registerRef={registerRef}
renderTab={child}
@@ -293,7 +279,6 @@ function PagerItem({
index,
isReady,
isFocused,
isScrolledDown,
onScrollWorklet,
renderTab,
registerRef,
@@ -302,7 +287,6 @@ function PagerItem({
index: number
isFocused: boolean
isReady: boolean
isScrolledDown: boolean
registerRef: (scrollRef: AnimatedRef<any> | null, atIndex: number) => void
onScrollWorklet: (e: NativeScrollEvent) => void
renderTab: ((props: PagerWithHeaderChildParams) => JSX.Element) | null
@@ -316,24 +300,21 @@ function PagerItem({
}
}, [scrollElRef, registerRef, index])
const scrollHandler = React.useMemo(
() => ({onScroll: onScrollWorklet}),
[onScrollWorklet],
)
if (!isReady || renderTab == null) {
return null
}
return renderTab({
headerHeight,
isFocused,
isScrolledDown,
onScroll: scrollHandler,
scrollElRef: scrollElRef as React.MutableRefObject<
FlatList<any> | ScrollView | null
>,
})
return (
<ScrollProvider onScroll={onScrollWorklet}>
{renderTab({
headerHeight,
isFocused,
scrollElRef: scrollElRef as React.MutableRefObject<
ListMethods | ScrollView | null
>,
})}
</ScrollProvider>
)
}
const styles = StyleSheet.create({
+3 -2
View File
@@ -1,7 +1,8 @@
import React, {useCallback, useMemo, useState} from 'react'
import {ActivityIndicator, RefreshControl, StyleSheet, View} from 'react-native'
import {AppBskyFeedGetLikes as GetLikes} from '@atproto/api'
import {CenteredView, FlatList} from '../util/Views'
import {CenteredView} from '../util/Views'
import {List} from '../util/List'
import {ErrorMessage} from '../util/error/ErrorMessage'
import {ProfileCardWithFollowBtn} from '../profile/ProfileCard'
import {usePalette} from 'lib/hooks/usePalette'
@@ -84,7 +85,7 @@ export function PostLikedBy({uri}: {uri: string}) {
// loaded
// =
return (
<FlatList
<List
data={likes}
keyExtractor={item => item.actor.did}
refreshControl={
+3 -2
View File
@@ -1,7 +1,8 @@
import React, {useMemo, useCallback, useState} from 'react'
import {ActivityIndicator, RefreshControl, StyleSheet, View} from 'react-native'
import {AppBskyActorDefs as ActorDefs} from '@atproto/api'
import {CenteredView, FlatList} from '../util/Views'
import {CenteredView} from '../util/Views'
import {List} from '../util/List'
import {ProfileCardWithFollowBtn} from '../profile/ProfileCard'
import {ErrorMessage} from '../util/error/ErrorMessage'
import {usePalette} from 'lib/hooks/usePalette'
@@ -85,7 +86,7 @@ export function PostRepostedBy({uri}: {uri: string}) {
// loaded
// =
return (
<FlatList
<List
data={repostedBy}
keyExtractor={item => item.did}
refreshControl={
+18 -7
View File
@@ -8,7 +8,8 @@ import {
View,
} from 'react-native'
import {AppBskyFeedDefs} from '@atproto/api'
import {CenteredView, FlatList} from '../util/Views'
import {CenteredView} from '../util/Views'
import {List, ListMethods} from '../util/List'
import {
FontAwesomeIcon,
FontAwesomeIconStyle,
@@ -140,7 +141,7 @@ function PostThreadLoaded({
const {_} = useLingui()
const pal = usePalette('default')
const {isTablet, isDesktop} = useWebMediaQueries()
const ref = useRef<FlatList>(null)
const ref = useRef<ListMethods>(null)
const highlightedPostRef = useRef<View | null>(null)
const needsScrollAdjustment = useRef<boolean>(
!isNative || // web always uses scroll adjustment
@@ -156,7 +157,9 @@ function PostThreadLoaded({
// construct content
const posts = React.useMemo(() => {
let arr = [TOP_COMPONENT].concat(
Array.from(flattenThreadSkeleton(sortThread(thread, threadViewPrefs))),
Array.from(
flattenThreadSkeleton(sortThread(thread, threadViewPrefs), hasSession),
),
)
if (arr.length > maxVisible) {
arr = arr.slice(0, maxVisible).concat([LOAD_MORE])
@@ -165,7 +168,7 @@ function PostThreadLoaded({
arr.push(BOTTOM_COMPONENT)
}
return arr
}, [thread, maxVisible, threadViewPrefs])
}, [thread, maxVisible, threadViewPrefs, hasSession])
/**
* NOTE
@@ -335,7 +338,7 @@ function PostThreadLoaded({
)
return (
<FlatList
<List
ref={ref}
data={posts}
initialNumToRender={!isNative ? posts.length : undefined}
@@ -467,20 +470,24 @@ function isThreadPost(v: unknown): v is ThreadPost {
function* flattenThreadSkeleton(
node: ThreadNode,
hasSession: boolean,
): Generator<YieldedItem, void> {
if (node.type === 'post') {
if (node.parent) {
yield* flattenThreadSkeleton(node.parent)
yield* flattenThreadSkeleton(node.parent, hasSession)
} else if (node.ctx.isParentLoading) {
yield PARENT_SPINNER
}
if (!hasSession && node.ctx.depth > 0 && hasPwiOptOut(node)) {
return
}
yield node
if (node.ctx.isHighlightedPost && !node.post.viewer?.replyDisabled) {
yield REPLY_PROMPT
}
if (node.replies?.length) {
for (const reply of node.replies) {
yield* flattenThreadSkeleton(reply)
yield* flattenThreadSkeleton(reply, hasSession)
}
} else if (node.ctx.isChildLoading) {
yield CHILD_SPINNER
@@ -492,6 +499,10 @@ function* flattenThreadSkeleton(
}
}
function hasPwiOptOut(node: ThreadPost) {
return !!node.post.author.labels?.find(l => l.val === '!no-unauthenticated')
}
function hasBranchingReplies(node: ThreadNode) {
if (node.type !== 'post') {
return false
+4 -1
View File
@@ -187,6 +187,9 @@ let PostThreadItemLoaded = ({
return makeProfileLink(post.author, 'post', urip.rkey, 'reposted-by')
}, [post.uri, post.author])
const repostsTitle = 'Reposts of this post'
const isSelfLabeledPost =
moderation.decisions.post.cause?.type === 'label' &&
moderation.decisions.post.cause.label.src === currentAccount?.did
const translatorUrl = getTranslatorLink(
record?.text || '',
@@ -351,7 +354,7 @@ let PostThreadItemLoaded = ({
includeMute
style={styles.alert}
/>
{post.author.did === currentAccount?.did ? (
{post.author.did === currentAccount?.did && !isSelfLabeledPost ? (
<LabelInfo
details={{uri: post.uri, cid: post.cid}}
labels={post.labels}
+29 -17
View File
@@ -1,4 +1,4 @@
import React, {memo, MutableRefObject} from 'react'
import React, {memo} from 'react'
import {
ActivityIndicator,
AppState,
@@ -10,15 +10,13 @@ import {
ViewStyle,
} from 'react-native'
import {useQueryClient} from '@tanstack/react-query'
import {FlatList} from '../util/Views'
import {List, ListRef} from '../util/List'
import {PostFeedLoadingPlaceholder} from '../util/LoadingPlaceholder'
import {FeedErrorMessage} from './FeedErrorMessage'
import {FeedSlice} from './FeedSlice'
import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn'
import {OnScrollHandler} from 'lib/hooks/useOnMainScroll'
import {useAnalytics} from 'lib/analytics/analytics'
import {usePalette} from 'lib/hooks/usePalette'
import {useAnimatedScrollHandler} from '#/lib/hooks/useAnimatedScrollHandler_FIXED'
import {useTheme} from 'lib/ThemeContext'
import {logger} from '#/logger'
import {
@@ -31,12 +29,16 @@ import {
import {isWeb} from '#/platform/detection'
import {listenPostCreated} from '#/state/events'
import {useSession} from '#/state/session'
import {STALE} from '#/state/queries'
const LOADING_ITEM = {_reactKey: '__loading__'}
const EMPTY_FEED_ITEM = {_reactKey: '__empty__'}
const ERROR_ITEM = {_reactKey: '__error__'}
const LOAD_MORE_ERROR_ITEM = {_reactKey: '__load_more_error__'}
const REFRESH_AFTER = STALE.HOURS.ONE
const CHECK_LATEST_AFTER = STALE.SECONDS.THIRTY
let Feed = ({
feed,
feedParams,
@@ -45,9 +47,8 @@ let Feed = ({
enabled,
pollInterval,
scrollElRef,
onScroll,
onScrolledDownChange,
onHasNew,
scrollEventThrottle,
renderEmptyState,
renderEndOfFeed,
testID,
@@ -62,10 +63,9 @@ let Feed = ({
style?: StyleProp<ViewStyle>
enabled?: boolean
pollInterval?: number
scrollElRef?: MutableRefObject<FlatList<any> | null>
scrollElRef?: ListRef
onHasNew?: (v: boolean) => void
onScroll?: OnScrollHandler
scrollEventThrottle?: number
onScrolledDownChange?: (isScrolledDown: boolean) => void
renderEmptyState: () => JSX.Element
renderEndOfFeed?: () => JSX.Element
testID?: string
@@ -81,6 +81,7 @@ let Feed = ({
const {currentAccount} = useSession()
const [isPTRing, setIsPTRing] = React.useState(false)
const checkForNewRef = React.useRef<(() => void) | null>(null)
const lastFetchRef = React.useRef<number>(Date.now())
const opts = React.useMemo(
() => ({enabled, ignoreFilterFor}),
@@ -98,6 +99,9 @@ let Feed = ({
fetchNextPage,
} = usePostFeedQuery(feed, feedParams, opts)
const isEmpty = !isFetching && !data?.pages[0]?.slices.length
if (data?.pages[0]) {
lastFetchRef.current = data?.pages[0].fetchedAt
}
const checkForNew = React.useCallback(async () => {
if (!data?.pages[0] || isFetching || !onHasNew || !enabled) {
@@ -137,11 +141,21 @@ let Feed = ({
checkForNewRef.current = checkForNew
}, [checkForNew])
React.useEffect(() => {
if (enabled && checkForNewRef.current) {
// check for new on enable (aka on focus)
checkForNewRef.current()
if (enabled) {
const timeSinceFirstLoad = Date.now() - lastFetchRef.current
if (timeSinceFirstLoad > REFRESH_AFTER) {
// do a full refresh
scrollElRef?.current?.scrollToOffset({offset: 0, animated: false})
queryClient.resetQueries({queryKey: RQKEY(feed)})
} else if (
timeSinceFirstLoad > CHECK_LATEST_AFTER &&
checkForNewRef.current
) {
// check for new on enable (aka on focus)
checkForNewRef.current()
}
}
}, [enabled])
}, [enabled, feed, queryClient, scrollElRef])
React.useEffect(() => {
let cleanup1: () => void | undefined, cleanup2: () => void | undefined
const subscription = AppState.addEventListener('change', nextAppState => {
@@ -270,10 +284,9 @@ let Feed = ({
)
}, [isFetchingNextPage, shouldRenderEndOfFeed, renderEndOfFeed, headerOffset])
const scrollHandler = useAnimatedScrollHandler(onScroll || {})
return (
<View testID={testID} style={style}>
<FlatList
<List
testID={testID ? `${testID}-flatlist` : undefined}
ref={scrollElRef}
data={feedItems}
@@ -294,8 +307,7 @@ let Feed = ({
minHeight: Dimensions.get('window').height * 1.5,
}}
style={{paddingTop: headerOffset}}
onScroll={onScroll != null ? scrollHandler : undefined}
scrollEventThrottle={scrollEventThrottle}
onScrolledDownChange={onScrolledDownChange}
indicatorStyle={theme.colorScheme === 'dark' ? 'white' : 'black'}
onEndReached={onEndReached}
onEndReachedThreshold={2} // number of posts left to trigger load more
+3
View File
@@ -50,6 +50,9 @@ export function ProfileCard({
return null
}
const moderation = moderateProfile(profile, moderationOpts)
if (moderation.account.filter) {
return null
}
return (
<Link
+3 -2
View File
@@ -1,7 +1,8 @@
import React from 'react'
import {ActivityIndicator, RefreshControl, StyleSheet, View} from 'react-native'
import {AppBskyActorDefs as ActorDefs} from '@atproto/api'
import {CenteredView, FlatList} from '../util/Views'
import {CenteredView} from '../util/Views'
import {List} from '../util/List'
import {ErrorMessage} from '../util/error/ErrorMessage'
import {ProfileCardWithFollowBtn} from './ProfileCard'
import {usePalette} from 'lib/hooks/usePalette'
@@ -86,7 +87,7 @@ export function ProfileFollowers({name}: {name: string}) {
// loaded
// =
return (
<FlatList
<List
data={followers}
keyExtractor={item => item.did}
refreshControl={
+3 -2
View File
@@ -1,7 +1,8 @@
import React from 'react'
import {ActivityIndicator, RefreshControl, StyleSheet, View} from 'react-native'
import {AppBskyActorDefs as ActorDefs} from '@atproto/api'
import {CenteredView, FlatList} from '../util/Views'
import {CenteredView} from '../util/Views'
import {List} from '../util/List'
import {ErrorMessage} from '../util/error/ErrorMessage'
import {ProfileCardWithFollowBtn} from './ProfileCard'
import {usePalette} from 'lib/hooks/usePalette'
@@ -86,7 +87,7 @@ export function ProfileFollows({name}: {name: string}) {
// loaded
// =
return (
<FlatList
<List
data={follows}
keyExtractor={item => item.did}
refreshControl={
+64
View File
@@ -0,0 +1,64 @@
import React, {memo, startTransition} from 'react'
import {FlatListProps} from 'react-native'
import {FlatList_INTERNAL} from './Views'
import {useScrollHandlers} from '#/lib/ScrollContext'
import {runOnJS, useSharedValue} from 'react-native-reanimated'
import {useAnimatedScrollHandler} from '#/lib/hooks/useAnimatedScrollHandler_FIXED'
export type ListMethods = FlatList_INTERNAL
export type ListProps<ItemT> = Omit<
FlatListProps<ItemT>,
'onScroll' // Use ScrollContext instead.
> & {
onScrolledDownChange?: (isScrolledDown: boolean) => void
}
export type ListRef = React.MutableRefObject<FlatList_INTERNAL | null>
const SCROLLED_DOWN_LIMIT = 200
function ListImpl<ItemT>(
{onScrolledDownChange, ...props}: ListProps<ItemT>,
ref: React.Ref<ListMethods>,
) {
const isScrolledDown = useSharedValue(false)
const contextScrollHandlers = useScrollHandlers()
function handleScrolledDownChange(didScrollDown: boolean) {
startTransition(() => {
onScrolledDownChange?.(didScrollDown)
})
}
const scrollHandler = useAnimatedScrollHandler({
onBeginDrag(e, ctx) {
contextScrollHandlers.onBeginDrag?.(e, ctx)
},
onEndDrag(e, ctx) {
contextScrollHandlers.onEndDrag?.(e, ctx)
},
onScroll(e, ctx) {
contextScrollHandlers.onScroll?.(e, ctx)
const didScrollDown = e.contentOffset.y > SCROLLED_DOWN_LIMIT
if (isScrolledDown.value !== didScrollDown) {
isScrolledDown.value = didScrollDown
if (onScrolledDownChange != null) {
runOnJS(handleScrolledDownChange)(didScrollDown)
}
}
},
})
return (
<FlatList_INTERNAL
{...props}
onScroll={scrollHandler}
scrollEventThrottle={1}
ref={ref}
/>
)
}
export const List = memo(React.forwardRef(ListImpl)) as <ItemT>(
props: ListProps<ItemT> & {ref?: React.Ref<ListMethods>},
) => React.ReactElement
@@ -1,30 +1,18 @@
import {useState, useCallback, useMemo} from 'react'
import {NativeSyntheticEvent, NativeScrollEvent} from 'react-native'
import React, {useCallback} from 'react'
import {ScrollProvider} from '#/lib/ScrollContext'
import {NativeScrollEvent} from 'react-native'
import {useSetMinimalShellMode, useMinimalShellMode} from '#/state/shell'
import {useShellLayout} from '#/state/shell/shell-layout'
import {s} from 'lib/styles'
import {isWeb} from 'platform/detection'
import {
useSharedValue,
interpolate,
runOnJS,
ScrollHandlers,
} from 'react-native-reanimated'
import {useSharedValue, interpolate} from 'react-native-reanimated'
function clamp(num: number, min: number, max: number) {
'worklet'
return Math.min(Math.max(num, min), max)
}
export type OnScrollCb = (
event: NativeSyntheticEvent<NativeScrollEvent>,
) => void
export type OnScrollHandler = ScrollHandlers<any>
export type ResetCb = () => void
export function useOnMainScroll(): [OnScrollHandler, boolean, ResetCb] {
export function MainScrollProvider({children}: {children: React.ReactNode}) {
const {headerHeight} = useShellLayout()
const [isScrolledDown, setIsScrolledDown] = useState(false)
const mode = useMinimalShellMode()
const setMode = useSetMinimalShellMode()
const startDragOffset = useSharedValue<number | null>(null)
@@ -58,13 +46,6 @@ export function useOnMainScroll(): [OnScrollHandler, boolean, ResetCb] {
const onScroll = useCallback(
(e: NativeScrollEvent) => {
'worklet'
// Keep track of whether we want to show "scroll to top".
if (!isScrolledDown && e.contentOffset.y > s.window.height) {
runOnJS(setIsScrolledDown)(true)
} else if (isScrolledDown && e.contentOffset.y < s.window.height) {
runOnJS(setIsScrolledDown)(false)
}
if (startDragOffset.value === null || startMode.value === null) {
if (mode.value !== 0 && e.contentOffset.y < headerHeight.value) {
// If we're close enough to the top, always show the shell.
@@ -102,24 +83,15 @@ export function useOnMainScroll(): [OnScrollHandler, boolean, ResetCb] {
startMode.value = mode.value
}
},
[headerHeight, mode, setMode, isScrolledDown, startDragOffset, startMode],
[headerHeight, mode, setMode, startDragOffset, startMode],
)
const scrollHandler: ScrollHandlers<any> = useMemo(
() => ({
onBeginDrag,
onEndDrag,
onScroll,
}),
[onBeginDrag, onEndDrag, onScroll],
return (
<ScrollProvider
onBeginDrag={onBeginDrag}
onEndDrag={onEndDrag}
onScroll={onScroll}>
{children}
</ScrollProvider>
)
return [
scrollHandler,
isScrolledDown,
useCallback(() => {
setIsScrolledDown(false)
setMode(false)
}, [setMode]),
]
}
+6 -5
View File
@@ -1,13 +1,14 @@
import React, {useEffect, useState} from 'react'
import {
NativeSyntheticEvent,
NativeScrollEvent,
Pressable,
RefreshControl,
StyleSheet,
View,
ScrollView,
} from 'react-native'
import {FlatList} from './Views'
import {OnScrollCb} from 'lib/hooks/useOnMainScroll'
import {FlatList_INTERNAL} from './Views'
import {useColorSchemeStyle} from 'lib/hooks/useColorSchemeStyle'
import {Text} from './text/Text'
import {usePalette} from 'lib/hooks/usePalette'
@@ -38,7 +39,7 @@ export const ViewSelector = React.forwardRef<
| null
| undefined
onSelectView?: (viewIndex: number) => void
onScroll?: OnScrollCb
onScroll?: (event: NativeSyntheticEvent<NativeScrollEvent>) => void
onRefresh?: () => void
onEndReached?: (info: {distanceFromEnd: number}) => void
}
@@ -59,7 +60,7 @@ export const ViewSelector = React.forwardRef<
) {
const pal = usePalette('default')
const [selectedIndex, setSelectedIndex] = useState<number>(0)
const flatListRef = React.useRef<FlatList>(null)
const flatListRef = React.useRef<FlatList_INTERNAL>(null)
// events
// =
@@ -110,7 +111,7 @@ export const ViewSelector = React.forwardRef<
[items],
)
return (
<FlatList
<FlatList_INTERNAL
ref={flatListRef}
data={data}
keyExtractor={keyExtractor}
+1 -1
View File
@@ -1,6 +1,6 @@
import React from 'react'
import {ViewProps} from 'react-native'
export {FlatList, ScrollView} from 'react-native'
export {FlatList as FlatList_INTERNAL, ScrollView} from 'react-native'
export function CenteredView({
style,
sideBorders,
+1 -1
View File
@@ -2,7 +2,7 @@ import React from 'react'
import {View} from 'react-native'
import Animated from 'react-native-reanimated'
export const FlatList = Animated.FlatList
export const FlatList_INTERNAL = Animated.FlatList
export const ScrollView = Animated.ScrollView
export function CenteredView(props) {
return <View {...props} />
+1 -1
View File
@@ -49,7 +49,7 @@ export function CenteredView({
return <View style={style} {...props} />
}
export const FlatList = React.forwardRef(function FlatListImpl<ItemT>(
export const FlatList_INTERNAL = React.forwardRef(function FlatListImpl<ItemT>(
{
contentContainerStyle,
style,
+12 -8
View File
@@ -7,7 +7,7 @@ import {Text} from '../text/Text'
import {ShieldExclamation} from 'lib/icons'
import {describeModerationCause} from 'lib/moderation'
import {useLingui} from '@lingui/react'
import {msg} from '@lingui/macro'
import {msg, Trans} from '@lingui/macro'
import {useModalControls} from '#/state/modals'
import {isPostMediaBlurred} from 'lib/moderation'
@@ -95,13 +95,17 @@ export function ContentHider({
<Text type="md" style={pal.text}>
{desc.name}
</Text>
{!moderation.noOverride && (
<View style={styles.showBtn}>
<Text type="lg" style={pal.link}>
{override ? 'Hide' : 'Show'}
</Text>
</View>
)}
<View style={styles.showBtn}>
<Text type="lg" style={pal.link}>
{moderation.noOverride ? (
<Trans>Learn more</Trans>
) : override ? (
<Trans>Hide</Trans>
) : (
<Trans>Show</Trans>
)}
</Text>
</View>
</Pressable>
{override && <View style={childContainerStyle}>{children}</View>}
</View>
+2 -1
View File
@@ -43,7 +43,8 @@ export function LabelInfo({
]}>
<Text type="sm" style={pal.text}>
<Trans>
This {'did' in details ? 'account' : 'post'} has been labeled.
A content warning has been applied to this{' '}
{'did' in details ? 'account' : 'post'}.
</Trans>{' '}
</Text>
<Pressable
+41 -23
View File
@@ -22,6 +22,7 @@ import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useModalControls} from '#/state/modals'
import {s} from '#/lib/styles'
import {CenteredView} from '../Views'
export function ScreenHider({
testID,
@@ -53,41 +54,58 @@ export function ScreenHider({
)
}
const isNoPwi =
moderation.cause?.type === 'label' &&
moderation.cause?.labelDef.id === '!no-unauthenticated'
const desc = describeModerationCause(moderation.cause, 'account')
return (
<View style={[styles.container, pal.view, containerStyle]}>
<CenteredView
style={[styles.container, pal.view, containerStyle]}
sideBorders>
<View style={styles.iconContainer}>
<View style={[styles.icon, palInverted.view]}>
<FontAwesomeIcon
icon="exclamation"
icon={isNoPwi ? ['far', 'eye-slash'] : 'exclamation'}
style={pal.textInverted as FontAwesomeIconStyle}
size={24}
/>
</View>
</View>
<Text type="title-2xl" style={[styles.title, pal.text]}>
<Trans>Content Warning</Trans>
{isNoPwi ? (
<Trans>Sign-in Required</Trans>
) : (
<Trans>Content Warning</Trans>
)}
</Text>
<Text type="2xl" style={[styles.description, pal.textLight]}>
<Trans>This {screenDescription} has been flagged:</Trans>
<Text type="2xl-medium" style={[pal.text, s.ml5]}>
{desc.name}.
</Text>
<TouchableWithoutFeedback
onPress={() => {
openModal({
name: 'moderation-details',
context: 'account',
moderation,
})
}}
accessibilityRole="button"
accessibilityLabel={_(msg`Learn more about this warning`)}
accessibilityHint="">
<Text type="2xl" style={pal.link}>
<Trans>Learn More</Trans>
</Text>
</TouchableWithoutFeedback>
{isNoPwi ? (
<Trans>
This account has requested that users sign in to view their profile.
</Trans>
) : (
<>
<Trans>This {screenDescription} has been flagged:</Trans>
<Text type="2xl-medium" style={[pal.text, s.ml5]}>
{desc.name}.
</Text>
<TouchableWithoutFeedback
onPress={() => {
openModal({
name: 'moderation-details',
context: 'account',
moderation,
})
}}
accessibilityRole="button"
accessibilityLabel={_(msg`Learn more about this warning`)}
accessibilityHint="">
<Text type="2xl" style={pal.link}>
<Trans>Learn More</Trans>
</Text>
</TouchableWithoutFeedback>
</>
)}{' '}
</Text>
{isMobile && <View style={styles.spacer} />}
<View style={styles.btnContainer}>
@@ -116,7 +134,7 @@ export function ScreenHider({
</Button>
)}
</View>
</View>
</CenteredView>
)
}
+3 -6
View File
@@ -10,7 +10,7 @@ import {AppBskyFeedDefs, AppBskyFeedPost} from '@atproto/api'
import {Text} from '../text/Text'
import {PostDropdownBtn} from '../forms/PostDropdownBtn'
import {HeartIcon, HeartIconSolid, CommentBottomArrow} from 'lib/icons'
import {s, colors} from 'lib/styles'
import {s} from 'lib/styles'
import {pluralize} from 'lib/strings/helpers'
import {useTheme} from 'lib/ThemeContext'
import {RepostButton} from './RepostButton'
@@ -180,7 +180,7 @@ let PostCtrls = ({
accessibilityHint=""
hitSlop={big ? HITSLOP_20 : HITSLOP_10}>
{post.viewer?.like ? (
<HeartIconSolid style={styles.ctrlIconLiked} size={big ? 22 : 16} />
<HeartIconSolid style={s.likeColor} size={big ? 22 : 16} />
) : (
<HeartIcon
style={[defaultCtrlColor, big ? styles.mt1 : undefined]}
@@ -193,7 +193,7 @@ let PostCtrls = ({
testID="likeCount"
style={
post.viewer?.like
? [s.bold, s.red3, s.f15, s.ml5]
? [s.bold, s.likeColor, s.f15, s.ml5]
: [defaultCtrlColor, s.f15, s.ml5]
}>
{post.likeCount}
@@ -233,9 +233,6 @@ const styles = StyleSheet.create({
paddingLeft: 5,
paddingRight: 5,
},
ctrlIconLiked: {
color: colors.like,
},
mt1: {
marginTop: 1,
},
+48
View File
@@ -0,0 +1,48 @@
import React from 'react'
import Svg, {
Path,
Defs,
LinearGradient,
Stop,
SvgProps,
PathProps,
} from 'react-native-svg'
import {colors} from '#/lib/styles'
const ratio = 57 / 64
type Props = {
fill?: PathProps['fill']
} & SvgProps
export const Logo = React.forwardRef(function LogoImpl(props: Props, ref) {
const {fill, ...rest} = props
const gradient = fill === 'sky'
const _fill = gradient ? 'url(#sky)' : fill || colors.blue3
// @ts-ignore it's fiiiiine
const size = parseInt(rest.width || 32)
return (
<Svg
fill="none"
// @ts-ignore it's fiiiiine
ref={ref}
viewBox="0 0 64 57"
{...rest}
style={{width: size, height: size * ratio}}>
{gradient && (
<Defs>
<LinearGradient id="sky" x1="0" y1="0" x2="0" y2="1">
<Stop offset="0" stopColor="#0A7AFF" stopOpacity="1" />
<Stop offset="1" stopColor="#59B9FF" stopOpacity="1" />
</LinearGradient>
</Defs>
)}
<Path
fill={_fill}
d="M13.873 3.805C21.21 9.332 29.103 20.537 32 26.55v15.882c0-.338-.13.044-.41.867-1.512 4.456-7.418 21.847-20.923 7.944-7.111-7.32-3.819-14.64 9.125-16.85-7.405 1.264-15.73-.825-18.014-9.015C1.12 23.022 0 8.51 0 6.55 0-3.268 8.579-.182 13.873 3.805ZM50.127 3.805C42.79 9.332 34.897 20.537 32 26.55v15.882c0-.338.13.044.41.867 1.512 4.456 7.418 21.847 20.923 7.944 7.111-7.32 3.819-14.64-9.125-16.85 7.405 1.264 15.73-.825 18.014-9.015C62.88 23.022 64 8.51 64 6.55c0-9.818-8.578-6.732-13.873-2.745Z"
/>
</Svg>
)
})
+29
View File
@@ -0,0 +1,29 @@
import React from 'react'
import Svg, {Path, SvgProps, PathProps} from 'react-native-svg'
import {usePalette} from '#/lib/hooks/usePalette'
const ratio = 17 / 64
export function Logotype({
fill,
...rest
}: {fill?: PathProps['fill']} & SvgProps) {
const pal = usePalette('default')
// @ts-ignore it's fiiiiine
const size = parseInt(rest.width || 32)
return (
<Svg
fill="none"
viewBox="0 0 64 17"
{...rest}
width={size}
height={Number(size) * ratio}>
<Path
fill={fill || pal.text.color}
d="M8.478 6.252c1.503.538 2.3 1.78 2.3 3.172 0 2.356-1.576 3.785-4.6 3.785H0V0h5.974c2.875 0 4.267 1.466 4.267 3.413 0 1.3-.594 2.245-1.763 2.839Zm-2.69-4.193H2.504v3.45h3.284c1.28 0 1.967-.667 1.967-1.78 0-1.02-.705-1.67-1.967-1.67Zm-3.284 9.072h3.544c1.41 0 2.17-.65 2.17-1.818 0-1.224-.723-1.837-2.17-1.837H2.504v3.655ZM14.251 13.209h-2.337V0h2.337v13.209ZM22.001 8.998V3.636h2.338v9.573h-2.263v-1.392c-.724 1.076-1.726 1.614-3.006 1.614-2.022 0-3.34-1.224-3.34-3.45V3.636h2.338v5.955c0 1.206.594 1.818 1.8 1.818 1.132 0 2.133-.835 2.133-2.411ZM34.979 8.59v.556h-7.161c.167 1.651 1.076 2.467 2.486 2.467 1.076 0 1.8-.463 2.189-1.372h2.244c-.5 1.947-2.17 3.19-4.452 3.19-1.428 0-2.579-.463-3.45-1.372-.872-.91-1.318-2.115-1.318-3.637 0-1.502.427-2.708 1.299-3.636.872-.909 2.004-1.372 3.432-1.372 1.447 0 2.597.482 3.45 1.428.854.946 1.28 2.208 1.28 3.747Zm-4.75-3.358c-1.28 0-2.17.742-2.393 2.281h4.805c-.204-1.391-1.057-2.281-2.411-2.281ZM40.16 13.469c-2.783 0-4.249-1.095-4.379-3.303h2.282c.13 1.188.724 1.633 2.134 1.633 1.261 0 1.892-.39 1.892-1.15 0-.687-.445-1.02-1.874-1.262l-1.094-.185c-2.097-.353-3.136-1.318-3.136-2.894 0-1.8 1.429-2.894 3.97-2.894 2.728 0 4.138 1.075 4.23 3.246h-2.207c-.056-1.169-.742-1.577-2.023-1.577-1.113 0-1.67.371-1.67 1.113 0 .668.483.965 1.596 1.169l1.206.186c2.32.426 3.32 1.28 3.32 2.912 0 1.93-1.557 3.006-4.247 3.006ZM54.667 13.209h-2.671l-2.783-4.453-1.447 1.447v3.006h-2.3V0h2.3v7.606l3.896-3.97h2.783l-3.618 3.618 3.84 5.955ZM60.772 6.048l.78-2.412H64l-3.692 10.352c-.39 1.057-.872 1.818-1.484 2.245-.612.426-1.484.63-2.634.63-.39 0-.724-.018-1.02-.055V14.97h.89c1.057 0 1.577-.65 1.577-1.54 0-.445-.149-1.094-.446-1.929l-2.746-7.866h2.487l.779 2.393c.575 1.8 1.076 3.58 1.521 5.343.408-1.521.928-3.302 1.54-5.324Z"
/>
</Svg>
)
}
+2 -2
View File
@@ -19,7 +19,7 @@ import {
import {ErrorMessage} from 'view/com/util/error/ErrorMessage'
import debounce from 'lodash.debounce'
import {Text} from 'view/com/util/text/Text'
import {FlatList} from 'view/com/util/Views'
import {List} from 'view/com/util/List'
import {useFocusEffect} from '@react-navigation/native'
import {FeedSourceCard} from 'view/com/feeds/FeedSourceCard'
import {Trans, msg} from '@lingui/macro'
@@ -481,7 +481,7 @@ export function FeedsScreen(_props: Props) {
{preferences ? <View /> : <ActivityIndicator />}
<FlatList
<List
style={[!isTabletOrDesktop && s.flex1, styles.list]}
data={items}
keyExtractor={item => item.key}
+2 -6
View File
@@ -9,6 +9,7 @@ import {CustomFeedEmptyState} from 'view/com/posts/CustomFeedEmptyState'
import {FeedsTabBar} from '../com/pager/FeedsTabBar'
import {Pager, RenderTabBarFnProps} from 'view/com/pager/Pager'
import {FeedPage} from 'view/com/feeds/FeedPage'
import {HomeLoggedOutCTA} from '../com/auth/HomeLoggedOutCTA'
import {useSetMinimalShellMode, useSetDrawerSwipeDisabled} from '#/state/shell'
import {usePreferencesQuery} from '#/state/queries/preferences'
import {UsePreferencesQueryResponse} from '#/state/queries/preferences/types'
@@ -199,12 +200,7 @@ function HomeScreenReady({
onPageScrollStateChanged={onPageScrollStateChanged}
renderTabBar={renderTabBar}
tabBarPosition="top">
<FeedPage
testID="customFeedPage"
isPageFocused={true}
feed={`feedgen|at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/whats-hot`}
renderEmptyState={renderCustomFeedEmptyState}
/>
<HomeLoggedOutCTA />
</Pager>
)
}
+14 -11
View File
@@ -1,5 +1,5 @@
import React from 'react'
import {FlatList, View} from 'react-native'
import {View} from 'react-native'
import {useFocusEffect} from '@react-navigation/native'
import {useQueryClient} from '@tanstack/react-query'
import {
@@ -9,8 +9,9 @@ import {
import {ViewHeader} from '../com/util/ViewHeader'
import {Feed} from '../com/notifications/Feed'
import {TextLink} from 'view/com/util/Link'
import {ListMethods} from 'view/com/util/List'
import {LoadLatestBtn} from 'view/com/util/load-latest/LoadLatestBtn'
import {useOnMainScroll} from 'lib/hooks/useOnMainScroll'
import {MainScrollProvider} from '../com/util/MainScrollProvider'
import {usePalette} from 'lib/hooks/usePalette'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {s, colors} from 'lib/styles'
@@ -35,8 +36,8 @@ type Props = NativeStackScreenProps<
export function NotificationsScreen({}: Props) {
const {_} = useLingui()
const setMinimalShellMode = useSetMinimalShellMode()
const [onMainScroll, isScrolledDown, resetMainScroll] = useOnMainScroll()
const scrollElRef = React.useRef<FlatList>(null)
const [isScrolledDown, setIsScrolledDown] = React.useState(false)
const scrollElRef = React.useRef<ListMethods>(null)
const checkLatestRef = React.useRef<() => void | null>()
const {screen} = useAnalytics()
const pal = usePalette('default')
@@ -50,8 +51,8 @@ export function NotificationsScreen({}: Props) {
// =
const scrollToTop = React.useCallback(() => {
scrollElRef.current?.scrollToOffset({animated: isNative, offset: 0})
resetMainScroll()
}, [scrollElRef, resetMainScroll])
setMinimalShellMode(false)
}, [scrollElRef, setMinimalShellMode])
const onPressLoadLatest = React.useCallback(() => {
scrollToTop()
@@ -130,11 +131,13 @@ export function NotificationsScreen({}: Props) {
return (
<View testID="notificationsScreen" style={s.hContentRegion}>
<ViewHeader title={_(msg`Notifications`)} canGoBack={false} />
<Feed
onScroll={onMainScroll}
scrollElRef={scrollElRef}
ListHeaderComponent={ListHeaderComponent}
/>
<MainScrollProvider>
<Feed
onScrolledDownChange={setIsScrolledDown}
scrollElRef={scrollElRef}
ListHeaderComponent={ListHeaderComponent}
/>
</MainScrollProvider>
{(isScrolledDown || hasNew) && (
<LoadLatestBtn
onPress={onPressLoadLatest}
+3 -1
View File
@@ -24,11 +24,13 @@ import {useResolveUriQuery} from '#/state/queries/resolve-uri'
import {ErrorMessage} from '../com/util/error/ErrorMessage'
import {CenteredView} from '../com/util/Views'
import {useComposerControls} from '#/state/shell/composer'
import {useSession} from '#/state/session'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'PostThread'>
export function PostThreadScreen({route}: Props) {
const queryClient = useQueryClient()
const {_} = useLingui()
const {hasSession} = useSession()
const {fabMinimalShellTransform} = useMinimalShellMode()
const setMinimalShellMode = useSetMinimalShellMode()
const {openComposer} = useComposerControls()
@@ -89,7 +91,7 @@ export function PostThreadScreen({route}: Props) {
/>
)}
</View>
{isMobile && canReply && (
{isMobile && canReply && hasSession && (
<Animated.View
style={[
styles.prompt,
+19 -65
View File
@@ -5,7 +5,8 @@ import {AppBskyActorDefs, moderateProfile, ModerationOpts} from '@atproto/api'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types'
import {CenteredView, FlatList} from '../com/util/Views'
import {CenteredView} from '../com/util/Views'
import {ListRef} from '../com/util/List'
import {ScreenHider} from 'view/com/util/moderation/ScreenHider'
import {Feed} from 'view/com/posts/Feed'
import {ProfileLists} from '../com/lists/ProfileLists'
@@ -20,7 +21,6 @@ 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 {OnScrollHandler} from '#/lib/hooks/useOnMainScroll'
import {FeedDescriptor} from '#/state/queries/post-feed'
import {useResolveDidQuery} from '#/state/queries/resolve-uri'
import {useProfileQuery} from '#/state/queries/profile'
@@ -153,7 +153,7 @@ function ProfileScreenLoaded({
const isMe = profile.did === currentAccount?.did
const showRepliesTab = hasSession
const showLikesTab = isMe
const showFeedsTab = isMe || extraInfoQuery.data?.hasFeedgens
const showFeedsTab = hasSession && (isMe || extraInfoQuery.data?.hasFeedgens)
const showListsTab = hasSession && (isMe || extraInfoQuery.data?.hasLists)
const sectionTitles = useMemo<string[]>(() => {
return [
@@ -277,103 +277,67 @@ function ProfileScreenLoaded({
onPageSelected={onPageSelected}
onCurrentPageSelected={onCurrentPageSelected}
renderHeader={renderHeader}>
{({onScroll, headerHeight, isFocused, isScrolledDown, scrollElRef}) => (
{({headerHeight, isFocused, scrollElRef}) => (
<FeedSection
ref={postsSectionRef}
feed={`author|${profile.did}|posts_and_author_threads`}
onScroll={onScroll}
headerHeight={headerHeight}
isFocused={isFocused}
isScrolledDown={isScrolledDown}
scrollElRef={
scrollElRef as React.MutableRefObject<FlatList<any> | null>
}
scrollElRef={scrollElRef as ListRef}
ignoreFilterFor={profile.did}
/>
)}
{showRepliesTab
? ({
onScroll,
headerHeight,
isFocused,
isScrolledDown,
scrollElRef,
}) => (
? ({headerHeight, isFocused, scrollElRef}) => (
<FeedSection
ref={repliesSectionRef}
feed={`author|${profile.did}|posts_with_replies`}
onScroll={onScroll}
headerHeight={headerHeight}
isFocused={isFocused}
isScrolledDown={isScrolledDown}
scrollElRef={
scrollElRef as React.MutableRefObject<FlatList<any> | null>
}
scrollElRef={scrollElRef as ListRef}
ignoreFilterFor={profile.did}
/>
)
: null}
{({onScroll, headerHeight, isFocused, isScrolledDown, scrollElRef}) => (
{({headerHeight, isFocused, scrollElRef}) => (
<FeedSection
ref={mediaSectionRef}
feed={`author|${profile.did}|posts_with_media`}
onScroll={onScroll}
headerHeight={headerHeight}
isFocused={isFocused}
isScrolledDown={isScrolledDown}
scrollElRef={
scrollElRef as React.MutableRefObject<FlatList<any> | null>
}
scrollElRef={scrollElRef as ListRef}
ignoreFilterFor={profile.did}
/>
)}
{showLikesTab
? ({
onScroll,
headerHeight,
isFocused,
isScrolledDown,
scrollElRef,
}) => (
? ({headerHeight, isFocused, scrollElRef}) => (
<FeedSection
ref={likesSectionRef}
feed={`likes|${profile.did}`}
onScroll={onScroll}
headerHeight={headerHeight}
isFocused={isFocused}
isScrolledDown={isScrolledDown}
scrollElRef={
scrollElRef as React.MutableRefObject<FlatList<any> | null>
}
scrollElRef={scrollElRef as ListRef}
ignoreFilterFor={profile.did}
/>
)
: null}
{showFeedsTab
? ({onScroll, headerHeight, isFocused, scrollElRef}) => (
? ({headerHeight, isFocused, scrollElRef}) => (
<ProfileFeedgens
ref={feedsSectionRef}
did={profile.did}
scrollElRef={
scrollElRef as React.MutableRefObject<FlatList<any> | null>
}
onScroll={onScroll}
scrollEventThrottle={1}
scrollElRef={scrollElRef as ListRef}
headerOffset={headerHeight}
enabled={isFocused}
/>
)
: null}
{showListsTab
? ({onScroll, headerHeight, isFocused, scrollElRef}) => (
? ({headerHeight, isFocused, scrollElRef}) => (
<ProfileLists
ref={listsSectionRef}
did={profile.did}
scrollElRef={
scrollElRef as React.MutableRefObject<FlatList<any> | null>
}
onScroll={onScroll}
scrollEventThrottle={1}
scrollElRef={scrollElRef as ListRef}
headerOffset={headerHeight}
enabled={isFocused}
/>
@@ -396,28 +360,19 @@ function ProfileScreenLoaded({
interface FeedSectionProps {
feed: FeedDescriptor
onScroll: OnScrollHandler
headerHeight: number
isFocused: boolean
isScrolledDown: boolean
scrollElRef: React.MutableRefObject<FlatList<any> | null>
scrollElRef: ListRef
ignoreFilterFor?: string
}
const FeedSection = React.forwardRef<SectionRef, FeedSectionProps>(
function FeedSectionImpl(
{
feed,
onScroll,
headerHeight,
isFocused,
isScrolledDown,
scrollElRef,
ignoreFilterFor,
},
{feed, headerHeight, isFocused, scrollElRef, ignoreFilterFor},
ref,
) {
const queryClient = useQueryClient()
const [hasNew, setHasNew] = React.useState(false)
const [isScrolledDown, setIsScrolledDown] = React.useState(false)
const onScrollToTop = React.useCallback(() => {
scrollElRef.current?.scrollToOffset({
@@ -443,8 +398,7 @@ const FeedSection = React.forwardRef<SectionRef, FeedSectionProps>(
feed={feed}
scrollElRef={scrollElRef}
onHasNew={setHasNew}
onScroll={onScroll}
scrollEventThrottle={1}
onScrolledDownChange={setIsScrolledDown}
renderEmptyState={renderPostsEmpty}
headerOffset={headerHeight}
renderEndOfFeed={ProfileEndOfFeed}
+32 -101
View File
@@ -1,25 +1,20 @@
import React, {useMemo, useCallback} from 'react'
import {
Dimensions,
StyleSheet,
View,
ActivityIndicator,
FlatList,
} from 'react-native'
import {Dimensions, StyleSheet, View, ActivityIndicator} from 'react-native'
import {NativeStackScreenProps} from '@react-navigation/native-stack'
import {useNavigation} from '@react-navigation/native'
import {useIsFocused, useNavigation} from '@react-navigation/native'
import {useQueryClient} from '@tanstack/react-query'
import {usePalette} from 'lib/hooks/usePalette'
import {HeartIcon, HeartIconSolid} from 'lib/icons'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {CommonNavigatorParams} from 'lib/routes/types'
import {makeRecordUri} from 'lib/strings/url-helpers'
import {colors, s} from 'lib/styles'
import {s} from 'lib/styles'
import {FeedDescriptor} from '#/state/queries/post-feed'
import {PagerWithHeader} from 'view/com/pager/PagerWithHeader'
import {ProfileSubpageHeader} from 'view/com/profile/ProfileSubpageHeader'
import {Feed} from 'view/com/posts/Feed'
import {TextLink} from 'view/com/util/Link'
import {ListRef} from 'view/com/util/List'
import {Button} from 'view/com/util/forms/Button'
import {Text} from 'view/com/util/text/Text'
import {RichText} from 'view/com/util/text/RichText'
@@ -29,12 +24,13 @@ import {EmptyState} from 'view/com/util/EmptyState'
import * as Toast from 'view/com/util/Toast'
import {useSetTitle} from 'lib/hooks/useSetTitle'
import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed'
import {OnScrollHandler} from 'lib/hooks/useOnMainScroll'
import {shareUrl} from 'lib/sharing'
import {toShareUrl} from 'lib/strings/url-helpers'
import {Haptics} from 'lib/haptics'
import {useAnalytics} from 'lib/analytics/analytics'
import {NativeDropdown, DropdownItem} from 'view/com/util/forms/NativeDropdown'
import {useScrollHandlers} from '#/lib/ScrollContext'
import {useAnimatedScrollHandler} from '#/lib/hooks/useAnimatedScrollHandler_FIXED'
import {makeCustomFeedLink} from 'lib/routes/links'
import {pluralize} from 'lib/strings/helpers'
import {CenteredView, ScrollView} from 'view/com/util/Views'
@@ -46,12 +42,7 @@ import {logger} from '#/logger'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useModalControls} from '#/state/modals'
import {useAnimatedScrollHandler} from '#/lib/hooks/useAnimatedScrollHandler_FIXED'
import {
useFeedSourceInfoQuery,
FeedSourceFeedInfo,
useIsFeedPublicQuery,
} from '#/state/queries/feed'
import {useFeedSourceInfoQuery, FeedSourceFeedInfo} from '#/state/queries/feed'
import {useResolveUriQuery} from '#/state/queries/resolve-uri'
import {
UsePreferencesQueryResponse,
@@ -137,10 +128,8 @@ export function ProfileFeedScreen(props: Props) {
function ProfileFeedScreenIntermediate({feedUri}: {feedUri: string}) {
const {data: preferences} = usePreferencesQuery()
const {data: info} = useFeedSourceInfoQuery({uri: feedUri})
const {isLoading: isPublicStatusLoading, data: isPublicResponse} =
useIsFeedPublicQuery({uri: feedUri})
if (!preferences || !info || isPublicStatusLoading) {
if (!preferences || !info) {
return (
<CenteredView>
<View style={s.p20}>
@@ -154,7 +143,6 @@ function ProfileFeedScreenIntermediate({feedUri}: {feedUri: string}) {
<ProfileFeedScreenInner
preferences={preferences}
feedInfo={info as FeedSourceFeedInfo}
isPublicResponse={isPublicResponse}
/>
)
}
@@ -162,11 +150,9 @@ function ProfileFeedScreenIntermediate({feedUri}: {feedUri: string}) {
export function ProfileFeedScreenInner({
preferences,
feedInfo,
isPublicResponse,
}: {
preferences: UsePreferencesQueryResponse
feedInfo: FeedSourceFeedInfo
isPublicResponse: ReturnType<typeof useIsFeedPublicQuery>['data']
}) {
const {_} = useLingui()
const pal = usePalette('default')
@@ -175,6 +161,7 @@ export function ProfileFeedScreenInner({
const {openComposer} = useComposerControls()
const {track} = useAnalytics()
const feedSectionRef = React.useRef<SectionRef>(null)
const isScreenFocused = useIsFocused()
const {
mutateAsync: saveFeed,
@@ -210,6 +197,9 @@ export function ProfileFeedScreenInner({
useSetTitle(feedInfo?.displayName)
// event handlers
//
const onToggleSaved = React.useCallback(async () => {
try {
Haptics.default()
@@ -403,32 +393,21 @@ export function ProfileFeedScreenInner({
isHeaderReady={true}
renderHeader={renderHeader}
onCurrentPageSelected={onCurrentPageSelected}>
{({onScroll, headerHeight, isScrolledDown, scrollElRef, isFocused}) =>
isPublicResponse?.isPublic ? (
<FeedSection
ref={feedSectionRef}
feed={`feedgen|${feedInfo.uri}`}
onScroll={onScroll}
headerHeight={headerHeight}
isScrolledDown={isScrolledDown}
scrollElRef={
scrollElRef as React.MutableRefObject<FlatList<any> | null>
}
isFocused={isFocused}
/>
) : (
<CenteredView sideBorders style={[{paddingTop: headerHeight}]}>
<NonPublicFeedMessage rawError={isPublicResponse?.error} />
</CenteredView>
)
}
{({onScroll, headerHeight, scrollElRef}) => (
{({headerHeight, scrollElRef, isFocused}) => (
<FeedSection
ref={feedSectionRef}
feed={`feedgen|${feedInfo.uri}`}
headerHeight={headerHeight}
scrollElRef={scrollElRef as ListRef}
isFocused={isScreenFocused && isFocused}
/>
)}
{({headerHeight, scrollElRef}) => (
<AboutSection
feedOwnerDid={feedInfo.creatorDid}
feedRkey={feedInfo.route.params.rkey}
feedInfo={feedInfo}
headerHeight={headerHeight}
onScroll={onScroll}
scrollElRef={
scrollElRef as React.MutableRefObject<ScrollView | null>
}
@@ -456,59 +435,16 @@ export function ProfileFeedScreenInner({
)
}
function NonPublicFeedMessage({rawError}: {rawError?: Error}) {
const pal = usePalette('default')
return (
<View
style={[
pal.border,
{
padding: 18,
borderTopWidth: 1,
minHeight: Dimensions.get('window').height * 1.5,
},
]}>
<View
style={[
pal.viewLight,
{
padding: 12,
borderRadius: 8,
gap: 12,
},
]}>
<Text style={[pal.text]}>
<Trans>
Looks like this feed is only available to users with a Bluesky
account. Please sign up or sign in to view this feed!
</Trans>
</Text>
{rawError?.message && (
<Text style={pal.textLight}>
<Trans>Message from server</Trans>: {rawError.message}
</Text>
)}
</View>
</View>
)
}
interface FeedSectionProps {
feed: FeedDescriptor
onScroll: OnScrollHandler
headerHeight: number
isScrolledDown: boolean
scrollElRef: React.MutableRefObject<FlatList<any> | null>
scrollElRef: ListRef
isFocused: boolean
}
const FeedSection = React.forwardRef<SectionRef, FeedSectionProps>(
function FeedSectionImpl(
{feed, onScroll, headerHeight, isScrolledDown, scrollElRef, isFocused},
ref,
) {
function FeedSectionImpl({feed, headerHeight, scrollElRef, isFocused}, ref) {
const [hasNew, setHasNew] = React.useState(false)
const [isScrolledDown, setIsScrolledDown] = React.useState(false)
const queryClient = useQueryClient()
const onScrollToTop = useCallback(() => {
@@ -533,11 +469,10 @@ const FeedSection = React.forwardRef<SectionRef, FeedSectionProps>(
<Feed
enabled={isFocused}
feed={feed}
pollInterval={30e3}
pollInterval={60e3}
scrollElRef={scrollElRef}
onHasNew={setHasNew}
onScroll={onScroll}
scrollEventThrottle={5}
onScrolledDownChange={setIsScrolledDown}
renderEmptyState={renderPostsEmpty}
headerOffset={headerHeight}
/>
@@ -558,7 +493,6 @@ function AboutSection({
feedRkey,
feedInfo,
headerHeight,
onScroll,
scrollElRef,
isOwner,
}: {
@@ -566,13 +500,13 @@ function AboutSection({
feedRkey: string
feedInfo: FeedSourceFeedInfo
headerHeight: number
onScroll: OnScrollHandler
scrollElRef: React.MutableRefObject<ScrollView | null>
isOwner: boolean
}) {
const pal = usePalette('default')
const {_} = useLingui()
const scrollHandler = useAnimatedScrollHandler(onScroll)
const scrollHandlers = useScrollHandlers()
const onScroll = useAnimatedScrollHandler(scrollHandlers)
const [likeUri, setLikeUri] = React.useState(feedInfo.likeUri)
const {hasSession} = useSession()
const {track} = useAnalytics()
@@ -608,12 +542,12 @@ function AboutSection({
return (
<ScrollView
ref={scrollElRef}
onScroll={onScroll}
scrollEventThrottle={1}
contentContainerStyle={{
paddingTop: headerHeight,
minHeight: Dimensions.get('window').height * 1.5,
}}
onScroll={scrollHandler}>
}}>
<View
style={[
{
@@ -646,7 +580,7 @@ function AboutSection({
onPress={onToggleLiked}
style={{paddingHorizontal: 10}}>
{isLiked ? (
<HeartIconSolid size={19} style={styles.liked} />
<HeartIconSolid size={19} style={s.likeColor} />
) : (
<HeartIcon strokeWidth={3} size={19} style={pal.textLight} />
)}
@@ -689,9 +623,6 @@ const styles = StyleSheet.create({
borderRadius: 50,
marginLeft: 6,
},
liked: {
color: colors.red3,
},
notFoundContainer: {
margin: 10,
paddingHorizontal: 18,
+20 -50
View File
@@ -1,12 +1,6 @@
import React, {useCallback, useMemo} from 'react'
import {
ActivityIndicator,
FlatList,
Pressable,
StyleSheet,
View,
} from 'react-native'
import {useFocusEffect} from '@react-navigation/native'
import {ActivityIndicator, Pressable, StyleSheet, View} from 'react-native'
import {useFocusEffect, useIsFocused} from '@react-navigation/native'
import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types'
import {useNavigation} from '@react-navigation/native'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
@@ -22,6 +16,7 @@ import {EmptyState} from 'view/com/util/EmptyState'
import {RichText} from 'view/com/util/text/RichText'
import {Button} from 'view/com/util/forms/Button'
import {TextLink} from 'view/com/util/Link'
import {ListRef} from 'view/com/util/List'
import * as Toast from 'view/com/util/Toast'
import {LoadLatestBtn} from 'view/com/util/load-latest/LoadLatestBtn'
import {FAB} from 'view/com/util/fab/FAB'
@@ -31,7 +26,6 @@ import {usePalette} from 'lib/hooks/usePalette'
import {useSetTitle} from 'lib/hooks/useSetTitle'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed'
import {OnScrollHandler} from 'lib/hooks/useOnMainScroll'
import {NavigationProp} from 'lib/routes/types'
import {toShareUrl} from 'lib/strings/url-helpers'
import {shareUrl} from 'lib/sharing'
@@ -121,6 +115,7 @@ function ProfileListScreenLoaded({
const aboutSectionRef = React.useRef<SectionRef>(null)
const {openModal} = useModalControls()
const isCurateList = list.purpose === 'app.bsky.graph.defs#curatelist'
const isScreenFocused = useIsFocused()
useSetTitle(list.name)
@@ -165,36 +160,22 @@ function ProfileListScreenLoaded({
isHeaderReady={true}
renderHeader={renderHeader}
onCurrentPageSelected={onCurrentPageSelected}>
{({
onScroll,
headerHeight,
isScrolledDown,
scrollElRef,
isFocused,
}) => (
{({headerHeight, scrollElRef, isFocused}) => (
<FeedSection
ref={feedSectionRef}
feed={`list|${uri}`}
scrollElRef={
scrollElRef as React.MutableRefObject<FlatList<any> | null>
}
onScroll={onScroll}
scrollElRef={scrollElRef as ListRef}
headerHeight={headerHeight}
isScrolledDown={isScrolledDown}
isFocused={isFocused}
isFocused={isScreenFocused && isFocused}
/>
)}
{({onScroll, headerHeight, isScrolledDown, scrollElRef}) => (
{({headerHeight, scrollElRef}) => (
<AboutSection
ref={aboutSectionRef}
scrollElRef={
scrollElRef as React.MutableRefObject<FlatList<any> | null>
}
scrollElRef={scrollElRef as ListRef}
list={list}
onPressAddUser={onPressAddUser}
onScroll={onScroll}
headerHeight={headerHeight}
isScrolledDown={isScrolledDown}
/>
)}
</PagerWithHeader>
@@ -221,16 +202,12 @@ function ProfileListScreenLoaded({
items={SECTION_TITLES_MOD}
isHeaderReady={true}
renderHeader={renderHeader}>
{({onScroll, headerHeight, isScrolledDown, scrollElRef}) => (
{({headerHeight, scrollElRef}) => (
<AboutSection
list={list}
scrollElRef={
scrollElRef as React.MutableRefObject<FlatList<any> | null>
}
scrollElRef={scrollElRef as ListRef}
onPressAddUser={onPressAddUser}
onScroll={onScroll}
headerHeight={headerHeight}
isScrolledDown={isScrolledDown}
/>
)}
</PagerWithHeader>
@@ -615,19 +592,15 @@ function Header({rkey, list}: {rkey: string; list: AppBskyGraphDefs.ListView}) {
interface FeedSectionProps {
feed: FeedDescriptor
onScroll: OnScrollHandler
headerHeight: number
isScrolledDown: boolean
scrollElRef: React.MutableRefObject<FlatList<any> | null>
scrollElRef: ListRef
isFocused: boolean
}
const FeedSection = React.forwardRef<SectionRef, FeedSectionProps>(
function FeedSectionImpl(
{feed, scrollElRef, onScroll, headerHeight, isScrolledDown, isFocused},
ref,
) {
function FeedSectionImpl({feed, scrollElRef, headerHeight, isFocused}, ref) {
const queryClient = useQueryClient()
const [hasNew, setHasNew] = React.useState(false)
const [isScrolledDown, setIsScrolledDown] = React.useState(false)
const onScrollToTop = useCallback(() => {
scrollElRef.current?.scrollToOffset({
@@ -651,11 +624,10 @@ const FeedSection = React.forwardRef<SectionRef, FeedSectionProps>(
testID="listFeed"
enabled={isFocused}
feed={feed}
pollInterval={30e3}
pollInterval={60e3}
scrollElRef={scrollElRef}
onHasNew={setHasNew}
onScroll={onScroll}
scrollEventThrottle={1}
onScrolledDownChange={setIsScrolledDown}
renderEmptyState={renderPostsEmpty}
headerOffset={headerHeight}
/>
@@ -674,20 +646,19 @@ const FeedSection = React.forwardRef<SectionRef, FeedSectionProps>(
interface AboutSectionProps {
list: AppBskyGraphDefs.ListView
onPressAddUser: () => void
onScroll: OnScrollHandler
headerHeight: number
isScrolledDown: boolean
scrollElRef: React.MutableRefObject<FlatList<any> | null>
scrollElRef: ListRef
}
const AboutSection = React.forwardRef<SectionRef, AboutSectionProps>(
function AboutSectionImpl(
{list, onPressAddUser, onScroll, headerHeight, isScrolledDown, scrollElRef},
{list, onPressAddUser, headerHeight, scrollElRef},
ref,
) {
const pal = usePalette('default')
const {_} = useLingui()
const {isMobile} = useWebMediaQueries()
const {currentAccount} = useSession()
const [isScrolledDown, setIsScrolledDown] = React.useState(false)
const isCurateList = list.purpose === 'app.bsky.graph.defs#curatelist'
const isOwner = list.creator.did === currentAccount?.did
@@ -817,8 +788,7 @@ const AboutSection = React.forwardRef<SectionRef, AboutSectionProps>(
renderHeader={renderHeader}
renderEmptyState={renderEmptyState}
headerOffset={headerHeight}
onScroll={onScroll}
scrollEventThrottle={1}
onScrolledDownChange={setIsScrolledDown}
/>
{isScrolledDown && (
<LoadLatestBtn
+71 -37
View File
@@ -8,7 +8,8 @@ import {
Pressable,
Platform,
} from 'react-native'
import {FlatList, ScrollView, CenteredView} from '#/view/com/util/Views'
import {ScrollView, CenteredView} from '#/view/com/util/Views'
import {List} from '#/view/com/util/List'
import {AppBskyActorDefs, AppBskyFeedDefs, moderateProfile} from '@atproto/api'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
@@ -155,7 +156,7 @@ function SearchScreenSuggestedFollows() {
}, [currentAccount, setSuggestions, getSuggestedFollowsByActor])
return suggestions.length ? (
<FlatList
<List
data={suggestions}
renderItem={({item}) => <ProfileCardWithFollowBtn profile={item} noBg />}
keyExtractor={item => item.did}
@@ -243,7 +244,7 @@ function SearchScreenPostResults({query}: {query: string}) {
{isFetched ? (
<>
{posts.length ? (
<FlatList
<List
data={items}
renderItem={({item}) => {
if (item.type === 'post') {
@@ -284,7 +285,7 @@ function SearchScreenUserResults({query}: {query: string}) {
return isFetched && results ? (
<>
{results.length ? (
<FlatList
<List
data={results}
renderItem={({item}) => (
<ProfileCardWithFollowBtn profile={item} noBg />
@@ -303,7 +304,8 @@ function SearchScreenUserResults({query}: {query: string}) {
)
}
const SECTIONS = ['Posts', 'Users']
const SECTIONS_LOGGEDOUT = ['Users']
const SECTIONS_LOGGEDIN = ['Posts', 'Users']
export function SearchScreenInner({query}: {query?: string}) {
const pal = usePalette('default')
const setMinimalShellMode = useSetMinimalShellMode()
@@ -319,44 +321,62 @@ export function SearchScreenInner({query}: {query?: string}) {
[setDrawerSwipeDisabled, setMinimalShellMode],
)
if (hasSession) {
return query ? (
<Pager
tabBarPosition="top"
onPageSelected={onPageSelected}
renderTabBar={props => (
<CenteredView sideBorders style={pal.border}>
<TabBar items={SECTIONS_LOGGEDIN} {...props} />
</CenteredView>
)}
initialPage={0}>
<View>
<SearchScreenPostResults query={query} />
</View>
<View>
<SearchScreenUserResults query={query} />
</View>
</Pager>
) : (
<View>
<CenteredView sideBorders style={pal.border}>
<Text
type="title"
style={[
pal.text,
pal.border,
{
display: 'flex',
paddingVertical: 12,
paddingHorizontal: 18,
fontWeight: 'bold',
},
]}>
<Trans>Suggested Follows</Trans>
</Text>
</CenteredView>
<SearchScreenSuggestedFollows />
</View>
)
}
return query ? (
<Pager
tabBarPosition="top"
onPageSelected={onPageSelected}
renderTabBar={props => (
<CenteredView sideBorders style={pal.border}>
<TabBar items={SECTIONS} {...props} />
<TabBar items={SECTIONS_LOGGEDOUT} {...props} />
</CenteredView>
)}
initialPage={0}>
<View>
<SearchScreenPostResults query={query} />
</View>
<View>
<SearchScreenUserResults query={query} />
</View>
</Pager>
) : hasSession ? (
<View>
<CenteredView sideBorders style={pal.border}>
<Text
type="title"
style={[
pal.text,
pal.border,
{
display: 'flex',
paddingVertical: 12,
paddingHorizontal: 18,
fontWeight: 'bold',
},
]}>
<Trans>Suggested Follows</Trans>
</Text>
</CenteredView>
<SearchScreenSuggestedFollows />
</View>
) : (
<CenteredView sideBorders style={pal.border}>
<View
@@ -382,13 +402,27 @@ export function SearchScreenInner({query}: {query?: string}) {
</Text>
)}
<Text
style={[
pal.textLight,
{textAlign: 'center', paddingVertical: 12, paddingHorizontal: 18},
]}>
<Trans>Search for posts and users.</Trans>
</Text>
<View
style={{
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
paddingVertical: 30,
gap: 15,
}}>
<MagnifyingGlassIcon
strokeWidth={3}
size={isDesktop ? 60 : 60}
style={pal.textLight}
/>
<Text type="xl" style={[pal.textLight, {paddingHorizontal: 18}]}>
{isDesktop ? (
<Trans>Find users with the search tool on the right</Trans>
) : (
<Trans>Find users on Bluesky</Trans>
)}
</Text>
</View>
</View>
</CenteredView>
)
+12 -12
View File
@@ -221,19 +221,17 @@ let DrawerContent = ({}: {}): React.ReactNode => {
<NavSignupCard />
)}
{hasSession && <InviteCodes />}
{hasSession && <View style={{height: 10}} />}
<SearchMenuItem isActive={isAtSearch} onPress={onPressSearch} />
<HomeMenuItem isActive={isAtHome} onPress={onPressHome} />
{hasSession && (
<NotificationsMenuItem
isActive={isAtNotifications}
onPress={onPressNotifications}
/>
)}
<FeedsMenuItem isActive={isAtFeeds} onPress={onPressMyFeeds} />
{hasSession && (
{hasSession ? (
<>
<InviteCodes />
<View style={{height: 10}} />
<SearchMenuItem isActive={isAtSearch} onPress={onPressSearch} />
<HomeMenuItem isActive={isAtHome} onPress={onPressHome} />
<NotificationsMenuItem
isActive={isAtNotifications}
onPress={onPressNotifications}
/>
<FeedsMenuItem isActive={isAtFeeds} onPress={onPressMyFeeds} />
<ListsMenuItem onPress={onPressLists} />
<ModerationMenuItem onPress={onPressModeration} />
<ProfileMenuItem
@@ -242,6 +240,8 @@ let DrawerContent = ({}: {}): React.ReactNode => {
/>
<SettingsMenuItem onPress={onPressSettings} />
</>
) : (
<SearchMenuItem isActive={isAtSearch} onPress={onPressSearch} />
)}
<View style={styles.smallSpacer} />
+16 -10
View File
@@ -5,22 +5,28 @@ import {useLingui} from '@lingui/react'
import {s} from 'lib/styles'
import {usePalette} from 'lib/hooks/usePalette'
import {DefaultAvatar} from '#/view/com/util/UserAvatar'
import {Text} from '#/view/com/util/text/Text'
import {Button} from '#/view/com/util/forms/Button'
import {useLoggedOutViewControls} from '#/state/shell/logged-out'
import {useCloseAllActiveElements} from '#/state/util'
import {Logo} from '#/view/icons/Logo'
let NavSignupCard = ({}: {}): React.ReactNode => {
const {_} = useLingui()
const pal = usePalette('default')
const {setShowLoggedOut} = useLoggedOutViewControls()
const {requestSwitchToAccount} = useLoggedOutViewControls()
const closeAllActiveElements = useCloseAllActiveElements()
const showLoggedOut = React.useCallback(() => {
const showSignIn = React.useCallback(() => {
closeAllActiveElements()
setShowLoggedOut(true)
}, [setShowLoggedOut, closeAllActiveElements])
requestSwitchToAccount({requestedAccount: 'none'})
}, [requestSwitchToAccount, closeAllActiveElements])
const showCreateAccount = React.useCallback(() => {
closeAllActiveElements()
requestSwitchToAccount({requestedAccount: 'new'})
// setShowLoggedOut(true)
}, [requestSwitchToAccount, closeAllActiveElements])
return (
<View
@@ -29,17 +35,17 @@ let NavSignupCard = ({}: {}): React.ReactNode => {
paddingTop: 6,
marginBottom: 24,
}}>
<DefaultAvatar type="user" size={48} />
<Logo width={48} />
<View style={{paddingTop: 12}}>
<Text type="md" style={[pal.text, s.bold]}>
<View style={{paddingTop: 18}}>
<Text type="md-bold" style={[pal.text]}>
<Trans>Sign up or sign in to join the conversation</Trans>
</Text>
</View>
<View style={{flexDirection: 'row', paddingTop: 12, gap: 8}}>
<Button
onPress={showLoggedOut}
onPress={showCreateAccount}
accessibilityHint={_(msg`Sign up`)}
accessibilityLabel={_(msg`Sign up`)}>
<Text type="md" style={[{color: 'white'}, s.bold]}>
@@ -48,7 +54,7 @@ let NavSignupCard = ({}: {}): React.ReactNode => {
</Button>
<Button
type="default"
onPress={showLoggedOut}
onPress={showSignIn}
accessibilityHint={_(msg`Sign in`)}
accessibilityLabel={_(msg`Sign in`)}>
<Text type="md" style={[pal.text, s.bold]}>
+130 -69
View File
@@ -23,13 +23,19 @@ import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode'
import {useNavigationTabState} from 'lib/hooks/useNavigationTabState'
import {UserAvatar} from 'view/com/util/UserAvatar'
import {useLingui} from '@lingui/react'
import {msg} from '@lingui/macro'
import {msg, Trans} from '@lingui/macro'
import {useModalControls} from '#/state/modals'
import {useShellLayout} from '#/state/shell/shell-layout'
import {useUnreadNotifications} from '#/state/queries/notifications/unread'
import {emitSoftReset} from '#/state/events'
import {useSession} from '#/state/session'
import {useProfileQuery} from '#/state/queries/profile'
import {useLoggedOutViewControls} from '#/state/shell/logged-out'
import {useCloseAllActiveElements} from '#/state/util'
import {Button} from '#/view/com/util/forms/Button'
import {s} from 'lib/styles'
import {Logo} from '#/view/icons/Logo'
import {Logotype} from '#/view/icons/Logotype'
type TabOptions = 'Home' | 'Search' | 'Notifications' | 'MyProfile' | 'Feeds'
@@ -46,6 +52,19 @@ export function BottomBar({navigation}: BottomTabBarProps) {
const numUnreadNotifications = useUnreadNotifications()
const {footerMinimalShellTransform} = useMinimalShellMode()
const {data: profile} = useProfileQuery({did: currentAccount?.did})
const {requestSwitchToAccount} = useLoggedOutViewControls()
const closeAllActiveElements = useCloseAllActiveElements()
const showSignIn = React.useCallback(() => {
closeAllActiveElements()
requestSwitchToAccount({requestedAccount: 'none'})
}, [requestSwitchToAccount, closeAllActiveElements])
const showCreateAccount = React.useCallback(() => {
closeAllActiveElements()
requestSwitchToAccount({requestedAccount: 'new'})
// setShowLoggedOut(true)
}, [requestSwitchToAccount, closeAllActiveElements])
const onPressTab = React.useCallback(
(tab: TabOptions) => {
@@ -94,75 +113,74 @@ export function BottomBar({navigation}: BottomTabBarProps) {
onLayout={e => {
footerHeight.value = e.nativeEvent.layout.height
}}>
<Btn
testID="bottomBarHomeBtn"
icon={
isAtHome ? (
<HomeIconSolid
strokeWidth={4}
size={24}
style={[styles.ctrlIcon, pal.text, styles.homeIcon]}
/>
) : (
<HomeIcon
strokeWidth={4}
size={24}
style={[styles.ctrlIcon, pal.text, styles.homeIcon]}
/>
)
}
onPress={onPressHome}
accessibilityRole="tab"
accessibilityLabel={_(msg`Home`)}
accessibilityHint=""
/>
<Btn
testID="bottomBarSearchBtn"
icon={
isAtSearch ? (
<MagnifyingGlassIcon2Solid
size={25}
style={[styles.ctrlIcon, pal.text, styles.searchIcon]}
strokeWidth={1.8}
/>
) : (
<MagnifyingGlassIcon2
size={25}
style={[styles.ctrlIcon, pal.text, styles.searchIcon]}
strokeWidth={1.8}
/>
)
}
onPress={onPressSearch}
accessibilityRole="search"
accessibilityLabel={_(msg`Search`)}
accessibilityHint=""
/>
<Btn
testID="bottomBarFeedsBtn"
icon={
isAtFeeds ? (
<HashtagIcon
size={24}
style={[styles.ctrlIcon, pal.text, styles.feedsIcon]}
strokeWidth={4}
/>
) : (
<HashtagIcon
size={24}
style={[styles.ctrlIcon, pal.text, styles.feedsIcon]}
strokeWidth={2.25}
/>
)
}
onPress={onPressFeeds}
accessibilityRole="tab"
accessibilityLabel={_(msg`Feeds`)}
accessibilityHint=""
/>
{hasSession && (
{hasSession ? (
<>
<Btn
testID="bottomBarHomeBtn"
icon={
isAtHome ? (
<HomeIconSolid
strokeWidth={4}
size={24}
style={[styles.ctrlIcon, pal.text, styles.homeIcon]}
/>
) : (
<HomeIcon
strokeWidth={4}
size={24}
style={[styles.ctrlIcon, pal.text, styles.homeIcon]}
/>
)
}
onPress={onPressHome}
accessibilityRole="tab"
accessibilityLabel={_(msg`Home`)}
accessibilityHint=""
/>
<Btn
testID="bottomBarSearchBtn"
icon={
isAtSearch ? (
<MagnifyingGlassIcon2Solid
size={25}
style={[styles.ctrlIcon, pal.text, styles.searchIcon]}
strokeWidth={1.8}
/>
) : (
<MagnifyingGlassIcon2
size={25}
style={[styles.ctrlIcon, pal.text, styles.searchIcon]}
strokeWidth={1.8}
/>
)
}
onPress={onPressSearch}
accessibilityRole="search"
accessibilityLabel={_(msg`Search`)}
accessibilityHint=""
/>
<Btn
testID="bottomBarFeedsBtn"
icon={
isAtFeeds ? (
<HashtagIcon
size={24}
style={[styles.ctrlIcon, pal.text, styles.feedsIcon]}
strokeWidth={4}
/>
) : (
<HashtagIcon
size={24}
style={[styles.ctrlIcon, pal.text, styles.feedsIcon]}
strokeWidth={2.25}
/>
)
}
onPress={onPressFeeds}
accessibilityRole="tab"
accessibilityLabel={_(msg`Feeds`)}
accessibilityHint=""
/>
<Btn
testID="bottomBarNotificationsBtn"
icon={
@@ -230,6 +248,49 @@ export function BottomBar({navigation}: BottomTabBarProps) {
accessibilityHint=""
/>
</>
) : (
<>
<View
style={{
width: '100%',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingTop: 14,
paddingBottom: 2,
paddingLeft: 14,
paddingRight: 6,
gap: 8,
}}>
<View style={{flexDirection: 'row', alignItems: 'center', gap: 8}}>
<Logo width={28} />
<View style={{paddingTop: 4}}>
<Logotype width={80} fill={pal.text.color} />
</View>
</View>
<View style={{flexDirection: 'row', alignItems: 'center', gap: 4}}>
<Button
onPress={showCreateAccount}
accessibilityHint={_(msg`Sign up`)}
accessibilityLabel={_(msg`Sign up`)}>
<Text type="md" style={[{color: 'white'}, s.bold]}>
<Trans>Sign up</Trans>
</Text>
</Button>
<Button
type="default"
onPress={showSignIn}
accessibilityHint={_(msg`Sign in`)}
accessibilityLabel={_(msg`Sign in`)}>
<Text type="md" style={[pal.text, s.bold]}>
<Trans>Sign in</Trans>
</Text>
</Button>
</View>
</View>
</>
)}
</Animated.View>
)
+128 -57
View File
@@ -3,6 +3,9 @@ import {usePalette} from 'lib/hooks/usePalette'
import {useNavigationState} from '@react-navigation/native'
import Animated from 'react-native-reanimated'
import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {View} from 'react-native'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {getCurrentRoute, isTab} from 'lib/routes/helpers'
import {styles} from './BottomBarStyles'
import {clamp} from 'lib/numbers'
@@ -22,12 +25,33 @@ import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode'
import {makeProfileLink} from 'lib/routes/links'
import {CommonNavigatorParams} from 'lib/routes/types'
import {useSession} from '#/state/session'
import {useLoggedOutViewControls} from '#/state/shell/logged-out'
import {useCloseAllActiveElements} from '#/state/util'
import {Button} from '#/view/com/util/forms/Button'
import {Text} from '#/view/com/util/text/Text'
import {s} from 'lib/styles'
import {Logo} from '#/view/icons/Logo'
import {Logotype} from '#/view/icons/Logotype'
export function BottomBarWeb() {
const {_} = useLingui()
const {hasSession, currentAccount} = useSession()
const pal = usePalette('default')
const safeAreaInsets = useSafeAreaInsets()
const {footerMinimalShellTransform} = useMinimalShellMode()
const {requestSwitchToAccount} = useLoggedOutViewControls()
const closeAllActiveElements = useCloseAllActiveElements()
const showSignIn = React.useCallback(() => {
closeAllActiveElements()
requestSwitchToAccount({requestedAccount: 'none'})
}, [requestSwitchToAccount, closeAllActiveElements])
const showCreateAccount = React.useCallback(() => {
closeAllActiveElements()
requestSwitchToAccount({requestedAccount: 'new'})
// setShowLoggedOut(true)
}, [requestSwitchToAccount, closeAllActiveElements])
return (
<Animated.View
@@ -38,79 +62,126 @@ export function BottomBarWeb() {
{paddingBottom: clamp(safeAreaInsets.bottom, 15, 30)},
footerMinimalShellTransform,
]}>
<NavItem routeName="Home" href="/">
{({isActive}) => {
const Icon = isActive ? HomeIconSolid : HomeIcon
return (
<Icon
strokeWidth={4}
size={24}
style={[styles.ctrlIcon, pal.text, styles.homeIcon]}
/>
)
}}
</NavItem>
<NavItem routeName="Search" href="/search">
{({isActive}) => {
const Icon = isActive
? MagnifyingGlassIcon2Solid
: MagnifyingGlassIcon2
return (
<Icon
size={25}
style={[styles.ctrlIcon, pal.text, styles.searchIcon]}
strokeWidth={1.8}
/>
)
}}
</NavItem>
<NavItem routeName="Feeds" href="/feeds">
{({isActive}) => {
return (
<HashtagIcon
size={22}
style={[styles.ctrlIcon, pal.text, styles.feedsIcon]}
strokeWidth={isActive ? 4 : 2.5}
/>
)
}}
</NavItem>
{hasSession && (
{hasSession ? (
<>
<NavItem routeName="Notifications" href="/notifications">
<NavItem routeName="Home" href="/">
{({isActive}) => {
const Icon = isActive ? BellIconSolid : BellIcon
const Icon = isActive ? HomeIconSolid : HomeIcon
return (
<Icon
strokeWidth={4}
size={24}
strokeWidth={1.9}
style={[styles.ctrlIcon, pal.text, styles.bellIcon]}
style={[styles.ctrlIcon, pal.text, styles.homeIcon]}
/>
)
}}
</NavItem>
<NavItem
routeName="Profile"
href={
currentAccount
? makeProfileLink({
did: currentAccount.did,
handle: currentAccount.handle,
})
: '/'
}>
<NavItem routeName="Search" href="/search">
{({isActive}) => {
const Icon = isActive ? UserIconSolid : UserIcon
const Icon = isActive
? MagnifyingGlassIcon2Solid
: MagnifyingGlassIcon2
return (
<Icon
size={28}
strokeWidth={1.5}
style={[styles.ctrlIcon, pal.text, styles.profileIcon]}
size={25}
style={[styles.ctrlIcon, pal.text, styles.searchIcon]}
strokeWidth={1.8}
/>
)
}}
</NavItem>
{hasSession && (
<>
<NavItem routeName="Feeds" href="/feeds">
{({isActive}) => {
return (
<HashtagIcon
size={22}
style={[styles.ctrlIcon, pal.text, styles.feedsIcon]}
strokeWidth={isActive ? 4 : 2.5}
/>
)
}}
</NavItem>
<NavItem routeName="Notifications" href="/notifications">
{({isActive}) => {
const Icon = isActive ? BellIconSolid : BellIcon
return (
<Icon
size={24}
strokeWidth={1.9}
style={[styles.ctrlIcon, pal.text, styles.bellIcon]}
/>
)
}}
</NavItem>
<NavItem
routeName="Profile"
href={
currentAccount
? makeProfileLink({
did: currentAccount.did,
handle: currentAccount.handle,
})
: '/'
}>
{({isActive}) => {
const Icon = isActive ? UserIconSolid : UserIcon
return (
<Icon
size={28}
strokeWidth={1.5}
style={[styles.ctrlIcon, pal.text, styles.profileIcon]}
/>
)
}}
</NavItem>
</>
)}
</>
) : (
<>
<View
style={{
width: '100%',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingTop: 14,
paddingBottom: 2,
paddingLeft: 14,
paddingRight: 6,
gap: 8,
}}>
<View style={{flexDirection: 'row', alignItems: 'center', gap: 12}}>
<Logo width={32} />
<View style={{paddingTop: 4}}>
<Logotype width={80} fill={pal.text.color} />
</View>
</View>
<View style={{flexDirection: 'row', alignItems: 'center', gap: 8}}>
<Button
onPress={showCreateAccount}
accessibilityHint={_(msg`Sign up`)}
accessibilityLabel={_(msg`Sign up`)}>
<Text type="md" style={[{color: 'white'}, s.bold]}>
<Trans>Sign up</Trans>
</Text>
</Button>
<Button
type="default"
onPress={showSignIn}
accessibilityHint={_(msg`Sign in`)}
accessibilityLabel={_(msg`Sign in`)}>
<Text type="md" style={[pal.text, s.bold]}>
<Trans>Sign in</Trans>
</Text>
</Button>
</View>
</View>
</>
)}
</Animated.View>
+55 -53
View File
@@ -266,6 +266,10 @@ export function DesktopLeftNav() {
const {isDesktop, isTablet} = useWebMediaQueries()
const numUnread = useUnreadNotifications()
if (!hasSession && !isDesktop) {
return null
}
return (
<View
style={[
@@ -282,59 +286,58 @@ export function DesktopLeftNav() {
</View>
) : null}
<BackBtn />
<NavItem
href="/"
icon={<HomeIcon size={isDesktop ? 24 : 28} style={pal.text} />}
iconFilled={
<HomeIconSolid
strokeWidth={4}
size={isDesktop ? 24 : 28}
style={pal.text}
/>
}
label={_(msg`Home`)}
/>
<NavItem
href="/search"
icon={
<MagnifyingGlassIcon2
strokeWidth={2}
size={isDesktop ? 24 : 26}
style={pal.text}
/>
}
iconFilled={
<MagnifyingGlassIcon2Solid
strokeWidth={2}
size={isDesktop ? 24 : 26}
style={pal.text}
/>
}
label={_(msg`Search`)}
/>
<NavItem
href="/feeds"
icon={
<HashtagIcon
strokeWidth={2.25}
style={pal.text as FontAwesomeIconStyle}
size={isDesktop ? 24 : 28}
/>
}
iconFilled={
<HashtagIcon
strokeWidth={2.5}
style={pal.text as FontAwesomeIconStyle}
size={isDesktop ? 24 : 28}
/>
}
label={_(msg`Feeds`)}
/>
{hasSession && (
<>
<BackBtn />
<NavItem
href="/"
icon={<HomeIcon size={isDesktop ? 24 : 28} style={pal.text} />}
iconFilled={
<HomeIconSolid
strokeWidth={4}
size={isDesktop ? 24 : 28}
style={pal.text}
/>
}
label={_(msg`Home`)}
/>
<NavItem
href="/search"
icon={
<MagnifyingGlassIcon2
strokeWidth={2}
size={isDesktop ? 24 : 26}
style={pal.text}
/>
}
iconFilled={
<MagnifyingGlassIcon2Solid
strokeWidth={2}
size={isDesktop ? 24 : 26}
style={pal.text}
/>
}
label={_(msg`Search`)}
/>
<NavItem
href="/feeds"
icon={
<HashtagIcon
strokeWidth={2.25}
style={pal.text as FontAwesomeIconStyle}
size={isDesktop ? 24 : 28}
/>
}
iconFilled={
<HashtagIcon
strokeWidth={2.5}
style={pal.text as FontAwesomeIconStyle}
size={isDesktop ? 24 : 28}
/>
}
label={_(msg`Feeds`)}
/>
<NavItem
href="/notifications"
count={numUnread}
@@ -406,7 +409,7 @@ export function DesktopLeftNav() {
style={pal.text}
/>
}
label="Profile"
label={_(msg`Profile`)}
/>
<NavItem
href="/settings"
@@ -512,7 +515,6 @@ const styles = StyleSheet.create({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
width: 140,
borderRadius: 24,
paddingTop: 10,
paddingBottom: 12, // visually aligns the text vertically inside the button
+1 -1
View File
@@ -52,7 +52,7 @@ export function DesktopRightNav() {
</Text>
</View>
) : undefined}
<View style={[s.flexRow]}>
<View style={[{flexWrap: 'wrap'}, s.flexRow]}>
{hasSession && (
<>
<TextLink
+6 -12
View File
@@ -19,10 +19,6 @@ import {useTheme} from 'lib/ThemeContext'
import {usePalette} from 'lib/hooks/usePalette'
import {RoutesContainer, TabsNavigator} from '../../Navigation'
import {isStateAtTabRoot} from 'lib/routes/helpers'
import {
SafeAreaProvider,
initialWindowMetrics,
} from 'react-native-safe-area-context'
import {
useIsDrawerOpen,
useSetDrawerOpen,
@@ -107,14 +103,12 @@ export const Shell: React.FC = function ShellImpl() {
const pal = usePalette('default')
const theme = useTheme()
return (
<SafeAreaProvider initialMetrics={initialWindowMetrics} style={pal.view}>
<View testID="mobileShellView" style={[styles.outerContainer, pal.view]}>
<StatusBar style={theme.colorScheme === 'dark' ? 'light' : 'dark'} />
<RoutesContainer>
<ShellInner />
</RoutesContainer>
</View>
</SafeAreaProvider>
<View testID="mobileShellView" style={[styles.outerContainer, pal.view]}>
<StatusBar style={theme.colorScheme === 'dark' ? 'light' : 'dark'} />
<RoutesContainer>
<ShellInner />
</RoutesContainer>
</View>
)
}