Merge remote-tracking branch 'origin/main' into 3p-moderators

* origin/main: (33 commits)
  Improved server selector during account creation and signin (#2840)
  remove line height from text input for composer on ios (#2844)
  Add follow button to highlighted post (#2828)
  Cleaning up old codes pt_BR (#2809)
  Fix wrong translations in zh-CN localization  (#2812)
  Adjust Japanese translation (#2814)
  Update ja messages.po (#2821)
  change follow to follow all when !== 20 (#2831)
  ios adult content link fix 🤦 (#2845)
  improves build.md completeness (#2835)
  increase stroke width for active hashtag icon (#2829)
  Update `blueskyweb.xyz` links to `bsky.social` (#2830)
  eas nightlies (#2826)
  Always show post dropdown button at the bottom of the post, add share button to highlighted post (#2646)
  Measure header layout reliably (#2817)
  Round line height (#2824)
  Design system tweaks (#2822)
  Fix layout calculations (#2816)
  Fix flashes and jumps when opening profile (#2815)
  Show text cursor on web bio (#2813)
  ...
This commit is contained in:
Eric Bailey
2024-02-12 15:43:33 -06:00
90 changed files with 1781 additions and 1707 deletions
+2 -2
View File
@@ -37,8 +37,8 @@ export const CommunityGuidelinesScreen = (_props: Props) => {
The Community Guidelines have been moved to{' '}
<TextLink
style={pal.link}
href="https://blueskyweb.xyz/support/community-guidelines"
text="blueskyweb.xyz/support/community-guidelines"
href="https://bsky.social/about/support/community-guidelines"
text="bsky.social/about/support/community-guidelines"
/>
</Trans>
</Text>
+2 -2
View File
@@ -34,8 +34,8 @@ export const CopyrightPolicyScreen = (_props: Props) => {
The Copyright Policy has been moved to{' '}
<TextLink
style={pal.link}
href="https://blueskyweb.xyz/support/community-guidelines"
text="blueskyweb.xyz/support/community-guidelines"
href="https://bsky.social/about/support/copyright"
text="bsky.social/about/support/copyright"
/>
</Trans>
</Text>
+18 -13
View File
@@ -7,7 +7,7 @@ import {FollowingEmptyState} from 'view/com/posts/FollowingEmptyState'
import {FollowingEndOfFeed} from 'view/com/posts/FollowingEndOfFeed'
import {CustomFeedEmptyState} from 'view/com/posts/CustomFeedEmptyState'
import {FeedsTabBar} from '../com/pager/FeedsTabBar'
import {Pager, RenderTabBarFnProps} from 'view/com/pager/Pager'
import {Pager, RenderTabBarFnProps, PagerRef} from 'view/com/pager/Pager'
import {FeedPage} from 'view/com/feeds/FeedPage'
import {HomeLoggedOutCTA} from '../com/auth/HomeLoggedOutCTA'
import {useSetMinimalShellMode, useSetDrawerSwipeDisabled} from '#/state/shell'
@@ -16,25 +16,19 @@ import {usePinnedFeedsInfos, FeedSourceInfo} from '#/state/queries/feed'
import {UsePreferencesQueryResponse} from '#/state/queries/preferences/types'
import {emitSoftReset} from '#/state/events'
import {useSession} from '#/state/session'
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
import * as persisted from '#/state/persisted'
import {useSelectedFeed, useSetSelectedFeed} from '#/state/shell/selected-feed'
type Props = NativeStackScreenProps<HomeTabNavigatorParams, 'Home'>
export function HomeScreen(props: Props) {
const {data: preferences} = usePreferencesQuery()
const {feeds: pinnedFeedInfos, isLoading: isPinnedFeedsLoading} =
usePinnedFeedsInfos()
const {isDesktop} = useWebMediaQueries()
const [rawInitialFeed] = React.useState<string>(
() => persisted.get('lastSelectedHomeFeed') ?? 'home',
)
if (preferences && pinnedFeedInfos && !isPinnedFeedsLoading) {
return (
<HomeScreenReady
{...props}
preferences={preferences}
pinnedFeedInfos={pinnedFeedInfos}
rawInitialFeed={isDesktop ? 'home' : rawInitialFeed}
/>
)
} else {
@@ -49,11 +43,9 @@ export function HomeScreen(props: Props) {
function HomeScreenReady({
preferences,
pinnedFeedInfos,
rawInitialFeed,
}: Props & {
preferences: UsePreferencesQueryResponse
pinnedFeedInfos: FeedSourceInfo[]
rawInitialFeed: string
}) {
const allFeeds = React.useMemo(() => {
const feeds: FeedDescriptor[] = []
@@ -68,12 +60,24 @@ function HomeScreenReady({
return feeds
}, [pinnedFeedInfos])
const [rawSelectedFeed, setSelectedFeed] =
React.useState<string>(rawInitialFeed)
const rawSelectedFeed = useSelectedFeed()
const setSelectedFeed = useSetSelectedFeed()
const maybeFoundIndex = allFeeds.indexOf(rawSelectedFeed as FeedDescriptor)
const selectedIndex = Math.max(0, maybeFoundIndex)
const selectedFeed = allFeeds[selectedIndex]
const pagerRef = React.useRef<PagerRef>(null)
const lastPagerReportedIndexRef = React.useRef(selectedIndex)
React.useLayoutEffect(() => {
// Since the pager is not a controlled component, adjust it imperatively
// if the selected index gets out of sync with what it last reported.
// This is supposed to only happen on the web when you use the right nav.
if (selectedIndex !== lastPagerReportedIndexRef.current) {
lastPagerReportedIndexRef.current = selectedIndex
pagerRef.current?.setPage(selectedIndex)
}
}, [selectedIndex])
const {hasSession} = useSession()
const setMinimalShellMode = useSetMinimalShellMode()
const setDrawerSwipeDisabled = useSetDrawerSwipeDisabled()
@@ -93,7 +97,7 @@ function HomeScreenReady({
setDrawerSwipeDisabled(index > 0)
const feed = allFeeds[index]
setSelectedFeed(feed)
persisted.write('lastSelectedHomeFeed', feed)
lastPagerReportedIndexRef.current = index
},
[setDrawerSwipeDisabled, setSelectedFeed, setMinimalShellMode, allFeeds],
)
@@ -147,6 +151,7 @@ function HomeScreenReady({
return hasSession ? (
<Pager
key={allFeeds.join(',')}
ref={pagerRef}
testID="homeScreen"
initialPage={selectedIndex}
onPageSelected={onPageSelected}
+12
View File
@@ -29,6 +29,9 @@ import {listenSoftReset, emitSoftReset} from '#/state/events'
import {truncateAndInvalidate} from '#/state/queries/util'
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
import {isNative} from '#/platform/detection'
import {FAB} from '../com/util/fab/FAB'
import {ComposeIcon2} from 'lib/icons'
import {useComposerControls} from '#/state/shell/composer'
type Props = NativeStackScreenProps<
NotificationsTabNavigatorParams,
@@ -47,6 +50,7 @@ export function NotificationsScreen({}: Props) {
const unreadApi = useUnreadNotificationsApi()
const hasNew = !!unreadNotifs
const isScreenFocused = useIsFocused()
const {openComposer} = useComposerControls()
// event handlers
// =
@@ -156,6 +160,14 @@ export function NotificationsScreen({}: Props) {
showIndicator={hasNew}
/>
)}
<FAB
testID="composeFAB"
onPress={() => openComposer({})}
icon={<ComposeIcon2 strokeWidth={1.5} size={29} style={s.white} />}
accessibilityRole="button"
accessibilityLabel={_(msg`New post`)}
accessibilityHint=""
/>
</View>
)
}
+2 -2
View File
@@ -34,8 +34,8 @@ export const PrivacyPolicyScreen = (_props: Props) => {
The Privacy Policy has been moved to{' '}
<TextLink
style={pal.link}
href="https://blueskyweb.xyz/support/privacy-policy"
text="blueskyweb.xyz/support/privacy-policy"
href="https://bsky.social/about/support/privacy-policy"
text="bsky.social/about/support/privacy-policy"
/>
</Trans>
</Text>
+75 -28
View File
@@ -1,7 +1,12 @@
import React, {useMemo} from 'react'
import {StyleSheet, View} from 'react-native'
import {useFocusEffect} from '@react-navigation/native'
import {AppBskyActorDefs, moderateProfile, ModerationOpts} from '@atproto/api'
import {
AppBskyActorDefs,
moderateProfile,
ModerationOpts,
RichText as RichTextAPI,
} from '@atproto/api'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types'
@@ -11,7 +16,7 @@ import {ScreenHider} from 'view/com/util/moderation/ScreenHider'
import {Feed} from 'view/com/posts/Feed'
import {ProfileLists} from '../com/lists/ProfileLists'
import {ProfileFeedgens} from '../com/feeds/ProfileFeedgens'
import {ProfileHeader} from '../com/profile/ProfileHeader'
import {ProfileHeader, ProfileHeaderLoading} from '../com/profile/ProfileHeader'
import {PagerWithHeader} from 'view/com/pager/PagerWithHeader'
import {ErrorScreen} from '../com/util/error/ErrorScreen'
import {EmptyState} from '../com/util/EmptyState'
@@ -28,7 +33,7 @@ import {
import {useResolveDidQuery} from '#/state/queries/resolve-uri'
import {useProfileQuery} from '#/state/queries/profile'
import {useProfileShadow} from '#/state/cache/profile-shadow'
import {useSession} from '#/state/session'
import {useSession, getAgent} from '#/state/session'
import {useModerationOpts} from '#/state/queries/preferences'
import {useProfileExtraInfoQuery} from '#/state/queries/profile-extra-info'
import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed'
@@ -50,6 +55,7 @@ interface SectionRef {
type Props = NativeStackScreenProps<CommonNavigatorParams, 'Profile'>
export function ProfileScreen({route}: Props) {
const {_} = useLingui()
const {currentAccount} = useSession()
const name =
route.params.name === 'me' ? currentAccount?.did : route.params.name
@@ -65,6 +71,7 @@ export function ProfileScreen({route}: Props) {
error: profileError,
refetch: refetchProfile,
isLoading: isLoadingProfile,
isPlaceholderData: isPlaceholderProfile,
} = useProfileQuery({
did: resolvedDid,
})
@@ -84,27 +91,23 @@ export function ProfileScreen({route}: Props) {
}
}, [profile?.viewer?.blockedBy, resolvedDid])
if (isLoadingDid || isLoadingProfile || !moderationOpts) {
// Most pushes will happen here, since we will have only placeholder data
if (isLoadingDid || isLoadingProfile) {
return (
<CenteredView>
<ProfileHeader
profile={null}
moderation={null}
isProfilePreview={true}
/>
<ProfileHeaderLoading />
</CenteredView>
)
}
if (resolveError || profileError) {
return (
<CenteredView>
<ErrorScreen
testID="profileErrorScreen"
title="Oops!"
message={cleanError(resolveError || profileError)}
onPressTryAgain={onPressTryAgain}
/>
</CenteredView>
<ErrorScreen
testID="profileErrorScreen"
title={profileError ? _(msg`Not Found`) : _(msg`Oops!`)}
message={cleanError(resolveError || profileError)}
onPressTryAgain={onPressTryAgain}
showHeader
/>
)
}
if (profile && moderationOpts) {
@@ -112,31 +115,33 @@ export function ProfileScreen({route}: Props) {
<ProfileScreenLoaded
profile={profile}
moderationOpts={moderationOpts}
isPlaceholderProfile={isPlaceholderProfile}
hideBackButton={!!route.params.hideBackButton}
/>
)
}
// should never happen
return (
<CenteredView>
<ErrorScreen
testID="profileErrorScreen"
title="Oops!"
message="Something went wrong and we're not sure what."
onPressTryAgain={onPressTryAgain}
/>
</CenteredView>
<ErrorScreen
testID="profileErrorScreen"
title="Oops!"
message="Something went wrong and we're not sure what."
onPressTryAgain={onPressTryAgain}
showHeader
/>
)
}
function ProfileScreenLoaded({
profile: profileUnshadowed,
isPlaceholderProfile,
moderationOpts,
hideBackButton,
}: {
profile: AppBskyActorDefs.ProfileViewDetailed
moderationOpts: ModerationOpts
hideBackButton: boolean
isPlaceholderProfile: boolean
}) {
const profile = useProfileShadow(profileUnshadowed)
const {hasSession, currentAccount} = useSession()
@@ -156,6 +161,10 @@ function ProfileScreenLoaded({
useSetTitle(combinedDisplayName(profile))
const description = profile.description ?? ''
const hasDescription = description !== ''
const [descriptionRT, isResolvingDescriptionRT] = useRichText(description)
const showPlaceholder = isPlaceholderProfile || isResolvingDescriptionRT
const moderation = useMemo(
() => moderateProfile(profile, moderationOpts),
[profile, moderationOpts],
@@ -269,11 +278,20 @@ function ProfileScreenLoaded({
return (
<ProfileHeader
profile={profile}
moderation={moderation}
descriptionRT={hasDescription ? descriptionRT : null}
moderationOpts={moderationOpts}
hideBackButton={hideBackButton}
isPlaceholderProfile={showPlaceholder}
/>
)
}, [profile, moderation, hideBackButton])
}, [
profile,
descriptionRT,
hasDescription,
moderationOpts,
hideBackButton,
showPlaceholder,
])
return (
<ScreenHider
@@ -283,7 +301,7 @@ function ProfileScreenLoaded({
moderation={moderation.account}>
<PagerWithHeader
testID="profilePager"
isHeaderReady={true}
isHeaderReady={!showPlaceholder}
items={sectionTitles}
onPageSelected={onPageSelected}
onCurrentPageSelected={onCurrentPageSelected}
@@ -440,6 +458,35 @@ function ProfileEndOfFeed() {
)
}
function useRichText(text: string): [RichTextAPI, boolean] {
const [prevText, setPrevText] = React.useState(text)
const [rawRT, setRawRT] = React.useState(() => new RichTextAPI({text}))
const [resolvedRT, setResolvedRT] = React.useState<RichTextAPI | null>(null)
if (text !== prevText) {
setPrevText(text)
setRawRT(new RichTextAPI({text}))
setResolvedRT(null)
// This will queue an immediate re-render
}
React.useEffect(() => {
let ignore = false
async function resolveRTFacets() {
// new each time
const resolvedRT = new RichTextAPI({text})
await resolvedRT.detectFacets(getAgent())
if (!ignore) {
setResolvedRT(resolvedRT)
}
}
resolveRTFacets()
return () => {
ignore = true
}
}, [text])
const isResolving = resolvedRT === null
return [resolvedRT ?? rawRT, isResolving]
}
const styles = StyleSheet.create({
container: {
flexDirection: 'column',
+2 -2
View File
@@ -863,13 +863,13 @@ export function SettingsScreen({}: Props) {
<TextLink
type="md"
style={pal.link}
href="https://blueskyweb.xyz/support/tos"
href="https://bsky.social/about/support/tos"
text={_(msg`Terms of Service`)}
/>
<TextLink
type="md"
style={pal.link}
href="https://blueskyweb.xyz/support/privacy-policy"
href="https://bsky.social/about/support/privacy-policy"
text={_(msg`Privacy Policy`)}
/>
</View>
+4 -4
View File
@@ -14,19 +14,19 @@ export function Links() {
<View style={[a.gap_md, a.align_start]}>
<InlineLink
to="https://blueskyweb.xyz"
to="https://bsky.social"
warnOnMismatchingTextChild
style={[a.text_md]}>
External
</InlineLink>
<InlineLink to="https://blueskyweb.xyz" style={[a.text_md]}>
<InlineLink to="https://bsky.social" style={[a.text_md]}>
<H3>External with custom children</H3>
</InlineLink>
<InlineLink
to="https://blueskyweb.xyz"
to="https://bsky.social"
warnOnMismatchingTextChild
style={[a.text_lg]}>
https://blueskyweb.xyz
https://bsky.social
</InlineLink>
<InlineLink
to="https://bsky.app/profile/bsky.app"
+10 -10
View File
@@ -14,22 +14,22 @@ export function Theming() {
<Text style={[a.font_bold, a.pt_xl, a.px_md]}>theme.atoms.text</Text>
<View style={[a.flex_1, t.atoms.border, a.border_t]} />
<Text style={[a.font_bold, t.atoms.text_contrast_600, a.px_md]}>
theme.atoms.text_contrast_600
<View style={[a.flex_1, t.atoms.border_contrast_high, a.border_t]} />
<Text style={[a.font_bold, t.atoms.text_contrast_high, a.px_md]}>
theme.atoms.text_contrast_high
</Text>
<View style={[a.flex_1, t.atoms.border, a.border_t]} />
<Text style={[a.font_bold, t.atoms.text_contrast_500, a.px_md]}>
theme.atoms.text_contrast_500
<View style={[a.flex_1, t.atoms.border_contrast_medium, a.border_t]} />
<Text style={[a.font_bold, t.atoms.text_contrast_medium, a.px_md]}>
theme.atoms.text_contrast_medium
</Text>
<View style={[a.flex_1, t.atoms.border, a.border_t]} />
<Text style={[a.font_bold, t.atoms.text_contrast_400, a.px_md]}>
theme.atoms.text_contrast_400
<View style={[a.flex_1, t.atoms.border_contrast_low, a.border_t]} />
<Text style={[a.font_bold, t.atoms.text_contrast_low, a.px_md]}>
theme.atoms.text_contrast_low
</Text>
<View style={[a.flex_1, t.atoms.border_contrast, a.border_t]} />
<View style={[a.flex_1, t.atoms.border_contrast_low, a.border_t]} />
<View style={[a.w_full, a.gap_md]}>
<View style={[t.atoms.bg, a.justify_center, a.p_md]}>
+3 -11
View File
@@ -2,20 +2,12 @@ import React from 'react'
import {View} from 'react-native'
import {atoms as a} from '#/alf'
import {Text, H1, H2, H3, H4, H5, H6, P} from '#/components/Typography'
import {Text} from '#/components/Typography'
import {RichText} from '#/components/RichText'
export function Typography() {
return (
<View style={[a.gap_md]}>
<H1>H1 Heading</H1>
<H2>H2 Heading</H2>
<H3>H3 Heading</H3>
<H4>H4 Heading</H4>
<H5>H5 Heading</H5>
<H6>H6 Heading</H6>
<P>P Paragraph</P>
<Text style={[a.text_5xl]}>atoms.text_5xl</Text>
<Text style={[a.text_4xl]}>atoms.text_4xl</Text>
<Text style={[a.text_3xl]}>atoms.text_3xl</Text>
@@ -29,11 +21,11 @@ export function Typography() {
<RichText
resolveFacets
value={`This is rich text. It can have mentions like @bsky.app or links like https://blueskyweb.xyz`}
value={`This is rich text. It can have mentions like @bsky.app or links like https://bsky.social`}
/>
<RichText
resolveFacets
value={`This is rich text. It can have mentions like @bsky.app or links like https://blueskyweb.xyz`}
value={`This is rich text. It can have mentions like @bsky.app or links like https://bsky.social`}
style={[a.text_xl]}
/>
</View>
+2 -2
View File
@@ -33,8 +33,8 @@ export const TermsOfServiceScreen = (_props: Props) => {
<Trans>The Terms of Service have been moved to</Trans>{' '}
<TextLink
style={pal.link}
href="https://blueskyweb.xyz/support/tos"
text="blueskyweb.xyz/support/tos"
href="https://bsky.social/about/support/tos"
text="bsky.social/about/support/tos"
/>
</Text>
</View>