diff options
Diffstat (limited to 'src/screens')
42 files changed, 202 insertions, 131 deletions
diff --git a/src/screens/Login/PasswordUpdatedForm.tsx b/src/screens/Login/PasswordUpdatedForm.tsx index 9c12a47e3..b85815018 100644 --- a/src/screens/Login/PasswordUpdatedForm.tsx +++ b/src/screens/Login/PasswordUpdatedForm.tsx @@ -1,4 +1,3 @@ -import React from 'react' import {View} from 'react-native' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' diff --git a/src/screens/Login/SetNewPasswordForm.tsx b/src/screens/Login/SetNewPasswordForm.tsx index 9efbb96ce..4d09e32a3 100644 --- a/src/screens/Login/SetNewPasswordForm.tsx +++ b/src/screens/Login/SetNewPasswordForm.tsx @@ -1,4 +1,4 @@ -import React, {useState} from 'react' +import {useState} from 'react' import {ActivityIndicator, View} from 'react-native' import {BskyAgent} from '@atproto/api' import {msg, Trans} from '@lingui/macro' diff --git a/src/screens/Messages/ChatList.tsx b/src/screens/Messages/ChatList.tsx index 45b3bf14f..4f2bd251f 100644 --- a/src/screens/Messages/ChatList.tsx +++ b/src/screens/Messages/ChatList.tsx @@ -1,4 +1,4 @@ -import React, {useCallback, useEffect, useMemo, useState} from 'react' +import {useCallback, useEffect, useMemo, useState} from 'react' import {View} from 'react-native' import {ChatBskyConvoDefs} from '@atproto/api' import {msg, Trans} from '@lingui/macro' diff --git a/src/screens/Messages/Conversation.tsx b/src/screens/Messages/Conversation.tsx index e2e646a3d..ee09adaf0 100644 --- a/src/screens/Messages/Conversation.tsx +++ b/src/screens/Messages/Conversation.tsx @@ -4,10 +4,11 @@ import {useKeyboardController} from 'react-native-keyboard-controller' import {AppBskyActorDefs, moderateProfile, ModerationOpts} from '@atproto/api' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' -import {useFocusEffect} from '@react-navigation/native' +import {useFocusEffect, useNavigation} from '@react-navigation/native' import {NativeStackScreenProps} from '@react-navigation/native-stack' -import {CommonNavigatorParams} from '#/lib/routes/types' +import {useEmail} from '#/lib/hooks/useEmail' +import {CommonNavigatorParams, NavigationProp} from '#/lib/routes/types' import {isWeb} from '#/platform/detection' import {useProfileShadow} from '#/state/cache/profile-shadow' import {ConvoProvider, isConvoActive, useConvo} from '#/state/messages/convo' @@ -19,6 +20,8 @@ import {useSetMinimalShellMode} from '#/state/shell' import {CenteredView} from '#/view/com/util/Views' import {MessagesList} from '#/screens/Messages/components/MessagesList' import {atoms as a, useBreakpoints, useTheme, web} from '#/alf' +import {useDialogControl} from '#/components/Dialog' +import {VerifyEmailDialog} from '#/components/dialogs/VerifyEmailDialog' import {MessagesListBlockedFooter} from '#/components/dms/MessagesListBlockedFooter' import {MessagesListHeader} from '#/components/dms/MessagesListHeader' import {Error} from '#/components/Error' @@ -161,8 +164,12 @@ function InnerReady({ hasScrolled: boolean setHasScrolled: React.Dispatch<React.SetStateAction<boolean>> }) { + const {_} = useLingui() const convoState = useConvo() + const navigation = useNavigation<NavigationProp>() const recipient = useProfileShadow(recipientUnshadowed) + const verifyEmailControl = useDialogControl() + const {needsEmailVerification} = useEmail() const moderation = React.useMemo(() => { return moderateProfile(recipient, moderationOpts) @@ -179,6 +186,12 @@ function InnerReady({ } }, [moderation]) + React.useEffect(() => { + if (needsEmailVerification) { + verifyEmailControl.open() + } + }, [needsEmailVerification, verifyEmailControl]) + return ( <> <MessagesListHeader @@ -201,6 +214,15 @@ function InnerReady({ } /> )} + <VerifyEmailDialog + reasonText={_( + msg`Before you may message another user, you must first verify your email.`, + )} + control={verifyEmailControl} + onCloseWithoutVerifying={() => { + navigation.navigate('Home') + }} + /> </> ) } diff --git a/src/screens/Messages/Settings.tsx b/src/screens/Messages/Settings.tsx index 93e3bc400..50b1c4cc9 100644 --- a/src/screens/Messages/Settings.tsx +++ b/src/screens/Messages/Settings.tsx @@ -1,4 +1,4 @@ -import React, {useCallback} from 'react' +import {useCallback} from 'react' import {View} from 'react-native' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' diff --git a/src/screens/Messages/components/ChatDisabled.tsx b/src/screens/Messages/components/ChatDisabled.tsx index c768d2504..5e9f57fa5 100644 --- a/src/screens/Messages/components/ChatDisabled.tsx +++ b/src/screens/Messages/components/ChatDisabled.tsx @@ -1,4 +1,4 @@ -import React, {useCallback, useState} from 'react' +import {useCallback, useState} from 'react' import {View} from 'react-native' import {ComAtprotoModerationDefs} from '@atproto/api' import {msg, Trans} from '@lingui/macro' diff --git a/src/screens/Messages/components/MessageInput.tsx b/src/screens/Messages/components/MessageInput.tsx index 21d6e574e..85509211b 100644 --- a/src/screens/Messages/components/MessageInput.tsx +++ b/src/screens/Messages/components/MessageInput.tsx @@ -18,6 +18,7 @@ import Graphemer from 'graphemer' import {HITSLOP_10, MAX_DM_GRAPHEME_LENGTH} from '#/lib/constants' import {useHaptics} from '#/lib/haptics' +import {useEmail} from '#/lib/hooks/useEmail' import {isIOS} from '#/platform/detection' import { useMessageDraft, @@ -61,10 +62,15 @@ export function MessageInput({ const [message, setMessage] = React.useState(getDraft) const inputRef = useAnimatedRef<TextInput>() + const {needsEmailVerification} = useEmail() + useSaveMessageDraft(message) useExtractEmbedFromFacets(message, setEmbed) const onSubmit = React.useCallback(() => { + if (needsEmailVerification) { + return + } if (!hasEmbed && message.trim() === '') { return } @@ -84,6 +90,7 @@ export function MessageInput({ inputRef.current?.focus() }, 100) }, [ + needsEmailVerification, hasEmbed, message, clearDraft, @@ -101,22 +108,22 @@ export function MessageInput({ const measurement = measure(inputRef) if (!measurement) return - const max = windowHeight - -keyboardHeight.value - topInset - 150 + const max = windowHeight - -keyboardHeight.get() - topInset - 150 const availableSpace = max - measurement.height - maxHeight.value = max - isInputScrollable.value = availableSpace < 30 + maxHeight.set(max) + isInputScrollable.set(availableSpace < 30) }, }, [windowHeight, topInset], ) const animatedStyle = useAnimatedStyle(() => ({ - maxHeight: maxHeight.value, + maxHeight: maxHeight.get(), })) const animatedProps = useAnimatedProps(() => ({ - scrollEnabled: isInputScrollable.value, + scrollEnabled: isInputScrollable.get(), })) return ( @@ -159,6 +166,7 @@ export function MessageInput({ ref={inputRef} hitSlop={HITSLOP_10} animatedProps={animatedProps} + editable={!needsEmailVerification} /> <Pressable accessibilityRole="button" @@ -171,7 +179,8 @@ export function MessageInput({ a.justify_center, {height: 30, width: 30, backgroundColor: t.palette.primary_500}, ]} - onPress={onSubmit}> + onPress={onSubmit} + disabled={needsEmailVerification}> <PaperPlane fill={t.palette.white} style={[a.relative, {left: 1}]} /> </Pressable> </View> diff --git a/src/screens/Messages/components/MessageInput.web.tsx b/src/screens/Messages/components/MessageInput.web.tsx index b15cd2492..72e0382a9 100644 --- a/src/screens/Messages/components/MessageInput.web.tsx +++ b/src/screens/Messages/components/MessageInput.web.tsx @@ -38,7 +38,7 @@ export function MessageInput({ children?: React.ReactNode openEmojiPicker?: (pos: EmojiPickerPosition) => void }) { - const {isTabletOrDesktop} = useWebMediaQueries() + const {isMobile} = useWebMediaQueries() const {_} = useLingui() const t = useTheme() const {getDraft, clearDraft} = useMessageDraft() @@ -212,7 +212,7 @@ export function MessageInput({ onChange={onChange} // On mobile web phones, we want to keep the same behavior as the native app. Do not submit the message // in these cases. - onKeyDown={isTouchDevice && isTabletOrDesktop ? undefined : onKeyDown} + onKeyDown={isTouchDevice && isMobile ? undefined : onKeyDown} /> <Pressable accessibilityRole="button" diff --git a/src/screens/Messages/components/MessageInputEmbed.tsx b/src/screens/Messages/components/MessageInputEmbed.tsx index 2d1551019..6df0ef2fc 100644 --- a/src/screens/Messages/components/MessageInputEmbed.tsx +++ b/src/screens/Messages/components/MessageInputEmbed.tsx @@ -1,4 +1,4 @@ -import React, {useCallback, useEffect, useMemo, useState} from 'react' +import {useCallback, useEffect, useMemo, useState} from 'react' import {LayoutAnimation, View} from 'react-native' import { AppBskyFeedPost, diff --git a/src/screens/Messages/components/MessagesList.tsx b/src/screens/Messages/components/MessagesList.tsx index 9db4f07b6..9f67929a3 100644 --- a/src/screens/Messages/components/MessagesList.tsx +++ b/src/screens/Messages/components/MessagesList.tsx @@ -145,7 +145,7 @@ export function MessagesList({ (_: number, height: number) => { // Because web does not have `maintainVisibleContentPosition` support, we will need to manually scroll to the // previous off whenever we add new content to the previous offset whenever we add new content to the list. - if (isWeb && isAtTop.value && hasScrolled) { + if (isWeb && isAtTop.get() && hasScrolled) { flatListRef.current?.scrollToOffset({ offset: height - prevContentHeight.current, animated: false, @@ -153,7 +153,7 @@ export function MessagesList({ } // This number _must_ be the height of the MaybeLoader component - if (height > 50 && isAtBottom.value) { + if (height > 50 && isAtBottom.get()) { // If the size of the content is changing by more than the height of the screen, then we don't // want to scroll further than the start of all the new content. Since we are storing the previous offset, // we can just scroll the user to that offset and add a little bit of padding. We'll also show the pill @@ -161,7 +161,7 @@ export function MessagesList({ if ( didBackground.current && hasScrolled && - height - prevContentHeight.current > layoutHeight.value - 50 && + height - prevContentHeight.current > layoutHeight.get() - 50 && convoState.items.length - prevItemCount.current > 1 ) { flatListRef.current?.scrollToOffset({ @@ -209,7 +209,7 @@ export function MessagesList({ ) const onStartReached = useCallback(() => { - if (hasScrolled && prevContentHeight.current > layoutHeight.value) { + if (hasScrolled && prevContentHeight.current > layoutHeight.get()) { convoState.fetchMessageHistory() } }, [convoState, hasScrolled, layoutHeight]) @@ -217,18 +217,18 @@ export function MessagesList({ const onScroll = React.useCallback( (e: ReanimatedScrollEvent) => { 'worklet' - layoutHeight.value = e.layoutMeasurement.height + layoutHeight.set(e.layoutMeasurement.height) const bottomOffset = e.contentOffset.y + e.layoutMeasurement.height // Most apps have a little bit of space the user can scroll past while still automatically scrolling ot the bottom // when a new message is added, hence the 100 pixel offset - isAtBottom.value = e.contentSize.height - 100 < bottomOffset - isAtTop.value = e.contentOffset.y <= 1 + isAtBottom.set(e.contentSize.height - 100 < bottomOffset) + isAtTop.set(e.contentOffset.y <= 1) if ( newMessagesPill.show && (e.contentOffset.y > newMessagesPill.startContentOffset + 200 || - isAtBottom.value) + isAtBottom.get()) ) { runOnJS(setNewMessagesPill)({ show: false, @@ -256,28 +256,28 @@ export function MessagesList({ // Immediate updates - like opening the emoji picker - will have a duration of zero. In those cases, we should // just update the height here instead of having the `onMove` event do it (that event will not fire!) if (e.duration === 0) { - layoutScrollWithoutAnimation.value = true - keyboardHeight.value = e.height + layoutScrollWithoutAnimation.set(true) + keyboardHeight.set(e.height) } else { - keyboardIsOpening.value = true + keyboardIsOpening.set(true) } }, onMove: e => { 'worklet' - keyboardHeight.value = e.height + keyboardHeight.set(e.height) if (e.height > bottomOffset) { scrollTo(flatListRef, 0, 1e7, false) } }, onEnd: () => { 'worklet' - keyboardIsOpening.value = false + keyboardIsOpening.set(false) }, }) const animatedListStyle = useAnimatedStyle(() => ({ marginBottom: - keyboardHeight.value > bottomOffset ? keyboardHeight.value : bottomOffset, + keyboardHeight.get() > bottomOffset ? keyboardHeight.get() : bottomOffset, })) // -- Message sending @@ -363,13 +363,13 @@ export function MessagesList({ // -- List layout changes (opening emoji keyboard, etc.) const onListLayout = React.useCallback( (e: LayoutChangeEvent) => { - layoutHeight.value = e.nativeEvent.layout.height + layoutHeight.set(e.nativeEvent.layout.height) - if (isWeb || !keyboardIsOpening.value) { + if (isWeb || !keyboardIsOpening.get()) { flatListRef.current?.scrollToEnd({ - animated: !layoutScrollWithoutAnimation.value, + animated: !layoutScrollWithoutAnimation.get(), }) - layoutScrollWithoutAnimation.value = false + layoutScrollWithoutAnimation.set(false) } }, [ diff --git a/src/screens/Onboarding/Layout.tsx b/src/screens/Onboarding/Layout.tsx index 02b207303..4a07ebd83 100644 --- a/src/screens/Onboarding/Layout.tsx +++ b/src/screens/Onboarding/Layout.tsx @@ -17,10 +17,11 @@ import { useTheme, web, } from '#/alf' +import {leading} from '#/alf/typography' import {Button, ButtonIcon, ButtonText} from '#/components/Button' import {ChevronLeft_Stroke2_Corner0_Rounded as ChevronLeft} from '#/components/icons/Chevron' import {createPortalGroup} from '#/components/Portal' -import {leading, P, Text} from '#/components/Typography' +import {P, Text} from '#/components/Typography' import {IS_DEV} from '#/env' const COL_WIDTH = 420 diff --git a/src/screens/Onboarding/StepFinished.tsx b/src/screens/Onboarding/StepFinished.tsx index fdc0a3eb7..0d8971b6f 100644 --- a/src/screens/Onboarding/StepFinished.tsx +++ b/src/screens/Onboarding/StepFinished.tsx @@ -127,31 +127,36 @@ export function StepFinished() { })(), (async () => { const {imageUri, imageMime} = profileStepResults - if (imageUri && imageMime) { - const blobPromise = uploadBlob(agent, imageUri, imageMime) - await agent.upsertProfile(async existing => { - existing = existing ?? {} + const blobPromise = + imageUri && imageMime + ? uploadBlob(agent, imageUri, imageMime) + : undefined + + await agent.upsertProfile(async existing => { + existing = existing ?? {} + + if (blobPromise) { const res = await blobPromise if (res.data.blob) { existing.avatar = res.data.blob } + } - if (starterPack) { - existing.joinedViaStarterPack = { - uri: starterPack.uri, - cid: starterPack.cid, - } + if (starterPack) { + existing.joinedViaStarterPack = { + uri: starterPack.uri, + cid: starterPack.cid, } + } - existing.displayName = '' - // HACKFIX - // creating a bunch of identical profile objects is breaking the relay - // tossing this unspecced field onto it to reduce the size of the problem - // -prf - existing.createdAt = new Date().toISOString() - return existing - }) - } + existing.displayName = '' + // HACKFIX + // creating a bunch of identical profile objects is breaking the relay + // tossing this unspecced field onto it to reduce the size of the problem + // -prf + existing.createdAt = new Date().toISOString() + return existing + }) logEvent('onboarding:finished:avatarResult', { avatarResult: profileStepResults.isCreatedAvatar diff --git a/src/screens/Profile/Header/DisplayName.tsx b/src/screens/Profile/Header/DisplayName.tsx index 6d4eea2eb..b74c063d4 100644 --- a/src/screens/Profile/Header/DisplayName.tsx +++ b/src/screens/Profile/Header/DisplayName.tsx @@ -1,4 +1,3 @@ -import React from 'react' import {View} from 'react-native' import {AppBskyActorDefs, ModerationDecision} from '@atproto/api' diff --git a/src/screens/Profile/Header/EditProfileDialog.tsx b/src/screens/Profile/Header/EditProfileDialog.tsx index af4b5498a..952184816 100644 --- a/src/screens/Profile/Header/EditProfileDialog.tsx +++ b/src/screens/Profile/Header/EditProfileDialog.tsx @@ -1,4 +1,4 @@ -import React, {useCallback, useEffect, useState} from 'react' +import {useCallback, useEffect, useState} from 'react' import {Dimensions, View} from 'react-native' import {Image as RNImage} from 'react-native-image-crop-picker' import {AppBskyActorDefs} from '@atproto/api' diff --git a/src/screens/Profile/Header/GrowableAvatar.tsx b/src/screens/Profile/Header/GrowableAvatar.tsx index 20ac14892..dab69f955 100644 --- a/src/screens/Profile/Header/GrowableAvatar.tsx +++ b/src/screens/Profile/Header/GrowableAvatar.tsx @@ -45,7 +45,7 @@ function GrowableAvatarInner({ const animatedStyle = useAnimatedStyle(() => ({ transform: [ { - scale: interpolate(scrollY.value, [-150, 0], [1.2, 1], { + scale: interpolate(scrollY.get(), [-150, 0], [1.2, 1], { extrapolateRight: Extrapolation.CLAMP, }), }, diff --git a/src/screens/Profile/Header/GrowableBanner.tsx b/src/screens/Profile/Header/GrowableBanner.tsx index 144b7cd2d..7f5a3cd6e 100644 --- a/src/screens/Profile/Header/GrowableBanner.tsx +++ b/src/screens/Profile/Header/GrowableBanner.tsx @@ -66,7 +66,7 @@ function GrowableBannerInner({ const animatedStyle = useAnimatedStyle(() => ({ transform: [ { - scale: interpolate(scrollY.value, [-150, 0], [2, 1], { + scale: interpolate(scrollY.get(), [-150, 0], [2, 1], { extrapolateRight: Extrapolation.CLAMP, }), }, @@ -76,7 +76,7 @@ function GrowableBannerInner({ const animatedBlurViewProps = useAnimatedProps(() => { return { intensity: interpolate( - scrollY.value, + scrollY.get(), [-300, -65, -15], [50, 40, 0], Extrapolation.CLAMP, @@ -85,16 +85,17 @@ function GrowableBannerInner({ }) const animatedSpinnerStyle = useAnimatedStyle(() => { + const scrollYValue = scrollY.get() return { - display: scrollY.value < 0 ? 'flex' : 'none', + display: scrollYValue < 0 ? 'flex' : 'none', opacity: interpolate( - scrollY.value, + scrollYValue, [-60, -15], [1, 0], Extrapolation.CLAMP, ), transform: [ - {translateY: interpolate(scrollY.value, [-150, 0], [-75, 0])}, + {translateY: interpolate(scrollYValue, [-150, 0], [-75, 0])}, {rotate: '90deg'}, ], } @@ -103,7 +104,7 @@ function GrowableBannerInner({ const animatedBackButtonStyle = useAnimatedStyle(() => ({ transform: [ { - translateY: interpolate(scrollY.value, [-150, 60], [-150, 60], { + translateY: interpolate(scrollY.get(), [-150, 60], [-150, 60], { extrapolateRight: Extrapolation.CLAMP, }), }, @@ -168,7 +169,7 @@ function useShouldAnimateSpinner({ const stickyIsOverscrolled = useStickyToggle(isOverscrolled, 10) useAnimatedReaction( - () => scrollY.value < -5, + () => scrollY.get() < -5, (value, prevValue) => { if (value !== prevValue) { runOnJS(setIsOverscrolled)(value) diff --git a/src/screens/Profile/Header/Handle.tsx b/src/screens/Profile/Header/Handle.tsx index acfba3fb2..27b73da70 100644 --- a/src/screens/Profile/Header/Handle.tsx +++ b/src/screens/Profile/Header/Handle.tsx @@ -1,4 +1,3 @@ -import React from 'react' import {View} from 'react-native' import {AppBskyActorDefs} from '@atproto/api' import {msg, Trans} from '@lingui/macro' @@ -24,7 +23,7 @@ export function ProfileHeaderHandle({ const blockHide = profile.viewer?.blocking || profile.viewer?.blockedBy return ( <View - style={[a.flex_row, a.gap_xs, a.align_center]} + style={[a.flex_row, a.gap_xs, a.align_center, {maxWidth: '100%'}]} pointerEvents={disableTaps ? 'none' : isIOS ? 'auto' : 'box-none'}> <NewskieDialog profile={profile} disabled={disableTaps} /> {profile.viewer?.followedBy && !blockHide ? ( diff --git a/src/screens/Profile/Header/Metrics.tsx b/src/screens/Profile/Header/Metrics.tsx index 30686ef99..bd4c4521c 100644 --- a/src/screens/Profile/Header/Metrics.tsx +++ b/src/screens/Profile/Header/Metrics.tsx @@ -1,4 +1,3 @@ -import React from 'react' import {View} from 'react-native' import {AppBskyActorDefs} from '@atproto/api' import {msg, plural} from '@lingui/macro' diff --git a/src/screens/Profile/Header/Shell.tsx b/src/screens/Profile/Header/Shell.tsx index 1a1e7d4a2..573d38145 100644 --- a/src/screens/Profile/Header/Shell.tsx +++ b/src/screens/Profile/Header/Shell.tsx @@ -1,12 +1,6 @@ import React, {memo} from 'react' import {StyleSheet, TouchableWithoutFeedback, View} from 'react-native' -import Animated, { - measure, - MeasuredDimensions, - runOnJS, - runOnUI, - useAnimatedRef, -} from 'react-native-reanimated' +import {MeasuredDimensions, runOnJS, runOnUI} from 'react-native-reanimated' import {AppBskyActorDefs, ModerationDecision} from '@atproto/api' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {msg} from '@lingui/macro' @@ -14,6 +8,7 @@ import {useLingui} from '@lingui/react' import {useNavigation} from '@react-navigation/native' import {BACK_HITSLOP} from '#/lib/constants' +import {measureHandle, useHandleRef} from '#/lib/hooks/useHandleRef' import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' import {NavigationProp} from '#/lib/routes/types' import {isIOS} from '#/platform/detection' @@ -49,7 +44,7 @@ let ProfileHeaderShell = ({ const {openLightbox} = useLightboxControls() const navigation = useNavigation<NavigationProp>() const {isDesktop} = useWebMediaQueries() - const aviRef = useAnimatedRef() + const aviRef = useHandleRef() const onPressBack = React.useCallback(() => { if (navigation.canGoBack()) { @@ -86,9 +81,10 @@ let ProfileHeaderShell = ({ const modui = moderation.ui('avatar') const avatar = profile.avatar if (avatar && !(modui.blur && modui.noOverride)) { + const aviHandle = aviRef.current runOnUI(() => { 'worklet' - const rect = measure(aviRef) + const rect = measureHandle(aviHandle) runOnJS(_openLightbox)(avatar, rect) })() } @@ -170,14 +166,14 @@ let ProfileHeaderShell = ({ styles.avi, profile.associated?.labeler && styles.aviLabeler, ]}> - <Animated.View ref={aviRef} collapsable={false}> + <View ref={aviRef} collapsable={false}> <UserAvatar type={profile.associated?.labeler ? 'labeler' : 'user'} size={90} avatar={profile.avatar} moderation={moderation.ui('avatar')} /> - </Animated.View> + </View> </View> </TouchableWithoutFeedback> </GrowableAvatar> diff --git a/src/screens/Settings/AboutSettings.tsx b/src/screens/Settings/AboutSettings.tsx index 3c445b966..8019a20f9 100644 --- a/src/screens/Settings/AboutSettings.tsx +++ b/src/screens/Settings/AboutSettings.tsx @@ -1,4 +1,3 @@ -import React from 'react' import {Platform} from 'react-native' import {setStringAsync} from 'expo-clipboard' import {msg, Trans} from '@lingui/macro' diff --git a/src/screens/Settings/AccessibilitySettings.tsx b/src/screens/Settings/AccessibilitySettings.tsx index dfe2c14a5..6ab0131d9 100644 --- a/src/screens/Settings/AccessibilitySettings.tsx +++ b/src/screens/Settings/AccessibilitySettings.tsx @@ -1,4 +1,3 @@ -import React from 'react' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {NativeStackScreenProps} from '@react-navigation/native-stack' diff --git a/src/screens/Settings/AccountSettings.tsx b/src/screens/Settings/AccountSettings.tsx index 35c5f3aa0..2495a0f2f 100644 --- a/src/screens/Settings/AccountSettings.tsx +++ b/src/screens/Settings/AccountSettings.tsx @@ -1,4 +1,3 @@ -import React from 'react' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {NativeStackScreenProps} from '@react-navigation/native-stack' diff --git a/src/screens/Settings/AppPasswords.tsx b/src/screens/Settings/AppPasswords.tsx index 8cebf97ce..1ea0bd1b3 100644 --- a/src/screens/Settings/AppPasswords.tsx +++ b/src/screens/Settings/AppPasswords.tsx @@ -1,4 +1,4 @@ -import React, {useCallback} from 'react' +import {useCallback} from 'react' import {View} from 'react-native' import Animated, { FadeIn, diff --git a/src/screens/Settings/ContentAndMediaSettings.tsx b/src/screens/Settings/ContentAndMediaSettings.tsx index 6d293fbea..27448ba9a 100644 --- a/src/screens/Settings/ContentAndMediaSettings.tsx +++ b/src/screens/Settings/ContentAndMediaSettings.tsx @@ -1,4 +1,3 @@ -import React from 'react' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {NativeStackScreenProps} from '@react-navigation/native-stack' @@ -10,9 +9,16 @@ import { useInAppBrowser, useSetInAppBrowser, } from '#/state/preferences/in-app-browser' +import { + useOptOutOfUtm, + useSetOptOutOfUtm, +} from '#/state/preferences/opt-out-of-utm' import * as SettingsList from '#/screens/Settings/components/SettingsList' +import {atoms as a} from '#/alf' +import {Admonition} from '#/components/Admonition' import * as Toggle from '#/components/forms/Toggle' import {Bubbles_Stroke2_Corner2_Rounded as BubblesIcon} from '#/components/icons/Bubble' +import {ChainLink3_Stroke2_Corner0_Rounded as ChainLinkIcon} from '#/components/icons/ChainLink' import {Hashtag_Stroke2_Corner0_Rounded as HashtagIcon} from '#/components/icons/Hashtag' import {Home_Stroke2_Corner2_Rounded as HomeIcon} from '#/components/icons/Home' import {Macintosh_Stroke2_Corner2_Rounded as MacintoshIcon} from '#/components/icons/Macintosh' @@ -30,6 +36,8 @@ export function ContentAndMediaSettingsScreen({}: Props) { const setAutoplayDisabledPref = useSetAutoplayDisabled() const inAppBrowserPref = useInAppBrowser() const setUseInAppBrowser = useSetInAppBrowser() + const optOutOfUtm = useOptOutOfUtm() + const setOptOutOfUtm = useSetOptOutOfUtm() return ( <Layout.Screen> @@ -69,6 +77,19 @@ export function ContentAndMediaSettingsScreen({}: Props) { </SettingsList.ItemText> </SettingsList.LinkItem> <SettingsList.Divider /> + <Toggle.Item + name="disable_autoplay" + label={_(msg`Autoplay videos and GIFs`)} + value={!autoplayDisabledPref} + onChange={value => setAutoplayDisabledPref(!value)}> + <SettingsList.Item> + <SettingsList.ItemIcon icon={PlayIcon} /> + <SettingsList.ItemText> + <Trans>Autoplay videos and GIFs</Trans> + </SettingsList.ItemText> + <Toggle.Platform /> + </SettingsList.Item> + </Toggle.Item> {isNative && ( <Toggle.Item name="use_in_app_browser" @@ -84,19 +105,31 @@ export function ContentAndMediaSettingsScreen({}: Props) { </SettingsList.Item> </Toggle.Item> )} - <Toggle.Item - name="disable_autoplay" - label={_(msg`Autoplay videos and GIFs`)} - value={!autoplayDisabledPref} - onChange={value => setAutoplayDisabledPref(!value)}> + {isNative && <SettingsList.Divider />} + {isNative && ( + <Toggle.Item + name="allow_utm" + label={_(msg`Specify Bluesky as a referer`)} + value={!(optOutOfUtm ?? false)} + onChange={value => setOptOutOfUtm(!value)}> + <SettingsList.Item> + <SettingsList.ItemIcon icon={ChainLinkIcon} /> + <SettingsList.ItemText> + <Trans>Send Bluesky referrer</Trans> + </SettingsList.ItemText> + <Toggle.Platform /> + </SettingsList.Item> + </Toggle.Item> + )} + {isNative && ( <SettingsList.Item> - <SettingsList.ItemIcon icon={PlayIcon} /> - <SettingsList.ItemText> - <Trans>Autoplay videos and GIFs</Trans> - </SettingsList.ItemText> - <Toggle.Platform /> + <Admonition type="info" style={[a.flex_1]}> + <Trans> + Helps external sites estimate traffic from Bluesky. + </Trans> + </Admonition> </SettingsList.Item> - </Toggle.Item> + )} </SettingsList.Container> </Layout.Content> </Layout.Screen> diff --git a/src/screens/Settings/ExternalMediaPreferences.tsx b/src/screens/Settings/ExternalMediaPreferences.tsx index 91c7ea7fc..f7e081429 100644 --- a/src/screens/Settings/ExternalMediaPreferences.tsx +++ b/src/screens/Settings/ExternalMediaPreferences.tsx @@ -1,4 +1,4 @@ -import React, {Fragment} from 'react' +import {Fragment} from 'react' import {View} from 'react-native' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' diff --git a/src/screens/Settings/FollowingFeedPreferences.tsx b/src/screens/Settings/FollowingFeedPreferences.tsx index 12de2a31a..089491dd0 100644 --- a/src/screens/Settings/FollowingFeedPreferences.tsx +++ b/src/screens/Settings/FollowingFeedPreferences.tsx @@ -1,4 +1,3 @@ -import React from 'react' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' diff --git a/src/screens/Settings/LanguageSettings.tsx b/src/screens/Settings/LanguageSettings.tsx index c6cd8bb5a..acad0520c 100644 --- a/src/screens/Settings/LanguageSettings.tsx +++ b/src/screens/Settings/LanguageSettings.tsx @@ -1,4 +1,4 @@ -import React, {useCallback, useMemo} from 'react' +import {useCallback, useMemo} from 'react' import {View} from 'react-native' import RNPickerSelect, {PickerSelectProps} from 'react-native-picker-select' import {msg, Trans} from '@lingui/macro' diff --git a/src/screens/Settings/NotificationSettings.tsx b/src/screens/Settings/NotificationSettings.tsx index 8e1033742..c5f7078c4 100644 --- a/src/screens/Settings/NotificationSettings.tsx +++ b/src/screens/Settings/NotificationSettings.tsx @@ -1,4 +1,3 @@ -import React from 'react' import {Text} from 'react-native' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' diff --git a/src/screens/Settings/PrivacyAndSecuritySettings.tsx b/src/screens/Settings/PrivacyAndSecuritySettings.tsx index 12e28e0d2..d695f830d 100644 --- a/src/screens/Settings/PrivacyAndSecuritySettings.tsx +++ b/src/screens/Settings/PrivacyAndSecuritySettings.tsx @@ -1,4 +1,3 @@ -import React from 'react' import {View} from 'react-native' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' diff --git a/src/screens/Settings/Settings.tsx b/src/screens/Settings/Settings.tsx index adea42e5e..126a1bc88 100644 --- a/src/screens/Settings/Settings.tsx +++ b/src/screens/Settings/Settings.tsx @@ -1,4 +1,4 @@ -import React, {useState} from 'react' +import {useState} from 'react' import {LayoutAnimation, Pressable, View} from 'react-native' import {Linking} from 'react-native' import {useReducedMotion} from 'react-native-reanimated' diff --git a/src/screens/Settings/ThreadPreferences.tsx b/src/screens/Settings/ThreadPreferences.tsx index 24dd91bf6..d29daa58b 100644 --- a/src/screens/Settings/ThreadPreferences.tsx +++ b/src/screens/Settings/ThreadPreferences.tsx @@ -1,4 +1,3 @@ -import React from 'react' import {View} from 'react-native' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' @@ -57,6 +56,12 @@ export function ThreadPreferencesScreen({}: Props) { values={sortReplies ? [sortReplies] : []} onChange={values => setThreadViewPrefs({sort: values[0]})}> <View style={[a.gap_sm, a.flex_1]}> + <Toggle.Item name="hotness" label={_(msg`Hot replies first`)}> + <Toggle.Radio /> + <Toggle.LabelText> + <Trans>Hot replies first</Trans> + </Toggle.LabelText> + </Toggle.Item> <Toggle.Item name="oldest" label={_(msg`Oldest replies first`)}> diff --git a/src/screens/Settings/components/AddAppPasswordDialog.tsx b/src/screens/Settings/components/AddAppPasswordDialog.tsx index dcb212879..927875778 100644 --- a/src/screens/Settings/components/AddAppPasswordDialog.tsx +++ b/src/screens/Settings/components/AddAppPasswordDialog.tsx @@ -1,4 +1,4 @@ -import React, {useEffect, useMemo, useState} from 'react' +import {useEffect, useMemo, useState} from 'react' import {useWindowDimensions, View} from 'react-native' import Animated, { FadeIn, diff --git a/src/screens/Settings/components/ChangeHandleDialog.tsx b/src/screens/Settings/components/ChangeHandleDialog.tsx index e76d6257f..9b2c4f7c3 100644 --- a/src/screens/Settings/components/ChangeHandleDialog.tsx +++ b/src/screens/Settings/components/ChangeHandleDialog.tsx @@ -1,4 +1,4 @@ -import React, {useCallback, useMemo, useState} from 'react' +import {useCallback, useMemo, useState} from 'react' import {useWindowDimensions, View} from 'react-native' import Animated, { FadeIn, @@ -86,7 +86,6 @@ function ChangeHandleDialogInner() { return ( <Dialog.ScrollableInner label={_(msg`Change Handle`)} - style={[a.overflow_hidden]} header={ <Dialog.Header renderLeft={cancelButton}> <Dialog.HeaderText> diff --git a/src/screens/Settings/components/CopyButton.tsx b/src/screens/Settings/components/CopyButton.tsx index 8c6cdfa8a..82c11f58d 100644 --- a/src/screens/Settings/components/CopyButton.tsx +++ b/src/screens/Settings/components/CopyButton.tsx @@ -1,4 +1,4 @@ -import React, {useCallback, useEffect, useState} from 'react' +import {useCallback, useEffect, useState} from 'react' import {GestureResponderEvent, View} from 'react-native' import Animated, { FadeOutUp, diff --git a/src/screens/Settings/components/DisableEmail2FADialog.tsx b/src/screens/Settings/components/DisableEmail2FADialog.tsx index 1378759b0..8ee49c989 100644 --- a/src/screens/Settings/components/DisableEmail2FADialog.tsx +++ b/src/screens/Settings/components/DisableEmail2FADialog.tsx @@ -1,4 +1,4 @@ -import React, {useState} from 'react' +import {useState} from 'react' import {View} from 'react-native' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' diff --git a/src/screens/Settings/components/SettingsList.tsx b/src/screens/Settings/components/SettingsList.tsx index 07117aef3..520df4118 100644 --- a/src/screens/Settings/components/SettingsList.tsx +++ b/src/screens/Settings/components/SettingsList.tsx @@ -213,7 +213,6 @@ export function ItemIcon({ } export function ItemText({ - // eslint-disable-next-line react/prop-types style, ...props }: React.ComponentProps<typeof Button.ButtonText>) { diff --git a/src/screens/Signup/BackNextButtons.tsx b/src/screens/Signup/BackNextButtons.tsx index e2401bb11..888b9071e 100644 --- a/src/screens/Signup/BackNextButtons.tsx +++ b/src/screens/Signup/BackNextButtons.tsx @@ -1,4 +1,3 @@ -import React from 'react' import {View} from 'react-native' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' diff --git a/src/screens/Signup/StepInfo/Policies.tsx b/src/screens/Signup/StepInfo/Policies.tsx index ba0cfc2b4..3cde95be7 100644 --- a/src/screens/Signup/StepInfo/Policies.tsx +++ b/src/screens/Signup/StepInfo/Policies.tsx @@ -1,4 +1,4 @@ -import React, {ReactElement} from 'react' +import {ReactElement} from 'react' import {View} from 'react-native' import {ComAtprotoServerDescribeServer} from '@atproto/api' import {msg, Trans} from '@lingui/macro' diff --git a/src/screens/SignupQueued.tsx b/src/screens/SignupQueued.tsx index e7336569c..ed261f29e 100644 --- a/src/screens/SignupQueued.tsx +++ b/src/screens/SignupQueued.tsx @@ -1,16 +1,17 @@ import React from 'react' -import {View} from 'react-native' +import {Modal, View} from 'react-native' import {useSafeAreaInsets} from 'react-native-safe-area-context' +import {StatusBar} from 'expo-status-bar' import {msg, plural, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {logger} from '#/logger' -import {isWeb} from '#/platform/detection' +import {isIOS, isWeb} from '#/platform/detection' import {isSignupQueued, useAgent, useSessionApi} from '#/state/session' import {useOnboardingDispatch} from '#/state/shell' import {ScrollView} from '#/view/com/util/Views' import {Logo} from '#/view/icons/Logo' -import {atoms as a, useBreakpoints, useTheme} from '#/alf' +import {atoms as a, native, useBreakpoints, useTheme, web} from '#/alf' import {Button, ButtonIcon, ButtonText} from '#/components/Button' import {Loader} from '#/components/Loader' import {P, Text} from '#/components/Typography' @@ -86,19 +87,22 @@ export function SignupQueued() { ) return ( - <View - aria-modal - role="dialog" - aria-role="dialog" - aria-label={_(msg`You're in line`)} - accessibilityLabel={_(msg`You're in line`)} - accessibilityHint="" - style={[a.absolute, a.inset_0, a.flex_1, t.atoms.bg]}> + <Modal + visible + animationType={native('slide')} + presentationStyle="formSheet" + style={[web(a.util_screen_outer)]}> + {isIOS && <StatusBar style="light" />} <ScrollView - style={[a.h_full, a.w_full]} - contentContainerStyle={{borderWidth: 0}}> + style={[a.flex_1, t.atoms.bg]} + contentContainerStyle={{borderWidth: 0}} + bounces={false}> <View - style={[a.flex_row, a.justify_center, gtMobile ? a.pt_4xl : a.px_xl]}> + style={[ + a.flex_row, + a.justify_center, + gtMobile ? a.pt_4xl : [a.px_xl, a.pt_xl], + ]}> <View style={[a.flex_1, {maxWidth: COL_WIDTH}]}> <View style={[a.w_full, a.justify_center, a.align_center, a.my_4xl]}> @@ -121,11 +125,14 @@ export function SignupQueued() { a.px_2xl, a.py_4xl, a.mt_2xl, - t.atoms.bg_contrast_50, + a.mb_md, + a.border, + t.atoms.bg_contrast_25, + t.atoms.border_contrast_medium, ]}> {typeof placeInQueue === 'number' && ( <Text - style={[a.text_5xl, a.text_center, a.font_bold, a.mb_2xl]}> + style={[a.text_5xl, a.text_center, a.font_heavy, a.mb_2xl]}> {placeInQueue} </Text> )} @@ -148,7 +155,14 @@ export function SignupQueued() { </View> {isWeb && gtMobile && ( - <View style={[a.w_full, a.flex_row, a.justify_between, a.pt_5xl]}> + <View + style={[ + a.w_full, + a.flex_row, + a.justify_between, + a.pt_5xl, + {paddingBottom: 200}, + ]}> <Button variant="ghost" size="large" @@ -162,8 +176,6 @@ export function SignupQueued() { </View> )} </View> - - <View style={{height: 200}} /> </View> </ScrollView> @@ -171,6 +183,7 @@ export function SignupQueued() { <View style={[ a.align_center, + t.atoms.bg, gtMobile ? a.px_5xl : a.px_xl, { paddingBottom: Math.max(insets.bottom, a.pb_5xl.paddingBottom), @@ -190,7 +203,7 @@ export function SignupQueued() { </View> </View> )} - </View> + </Modal> ) } diff --git a/src/screens/StarterPack/Wizard/StepDetails.tsx b/src/screens/StarterPack/Wizard/StepDetails.tsx index 4ee2cada9..49de979ee 100644 --- a/src/screens/StarterPack/Wizard/StepDetails.tsx +++ b/src/screens/StarterPack/Wizard/StepDetails.tsx @@ -1,4 +1,3 @@ -import React from 'react' import {View} from 'react-native' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' diff --git a/src/screens/StarterPack/Wizard/StepFeeds.tsx b/src/screens/StarterPack/Wizard/StepFeeds.tsx index 0cf6ab231..b776e9551 100644 --- a/src/screens/StarterPack/Wizard/StepFeeds.tsx +++ b/src/screens/StarterPack/Wizard/StepFeeds.tsx @@ -1,4 +1,4 @@ -import React, {useState} from 'react' +import {useState} from 'react' import {ListRenderItemInfo, View} from 'react-native' import {KeyboardAwareScrollView} from 'react-native-keyboard-controller' import {AppBskyFeedDefs, ModerationOpts} from '@atproto/api' diff --git a/src/screens/StarterPack/Wizard/StepProfiles.tsx b/src/screens/StarterPack/Wizard/StepProfiles.tsx index 054a6a63e..68f0edb90 100644 --- a/src/screens/StarterPack/Wizard/StepProfiles.tsx +++ b/src/screens/StarterPack/Wizard/StepProfiles.tsx @@ -1,4 +1,4 @@ -import React, {useState} from 'react' +import {useState} from 'react' import {ListRenderItemInfo, View} from 'react-native' import {KeyboardAwareScrollView} from 'react-native-keyboard-controller' import {AppBskyActorDefs, ModerationOpts} from '@atproto/api' |