Merge remote-tracking branch 'origin/main' into samuel/alf-login

This commit is contained in:
Samuel Newman 2024-03-20 15:37:14 +00:00
commit d24ffba01d
62 changed files with 30007 additions and 10775 deletions

View file

@ -447,7 +447,7 @@ export const ComposePost = observer(function ComposePost({
/>
)}
{quote ? (
<View style={[s.mt5, isWeb && s.mb10]}>
<View style={[s.mt5, isWeb && s.mb10, {pointerEvents: 'none'}]}>
<QuoteEmbed quote={quote} />
</View>
) : undefined}

View file

@ -0,0 +1,45 @@
import {useState, useEffect} from 'react'
import * as apilib from 'lib/api/index'
import {getLinkMeta} from 'lib/link-meta/link-meta'
import {ComposerOpts} from 'state/shell/composer'
import {getAgent} from '#/state/session'
export function useExternalLinkFetch({}: {
setQuote: (opts: ComposerOpts['quote']) => void
}) {
const [extLink, setExtLink] = useState<apilib.ExternalEmbedDraft | undefined>(
undefined,
)
useEffect(() => {
let aborted = false
const cleanup = () => {
aborted = true
}
if (!extLink) {
return cleanup
}
if (!extLink.meta) {
getLinkMeta(getAgent(), extLink.uri).then(meta => {
if (aborted) {
return
}
setExtLink({
uri: extLink.uri,
isLoading: !!meta.image,
meta,
})
})
return cleanup
}
if (extLink.isLoading) {
setExtLink({
...extLink,
isLoading: false, // done
})
}
return cleanup
}, [extLink])
return {extLink, setExtLink}
}

View file

@ -22,6 +22,7 @@ import {listenSoftReset} from '#/state/events'
import {truncateAndInvalidate} from '#/state/queries/util'
import {TabState, getTabState, getRootNavigation} from '#/lib/routes/helpers'
import {isNative} from '#/platform/detection'
import {logEvent} from '#/lib/statsig/statsig'
const POLL_FREQ = 60e3 // 60sec
@ -68,6 +69,10 @@ export function FeedPage({
scrollToTop()
truncateAndInvalidate(queryClient, FEED_RQKEY(feed))
setHasNew(false)
logEvent('feed:refresh', {
feedType: feed.split('|')[0],
reason: 'soft-reset',
})
}
}, [navigation, isPageFocused, scrollToTop, queryClient, feed, setHasNew])
@ -89,6 +94,10 @@ export function FeedPage({
scrollToTop()
truncateAndInvalidate(queryClient, FEED_RQKEY(feed))
setHasNew(false)
logEvent('feed:refresh', {
feedType: feed.split('|')[0],
reason: 'load-latest',
})
}, [scrollToTop, feed, queryClient, setHasNew])
return (

View file

@ -6,7 +6,13 @@
*
*/
import React from 'react'
import {SafeAreaView, Text, TouchableOpacity, StyleSheet} from 'react-native'
import {
SafeAreaView,
TouchableOpacity,
StyleSheet,
ViewStyle,
} from 'react-native'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
@ -23,14 +29,14 @@ const ImageDefaultHeader = ({onRequestClose}: Props) => {
return (
<SafeAreaView style={styles.root}>
<TouchableOpacity
style={styles.closeButton}
style={[styles.closeButton, styles.blurredBackground]}
onPress={onRequestClose}
hitSlop={HIT_SLOP}
accessibilityRole="button"
accessibilityLabel={_(msg`Close image`)}
accessibilityHint={_(msg`Closes viewer for header image`)}
onAccessibilityEscape={onRequestClose}>
<Text style={styles.closeText}></Text>
<FontAwesomeIcon icon="close" color={'#fff'} size={22} />
</TouchableOpacity>
</SafeAreaView>
)
@ -42,8 +48,8 @@ const styles = StyleSheet.create({
pointerEvents: 'box-none',
},
closeButton: {
marginRight: 8,
marginTop: 8,
marginRight: 10,
marginTop: 10,
width: 44,
height: 44,
alignItems: 'center',
@ -51,13 +57,10 @@ const styles = StyleSheet.create({
borderRadius: 22,
backgroundColor: '#00000077',
},
closeText: {
lineHeight: 22,
fontSize: 19,
textAlign: 'center',
color: '#FFF',
includeFontPadding: false,
},
blurredBackground: {
backdropFilter: 'blur(10px)',
WebkitBackdropFilter: 'blur(10px)',
} as ViewStyle,
})
export default ImageDefaultHeader

View file

@ -7,6 +7,7 @@ import {
StyleSheet,
View,
Pressable,
ViewStyle,
} from 'react-native'
import {
FontAwesomeIcon,
@ -24,6 +25,7 @@ import {
ProfileImageLightbox,
} from '#/state/lightbox'
import {useWebBodyScrollLock} from '#/lib/hooks/useWebBodyScrollLock'
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
interface Img {
uri: string
@ -111,6 +113,14 @@ function LightboxInner({
return () => window.removeEventListener('keydown', onKeyDown)
}, [onKeyDown])
const {isTabletOrDesktop} = useWebMediaQueries()
const btnStyle = React.useMemo(() => {
return isTabletOrDesktop ? styles.btnTablet : styles.btnMobile
}, [isTabletOrDesktop])
const iconSize = React.useMemo(() => {
return isTabletOrDesktop ? 32 : 24
}, [isTabletOrDesktop])
return (
<View style={styles.mask}>
<TouchableWithoutFeedback
@ -130,28 +140,38 @@ function LightboxInner({
{canGoLeft && (
<TouchableOpacity
onPress={onPressLeft}
style={[styles.btn, styles.leftBtn]}
style={[
styles.btn,
btnStyle,
styles.leftBtn,
styles.blurredBackground,
]}
accessibilityRole="button"
accessibilityLabel={_(msg`Previous image`)}
accessibilityHint="">
<FontAwesomeIcon
icon="angle-left"
style={styles.icon as FontAwesomeIconStyle}
size={40}
size={iconSize}
/>
</TouchableOpacity>
)}
{canGoRight && (
<TouchableOpacity
onPress={onPressRight}
style={[styles.btn, styles.rightBtn]}
style={[
styles.btn,
btnStyle,
styles.rightBtn,
styles.blurredBackground,
]}
accessibilityRole="button"
accessibilityLabel={_(msg`Next image`)}
accessibilityHint="">
<FontAwesomeIcon
icon="angle-right"
style={styles.icon as FontAwesomeIconStyle}
size={40}
size={iconSize}
/>
</TouchableOpacity>
)}
@ -213,20 +233,30 @@ const styles = StyleSheet.create({
},
btn: {
position: 'absolute',
backgroundColor: '#000',
width: 50,
height: 50,
backgroundColor: '#00000077',
justifyContent: 'center',
alignItems: 'center',
},
btnTablet: {
width: 50,
height: 50,
borderRadius: 25,
left: 30,
right: 30,
},
btnMobile: {
width: 44,
height: 44,
borderRadius: 22,
left: 20,
right: 20,
},
leftBtn: {
left: 30,
right: 'auto',
top: '50%',
},
rightBtn: {
position: 'absolute',
right: 30,
left: 'auto',
top: '50%',
},
footer: {
@ -234,4 +264,8 @@ const styles = StyleSheet.create({
paddingVertical: 24,
backgroundColor: colors.black,
},
blurredBackground: {
backdropFilter: 'blur(10px)',
WebkitBackdropFilter: 'blur(10px)',
} as ViewStyle,
})

View file

@ -39,7 +39,7 @@ function SwitchAccountCard({account}: {account: SessionAccount}) {
track('Settings:SignOutButtonClicked')
closeAllActiveElements()
// needs to be in timeout or the modal re-opens
setTimeout(() => logout(), 0)
setTimeout(() => logout('SwitchAccount'), 0)
}, [track, logout, closeAllActiveElements])
const contents = (
@ -95,7 +95,9 @@ function SwitchAccountCard({account}: {account: SessionAccount}) {
key={account.did}
style={[isSwitchingAccounts && styles.dimmed]}
onPress={
isSwitchingAccounts ? undefined : () => onPressSwitchAccount(account)
isSwitchingAccounts
? undefined
: () => onPressSwitchAccount(account, 'SwitchAccount')
}
accessibilityRole="button"
accessibilityLabel={_(msg`Switch to ${account.handle}`)}

View file

@ -108,7 +108,8 @@ export function PostThread({
?.ui('contentList')
.blurs.find(
cause =>
cause.type === 'label' && cause.labelDef.id === '!no-unauthenticated',
cause.type === 'label' &&
cause.labelDef.identifier === '!no-unauthenticated',
)
}, [rootPost, moderationOpts])

View file

@ -90,6 +90,7 @@ let Feed = ({
const [isPTRing, setIsPTRing] = React.useState(false)
const checkForNewRef = React.useRef<(() => void) | null>(null)
const lastFetchRef = React.useRef<number>(Date.now())
const feedType = feed.split('|')[0]
const opts = React.useMemo(
() => ({enabled, ignoreFilterFor}),
@ -214,6 +215,10 @@ let Feed = ({
const onRefresh = React.useCallback(async () => {
track('Feed:onRefresh')
logEvent('feed:refresh', {
feedType: feedType,
reason: 'pull-to-refresh',
})
setIsPTRing(true)
try {
await refetch()
@ -222,9 +227,8 @@ let Feed = ({
logger.error('Failed to refresh posts feed', {message: err})
}
setIsPTRing(false)
}, [refetch, track, setIsPTRing, onHasNew])
}, [refetch, track, setIsPTRing, onHasNew, feedType])
const feedType = feed.split('|')[0]
const onEndReached = React.useCallback(async () => {
if (isFetching || !hasNextPage || isError) return

View file

@ -46,7 +46,7 @@ export function FeedErrorMessage({
if (
typeof knownError !== 'undefined' &&
knownError !== KnownError.Unknown &&
feedDesc.startsWith('feedgen')
(feedDesc.startsWith('feedgen') || knownError === KnownError.FeedNSFPublic)
) {
return (
<FeedgenErrorMessage
@ -240,6 +240,9 @@ function detectKnownError(
if (typeof error !== 'string') {
error = error.toString()
}
if (error.includes(KnownError.FeedNSFPublic)) {
return KnownError.FeedNSFPublic
}
if (!feedDesc.startsWith('feedgen')) {
return KnownError.Unknown
}
@ -263,8 +266,5 @@ function detectKnownError(
if (error.includes('feed provided an invalid response')) {
return KnownError.FeedgenBadResponse
}
if (error.includes(KnownError.FeedNSFPublic)) {
return KnownError.FeedNSFPublic
}
return KnownError.FeedgenUnknown
}

View file

@ -22,18 +22,24 @@ export function TestCtrls() {
const {mutate: setFeedViewPref} = useSetFeedViewPreferencesMutation()
const {setShowLoggedOut} = useLoggedOutViewControls()
const onPressSignInAlice = async () => {
await login({
service: 'http://localhost:3000',
identifier: 'alice.test',
password: 'hunter2',
})
await login(
{
service: 'http://localhost:3000',
identifier: 'alice.test',
password: 'hunter2',
},
'LoginForm',
)
}
const onPressSignInBob = async () => {
await login({
service: 'http://localhost:3000',
identifier: 'bob.test',
password: 'hunter2',
})
await login(
{
service: 'http://localhost:3000',
identifier: 'bob.test',
password: 'hunter2',
},
'LoginForm',
)
}
return (
<View style={{position: 'absolute', top: 100, right: 0, zIndex: 100}}>
@ -51,7 +57,7 @@ export function TestCtrls() {
/>
<Pressable
testID="e2eSignOut"
onPress={() => logout()}
onPress={() => logout('Settings')}
accessibilityRole="button"
style={BTN}
/>

View file

@ -298,7 +298,10 @@ let EditableUserAvatar = ({
<Menu.Root>
<Menu.Trigger label={_(msg`Edit avatar`)}>
{({props}) => (
<TouchableOpacity {...props} activeOpacity={0.8}>
<TouchableOpacity
{...props}
activeOpacity={0.8}
testID="changeAvatarBtn">
{avatar ? (
<HighPriorityImage
testID="userAvatarImage"

View file

@ -84,7 +84,10 @@ export function UserBanner({
<Menu.Root>
<Menu.Trigger label={_(msg`Edit avatar`)}>
{({props}) => (
<TouchableOpacity {...props} activeOpacity={0.8}>
<TouchableOpacity
{...props}
activeOpacity={0.8}
testID="changeBannerBtn">
{banner ? (
<Image
testID="userBannerImage"

View file

@ -237,7 +237,7 @@ const styles = StyleSheet.create({
paddingRight: 12,
borderRadius: 8,
fontFamily:
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif',
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Liberation Sans", Helvetica, Arial, sans-serif',
outline: 0,
border: 0,
},

View file

@ -2,7 +2,7 @@ import React from 'react'
import {Text as RNText, TextProps} from 'react-native'
import {s, lh} from 'lib/styles'
import {useTheme, TypographyVariant} from 'lib/ThemeContext'
import {isIOS} from 'platform/detection'
import {isIOS, isWeb} from 'platform/detection'
import {UITextView} from 'react-native-ui-text-view'
export type CustomTextProps = TextProps & {
@ -13,6 +13,11 @@ export type CustomTextProps = TextProps & {
selectable?: boolean
}
const fontFamilyStyle = {
fontFamily:
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Liberation Sans", Helvetica, Arial, sans-serif',
}
export function Text({
type = 'md',
children,
@ -39,7 +44,13 @@ export function Text({
return (
<RNText
style={[s.black, typography, lineHeightStyle, style]}
style={[
s.black,
typography,
isWeb && fontFamilyStyle,
lineHeightStyle,
style,
]}
// @ts-ignore web only -esb
dataSet={Object.assign({tooltip: title}, dataSet || {})}
selectable={selectable}

View file

@ -131,7 +131,7 @@ export function ModerationBlockedAccounts({}: Props) {
<Text type="lg" style={[pal.text, styles.emptyText]}>
<Trans>
You have not blocked any accounts yet. To block an account, go
to their profile and selected "Block account" from the menu on
to their profile and select "Block account" from the menu on
their account.
</Trans>
</Text>

View file

@ -130,8 +130,8 @@ export function ModerationMutedAccounts({}: Props) {
<Text type="lg" style={[pal.text, styles.emptyText]}>
<Trans>
You have not muted any accounts yet. To mute an account, go to
their profile and selected "Mute account" from the menu on
their account.
their profile and select "Mute account" from the menu on their
account.
</Trans>
</Text>
</View>

View file

@ -100,7 +100,9 @@ function SettingsAccountCard({account}: {account: SessionAccount}) {
{isCurrentAccount ? (
<TouchableOpacity
testID="signOutBtn"
onPress={logout}
onPress={() => {
logout('Settings')
}}
accessibilityRole="button"
accessibilityLabel={_(msg`Sign out`)}
accessibilityHint={`Signs ${profile?.displayName} out of Bluesky`}>
@ -129,7 +131,9 @@ function SettingsAccountCard({account}: {account: SessionAccount}) {
testID={`switchToAccountBtn-${account.handle}`}
key={account.did}
onPress={
isSwitchingAccounts ? undefined : () => onPressSwitchAccount(account)
isSwitchingAccounts
? undefined
: () => onPressSwitchAccount(account, 'Settings')
}
accessibilityRole="button"
accessibilityLabel={_(msg`Switch to ${account.handle}`)}
@ -711,7 +715,7 @@ export function SettingsScreen({}: Props) {
accessibilityRole="button"
accessibilityLabel={_(msg`Change handle`)}
accessibilityHint={_(
msg`Opens modal for choosing or creating a new Bluesky username`,
msg`Opens modal for choosing a new Bluesky handle`,
)}>
<View style={[styles.iconContainer, pal.btn]}>
<FontAwesomeIcon
@ -772,7 +776,7 @@ export function SettingsScreen({}: Props) {
accessibilityRole="button"
accessibilityLabel={_(msg`Export my data`)}
accessibilityHint={_(
msg`Opens modal for downloading Bluesky account data (repository)`,
msg`Opens modal for downloading your Bluesky account data (repository)`,
)}>
<View style={[styles.iconContainer, pal.btn]}>
<FontAwesomeIcon