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}