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:
@@ -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>
|
||||
|
||||
@@ -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
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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]}>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user