Merge branch 'main' into main
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
import React from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {moderateProfile} from '@atproto/api'
|
||||
|
||||
|
||||
@@ -458,7 +458,6 @@ export const Button = React.forwardRef<View, ButtonProps>(
|
||||
// @ts-ignore - this will always be a pressable
|
||||
ref={ref}
|
||||
aria-label={label}
|
||||
aria-pressed={state.pressed}
|
||||
accessibilityLabel={label}
|
||||
disabled={disabled || false}
|
||||
accessibilityState={{
|
||||
|
||||
@@ -75,7 +75,7 @@ export function Outer({
|
||||
try {
|
||||
cb()
|
||||
} catch (e: any) {
|
||||
logger.error('Error running close callback', e)
|
||||
logger.error(e || 'Error running close callback')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
View,
|
||||
ViewStyle,
|
||||
} from 'react-native'
|
||||
import Animated, {FadeIn, FadeInDown} from 'react-native-reanimated'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {DismissableLayer} from '@radix-ui/react-dismissable-layer'
|
||||
@@ -42,7 +41,6 @@ export function Outer({
|
||||
onClose,
|
||||
}: React.PropsWithChildren<DialogOuterProps>) {
|
||||
const {_} = useLingui()
|
||||
const t = useTheme()
|
||||
const {gtMobile} = useBreakpoints()
|
||||
const [isOpen, setIsOpen] = React.useState(false)
|
||||
const {setDialogIsOpen} = useDialogStateControlContext()
|
||||
@@ -118,16 +116,7 @@ export function Outer({
|
||||
gtMobile ? a.p_lg : a.p_md,
|
||||
{overflowY: 'auto'},
|
||||
]}>
|
||||
<Animated.View
|
||||
entering={FadeIn.duration(150)}
|
||||
// exiting={FadeOut.duration(150)}
|
||||
style={[
|
||||
web(a.fixed),
|
||||
a.inset_0,
|
||||
{opacity: 0.8, backgroundColor: t.palette.black},
|
||||
]}
|
||||
/>
|
||||
|
||||
<Backdrop />
|
||||
<View
|
||||
style={[
|
||||
a.w_full,
|
||||
@@ -164,7 +153,7 @@ export function Inner({
|
||||
useFocusGuards()
|
||||
return (
|
||||
<FocusScope loop asChild trapped>
|
||||
<Animated.View
|
||||
<View
|
||||
role="dialog"
|
||||
aria-role="dialog"
|
||||
aria-label={label}
|
||||
@@ -174,8 +163,6 @@ export function Inner({
|
||||
onClick={stopPropagation}
|
||||
onStartShouldSetResponder={_ => true}
|
||||
onTouchEnd={stopPropagation}
|
||||
entering={FadeInDown.duration(100)}
|
||||
// exiting={FadeOut.duration(100)}
|
||||
style={flatten([
|
||||
a.relative,
|
||||
a.rounded_md,
|
||||
@@ -188,6 +175,8 @@ export function Inner({
|
||||
shadowColor: t.palette.black,
|
||||
shadowOpacity: t.name === 'light' ? 0.1 : 0.4,
|
||||
shadowRadius: 30,
|
||||
// @ts-ignore web only
|
||||
animation: 'fadeIn ease-out 0.1s',
|
||||
},
|
||||
flatten(style),
|
||||
])}>
|
||||
@@ -201,7 +190,7 @@ export function Inner({
|
||||
{children}
|
||||
</View>
|
||||
</DismissableLayer>
|
||||
</Animated.View>
|
||||
</View>
|
||||
</FocusScope>
|
||||
)
|
||||
}
|
||||
@@ -268,3 +257,25 @@ export function Close() {
|
||||
export function Handle() {
|
||||
return null
|
||||
}
|
||||
|
||||
function Backdrop() {
|
||||
const t = useTheme()
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
opacity: 0.8,
|
||||
}}>
|
||||
<View
|
||||
style={[
|
||||
a.fixed,
|
||||
a.inset_0,
|
||||
{
|
||||
backgroundColor: t.palette.black,
|
||||
// @ts-ignore web only
|
||||
animation: 'fadeIn ease-out 0.15s',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react'
|
||||
import {StyleProp, TextStyle, View, ViewStyle} from 'react-native'
|
||||
|
||||
import {atoms as a, useTheme, web} from '#/alf'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {Text} from '#/components/Typography'
|
||||
|
||||
export function Header({
|
||||
@@ -29,10 +29,8 @@ export function Header({
|
||||
a.border_b,
|
||||
t.atoms.border_contrast_medium,
|
||||
t.atoms.bg,
|
||||
web([
|
||||
{borderRadiusTopLeft: a.rounded_md.borderRadius},
|
||||
{borderRadiusTopRight: a.rounded_md.borderRadius},
|
||||
]),
|
||||
{borderTopLeftRadius: a.rounded_md.borderRadius},
|
||||
{borderTopRightRadius: a.rounded_md.borderRadius},
|
||||
style,
|
||||
]}>
|
||||
{renderLeft && (
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react'
|
||||
import {View} from 'react-native'
|
||||
|
||||
import {atoms as a, flatten, useTheme, ViewStyleProp} from '#/alf'
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react'
|
||||
import {LinearGradient} from 'expo-linear-gradient'
|
||||
|
||||
import {atoms as a, tokens} from '#/alf'
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react'
|
||||
import {View} from 'react-native'
|
||||
|
||||
import {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, {useCallback, useMemo} from 'react'
|
||||
import {useCallback, useMemo} from 'react'
|
||||
import {ActivityIndicator, FlatList, View} from 'react-native'
|
||||
import {AppBskyFeedGetLikes as GetLikes} from '@atproto/api'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
|
||||
+9
-13
@@ -223,7 +223,7 @@ export function Link({
|
||||
{...web({
|
||||
hrefAttrs: {
|
||||
target: download ? undefined : isExternal ? 'blank' : undefined,
|
||||
rel: isExternal ? 'noopener noreferrer' : undefined,
|
||||
rel: isExternal ? 'noopener' : undefined,
|
||||
download,
|
||||
},
|
||||
dataSet: {
|
||||
@@ -274,11 +274,6 @@ export function InlineLinkText({
|
||||
onOut: onHoverOut,
|
||||
} = useInteractionState()
|
||||
const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState()
|
||||
const {
|
||||
state: pressed,
|
||||
onIn: onPressIn,
|
||||
onOut: onPressOut,
|
||||
} = useInteractionState()
|
||||
const flattenedStyle = flatten(style) || {}
|
||||
|
||||
return (
|
||||
@@ -289,19 +284,20 @@ export function InlineLinkText({
|
||||
{...rest}
|
||||
style={[
|
||||
{color: t.palette.primary_500},
|
||||
(hovered || focused || pressed) &&
|
||||
(hovered || focused) &&
|
||||
!disableUnderline && {
|
||||
...web({outline: 0}),
|
||||
textDecorationLine: 'underline',
|
||||
textDecorationColor: flattenedStyle.color ?? t.palette.primary_500,
|
||||
...web({
|
||||
outline: 0,
|
||||
textDecorationLine: 'underline',
|
||||
textDecorationColor:
|
||||
flattenedStyle.color ?? t.palette.primary_500,
|
||||
}),
|
||||
},
|
||||
flattenedStyle,
|
||||
]}
|
||||
role="link"
|
||||
onPress={download ? undefined : onPress}
|
||||
onLongPress={onLongPress}
|
||||
onPressIn={onPressIn}
|
||||
onPressOut={onPressOut}
|
||||
onFocus={onFocus}
|
||||
onBlur={onBlur}
|
||||
onMouseEnter={onHoverIn}
|
||||
@@ -311,7 +307,7 @@ export function InlineLinkText({
|
||||
{...web({
|
||||
hrefAttrs: {
|
||||
target: download ? undefined : isExternal ? 'blank' : undefined,
|
||||
rel: isExternal ? 'noopener noreferrer' : undefined,
|
||||
rel: isExternal ? 'noopener' : undefined,
|
||||
download,
|
||||
},
|
||||
dataSet: {
|
||||
|
||||
@@ -17,13 +17,12 @@ export function Loader(props: Props) {
|
||||
const rotation = useSharedValue(0)
|
||||
|
||||
const animatedStyles = useAnimatedStyle(() => ({
|
||||
transform: [{rotate: rotation.value + 'deg'}],
|
||||
transform: [{rotate: rotation.get() + 'deg'}],
|
||||
}))
|
||||
|
||||
React.useEffect(() => {
|
||||
rotation.value = withRepeat(
|
||||
withTiming(360, {duration: 500, easing: Easing.linear}),
|
||||
-1,
|
||||
rotation.set(() =>
|
||||
withRepeat(withTiming(360, {duration: 500, easing: Easing.linear}), -1),
|
||||
)
|
||||
}, [rotation])
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react'
|
||||
import {View} from 'react-native'
|
||||
|
||||
import {atoms as a, flatten, useTheme} from '#/alf'
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
|
||||
import React from 'react'
|
||||
import {Pressable, StyleProp, View, ViewStyle} from 'react-native'
|
||||
import {msg} from '@lingui/macro'
|
||||
|
||||
@@ -283,8 +283,8 @@ export function DescriptionPlaceholder({
|
||||
export type FollowButtonProps = {
|
||||
profile: AppBskyActorDefs.ProfileViewBasic
|
||||
moderationOpts: ModerationOpts
|
||||
logContext: LogEvents['profile:follow:sampled']['logContext'] &
|
||||
LogEvents['profile:unfollow:sampled']['logContext']
|
||||
logContext: LogEvents['profile:follow']['logContext'] &
|
||||
LogEvents['profile:unfollow']['logContext']
|
||||
} & Partial<ButtonProps>
|
||||
|
||||
export function FollowButton(props: FollowButtonProps) {
|
||||
|
||||
@@ -302,8 +302,8 @@ export function ProfileHoverCardInner(props: ProfileHoverCardProps) {
|
||||
const animationStyle = {
|
||||
animation:
|
||||
currentState.stage === 'hiding'
|
||||
? `avatarHoverFadeOut ${HIDE_DURATION}ms both`
|
||||
: `avatarHoverFadeIn ${SHOW_DURATION}ms both`,
|
||||
? `fadeOut ${HIDE_DURATION}ms both`
|
||||
: `fadeIn ${SHOW_DURATION}ms both`,
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react'
|
||||
import {StyleProp, View, ViewStyle} from 'react-native'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react'
|
||||
import {View} from 'react-native'
|
||||
import * as Progress from 'react-native-progress'
|
||||
|
||||
|
||||
@@ -55,13 +55,15 @@ export const ProgressGuideToast = React.forwardRef<
|
||||
|
||||
// animate the opacity then set isOpen to false when done
|
||||
const setIsntOpen = () => setIsOpen(false)
|
||||
opacity.value = withTiming(
|
||||
0,
|
||||
{
|
||||
duration: 400,
|
||||
easing: Easing.out(Easing.cubic),
|
||||
},
|
||||
() => runOnJS(setIsntOpen)(),
|
||||
opacity.set(() =>
|
||||
withTiming(
|
||||
0,
|
||||
{
|
||||
duration: 400,
|
||||
easing: Easing.out(Easing.cubic),
|
||||
},
|
||||
() => runOnJS(setIsntOpen)(),
|
||||
),
|
||||
)
|
||||
}, [setIsOpen, opacity])
|
||||
|
||||
@@ -71,20 +73,24 @@ export const ProgressGuideToast = React.forwardRef<
|
||||
|
||||
// animate the vertical translation, the opacity, and the checkmark
|
||||
const playCheckmark = () => animatedCheckRef.current?.play()
|
||||
opacity.value = 0
|
||||
opacity.value = withTiming(
|
||||
1,
|
||||
{
|
||||
duration: 100,
|
||||
easing: Easing.out(Easing.cubic),
|
||||
},
|
||||
() => runOnJS(playCheckmark)(),
|
||||
opacity.set(0)
|
||||
opacity.set(() =>
|
||||
withTiming(
|
||||
1,
|
||||
{
|
||||
duration: 100,
|
||||
easing: Easing.out(Easing.cubic),
|
||||
},
|
||||
() => runOnJS(playCheckmark)(),
|
||||
),
|
||||
)
|
||||
translateY.set(0)
|
||||
translateY.set(() =>
|
||||
withTiming(insets.top + 10, {
|
||||
duration: 500,
|
||||
easing: Easing.out(Easing.cubic),
|
||||
}),
|
||||
)
|
||||
translateY.value = 0
|
||||
translateY.value = withTiming(insets.top + 10, {
|
||||
duration: 500,
|
||||
easing: Easing.out(Easing.cubic),
|
||||
})
|
||||
|
||||
// start the countdown timer to autoclose
|
||||
timeoutRef.current = setTimeout(close, visibleDuration || 5e3)
|
||||
@@ -114,8 +120,8 @@ export const ProgressGuideToast = React.forwardRef<
|
||||
}, [winDim.width])
|
||||
|
||||
const animatedStyle = useAnimatedStyle(() => ({
|
||||
transform: [{translateY: translateY.value}],
|
||||
opacity: opacity.value,
|
||||
transform: [{translateY: translateY.get()}],
|
||||
opacity: opacity.get(),
|
||||
}))
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {AppBskyLabelerDefs} from '@atproto/api'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
|
||||
@@ -9,6 +9,7 @@ import {NavigationProp} from '#/lib/routes/types'
|
||||
import {toShortUrl} from '#/lib/strings/url-helpers'
|
||||
import {isNative} from '#/platform/detection'
|
||||
import {atoms as a, flatten, native, TextStyleProp, useTheme, web} from '#/alf'
|
||||
import {isOnlyEmoji} from '#/alf/typography'
|
||||
import {useInteractionState} from '#/components/hooks/useInteractionState'
|
||||
import {InlineLinkText, LinkProps} from '#/components/Link'
|
||||
import {ProfileHoverCard} from '#/components/ProfileHoverCard'
|
||||
@@ -53,7 +54,6 @@ export function RichText({
|
||||
const plainStyles = [a.leading_snug, flattenedStyle]
|
||||
const interactiveStyles = [
|
||||
a.leading_snug,
|
||||
a.pointer_events_auto,
|
||||
flatten(interactiveStyle),
|
||||
flattenedStyle,
|
||||
]
|
||||
@@ -151,17 +151,14 @@ export function RichText({
|
||||
/>,
|
||||
)
|
||||
} else {
|
||||
els.push(
|
||||
<Text key={key} emoji style={plainStyles}>
|
||||
{segment.text}
|
||||
</Text>,
|
||||
)
|
||||
els.push(segment.text)
|
||||
}
|
||||
key++
|
||||
}
|
||||
|
||||
return (
|
||||
<Text
|
||||
emoji
|
||||
selectable={selectable}
|
||||
testID={testID}
|
||||
style={plainStyles}
|
||||
@@ -194,11 +191,6 @@ function RichTextTag({
|
||||
onOut: onHoverOut,
|
||||
} = useInteractionState()
|
||||
const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState()
|
||||
const {
|
||||
state: pressed,
|
||||
onIn: onPressIn,
|
||||
onOut: onPressOut,
|
||||
} = useInteractionState()
|
||||
const navigation = useNavigation<NavigationProp>()
|
||||
|
||||
const navigateToPage = React.useCallback(() => {
|
||||
@@ -228,8 +220,6 @@ function RichTextTag({
|
||||
accessibilityRole: isNative ? 'button' : undefined,
|
||||
onPress: navigateToPage,
|
||||
onLongPress: openDialog,
|
||||
onPressIn: onPressIn,
|
||||
onPressOut: onPressOut,
|
||||
})}
|
||||
{...web({
|
||||
onMouseEnter: onHoverIn,
|
||||
@@ -243,10 +233,12 @@ function RichTextTag({
|
||||
cursor: 'pointer',
|
||||
}),
|
||||
{color: t.palette.primary_500},
|
||||
(hovered || focused || pressed) && {
|
||||
...web({outline: 0}),
|
||||
textDecorationLine: 'underline',
|
||||
textDecorationColor: t.palette.primary_500,
|
||||
(hovered || focused) && {
|
||||
...web({
|
||||
outline: 0,
|
||||
textDecorationLine: 'underline',
|
||||
textDecorationColor: t.palette.primary_500,
|
||||
}),
|
||||
},
|
||||
style,
|
||||
]}>
|
||||
@@ -256,10 +248,3 @@ function RichTextTag({
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
export function isOnlyEmoji(text: string) {
|
||||
return (
|
||||
text.length <= 15 &&
|
||||
/^[\p{Emoji_Presentation}\p{Extended_Pictographic}]+$/u.test(text)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ export const ProfilesList = React.forwardRef<SectionRef, ProfilesListProps>(
|
||||
ref,
|
||||
) {
|
||||
const t = useTheme()
|
||||
const bottomBarOffset = useBottomBarOffset(300)
|
||||
const bottomBarOffset = useBottomBarOffset(headerHeight)
|
||||
const initialNumToRender = useInitialNumToRender()
|
||||
const {currentAccount} = useSession()
|
||||
const {data, refetch, isError} = useAllListMembersQuery(listUri)
|
||||
|
||||
@@ -14,6 +14,7 @@ import {InfiniteData, UseInfiniteQueryResult} from '@tanstack/react-query'
|
||||
|
||||
import {useGenerateStarterPackMutation} from '#/lib/generate-starterpack'
|
||||
import {useBottomBarOffset} from '#/lib/hooks/useBottomBarOffset'
|
||||
import {useEmail} from '#/lib/hooks/useEmail'
|
||||
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
||||
import {NavigationProp} from '#/lib/routes/types'
|
||||
import {parseStarterPackUri} from '#/lib/strings/starter-pack'
|
||||
@@ -27,6 +28,7 @@ import {LinearGradientBackground} from '#/components/LinearGradientBackground'
|
||||
import {Loader} from '#/components/Loader'
|
||||
import * as Prompt from '#/components/Prompt'
|
||||
import {Default as StarterPackCard} from '#/components/StarterPack/StarterPackCard'
|
||||
import {VerifyEmailDialog} from '../dialogs/VerifyEmailDialog'
|
||||
import {PlusSmall_Stroke2_Corner0_Rounded as Plus} from '../icons/Plus'
|
||||
|
||||
interface SectionRef {
|
||||
@@ -186,6 +188,9 @@ function Empty() {
|
||||
const followersDialogControl = useDialogControl()
|
||||
const errorDialogControl = useDialogControl()
|
||||
|
||||
const {needsEmailVerification} = useEmail()
|
||||
const verifyEmailControl = useDialogControl()
|
||||
|
||||
const [isGenerating, setIsGenerating] = React.useState(false)
|
||||
|
||||
const {mutate: generateStarterPack} = useGenerateStarterPackMutation({
|
||||
@@ -249,7 +254,13 @@ function Empty() {
|
||||
color="primary"
|
||||
size="small"
|
||||
disabled={isGenerating}
|
||||
onPress={confirmDialogControl.open}
|
||||
onPress={() => {
|
||||
if (needsEmailVerification) {
|
||||
verifyEmailControl.open()
|
||||
} else {
|
||||
confirmDialogControl.open()
|
||||
}
|
||||
}}
|
||||
style={{backgroundColor: 'transparent'}}>
|
||||
<ButtonText style={{color: 'white'}}>
|
||||
<Trans>Make one for me</Trans>
|
||||
@@ -262,7 +273,13 @@ function Empty() {
|
||||
color="primary"
|
||||
size="small"
|
||||
disabled={isGenerating}
|
||||
onPress={() => navigation.navigate('StarterPackWizard')}
|
||||
onPress={() => {
|
||||
if (needsEmailVerification) {
|
||||
verifyEmailControl.open()
|
||||
} else {
|
||||
navigation.navigate('StarterPackWizard')
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
backgroundColor: 'white',
|
||||
borderColor: 'white',
|
||||
@@ -318,6 +335,12 @@ function Empty() {
|
||||
onConfirm={generate}
|
||||
confirmButtonCta={_(msg`Retry`)}
|
||||
/>
|
||||
<VerifyEmailDialog
|
||||
reasonText={_(
|
||||
msg`Before creating a starter pack, you must first verify your email.`,
|
||||
)}
|
||||
control={verifyEmailControl}
|
||||
/>
|
||||
</LinearGradientBackground>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {Image} from 'expo-image'
|
||||
import {requestMediaLibraryPermissionsAsync} from 'expo-image-picker'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, {useRef} from 'react'
|
||||
import {useRef} from 'react'
|
||||
import type {ListRenderItemInfo} from 'react-native'
|
||||
import {View} from 'react-native'
|
||||
import {AppBskyActorDefs, ModerationOpts} from '@atproto/api'
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react'
|
||||
import {Keyboard, View} from 'react-native'
|
||||
import {
|
||||
AppBskyActorDefs,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
export function SubtleWebHover({}: {hover: boolean}) {
|
||||
import {ViewStyleProp} from '#/alf'
|
||||
|
||||
export function SubtleWebHover({}: ViewStyleProp & {hover: boolean}) {
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import React from 'react'
|
||||
import {StyleSheet, View} from 'react-native'
|
||||
|
||||
import {isTouchDevice} from '#/lib/browser'
|
||||
import {useTheme} from '#/alf'
|
||||
import {useTheme, ViewStyleProp} from '#/alf'
|
||||
|
||||
export function SubtleWebHover({hover}: {hover: boolean}) {
|
||||
export function SubtleWebHover({
|
||||
style,
|
||||
hover,
|
||||
}: ViewStyleProp & {hover: boolean}) {
|
||||
const t = useTheme()
|
||||
if (isTouchDevice) {
|
||||
return null
|
||||
@@ -26,9 +28,8 @@ export function SubtleWebHover({hover}: {hover: boolean}) {
|
||||
style={[
|
||||
t.atoms.bg_contrast_25,
|
||||
styles.container,
|
||||
{
|
||||
opacity: hover ? opacity : 0,
|
||||
},
|
||||
{opacity: hover ? opacity : 0},
|
||||
style,
|
||||
]}
|
||||
/>
|
||||
)
|
||||
|
||||
+164
-150
@@ -40,9 +40,37 @@ export function TagMenu({
|
||||
tag: string
|
||||
authorHandle?: string
|
||||
}>) {
|
||||
const navigation = useNavigation<NavigationProp>()
|
||||
return (
|
||||
<>
|
||||
{children}
|
||||
<Dialog.Outer control={control}>
|
||||
<Dialog.Handle />
|
||||
<TagMenuInner
|
||||
control={control}
|
||||
tag={tag}
|
||||
authorHandle={authorHandle}
|
||||
navigation={navigation}
|
||||
/>
|
||||
</Dialog.Outer>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function TagMenuInner({
|
||||
control,
|
||||
tag,
|
||||
authorHandle,
|
||||
navigation,
|
||||
}: {
|
||||
control: Dialog.DialogOuterProps['control']
|
||||
tag: string
|
||||
authorHandle?: string
|
||||
// Passed down because on native, we don't use real portals (and context would be wrong).
|
||||
navigation: NavigationProp
|
||||
}) {
|
||||
const {_} = useLingui()
|
||||
const t = useTheme()
|
||||
const navigation = useNavigation<NavigationProp>()
|
||||
const {isLoading: isPreferencesLoading, data: preferences} =
|
||||
usePreferencesQuery()
|
||||
const {
|
||||
@@ -79,32 +107,75 @@ export function TagMenu({
|
||||
}, [tag, preferences?.moderationPrefs?.mutedWords])
|
||||
|
||||
return (
|
||||
<>
|
||||
{children}
|
||||
|
||||
<Dialog.Outer control={control}>
|
||||
<Dialog.Handle />
|
||||
<Dialog.Inner label={_(msg`Tag menu: ${displayTag}`)}>
|
||||
{isPreferencesLoading ? (
|
||||
<View style={[a.w_full, a.align_center]}>
|
||||
<Loader size="lg" />
|
||||
</View>
|
||||
) : (
|
||||
<>
|
||||
<Dialog.Inner label={_(msg`Tag menu: ${displayTag}`)}>
|
||||
{isPreferencesLoading ? (
|
||||
<View style={[a.w_full, a.align_center]}>
|
||||
<Loader size="lg" />
|
||||
</View>
|
||||
) : (
|
||||
<>
|
||||
<View
|
||||
style={[
|
||||
a.rounded_md,
|
||||
a.border,
|
||||
a.mb_md,
|
||||
t.atoms.border_contrast_low,
|
||||
t.atoms.bg_contrast_25,
|
||||
]}>
|
||||
<Link
|
||||
label={_(msg`View all posts with tag ${displayTag}`)}
|
||||
{...createStaticClick(() => {
|
||||
control.close(() => {
|
||||
navigation.push('Hashtag', {
|
||||
tag: encodeURIComponent(tag),
|
||||
})
|
||||
})
|
||||
})}>
|
||||
<View
|
||||
style={[
|
||||
a.rounded_md,
|
||||
a.border,
|
||||
a.mb_md,
|
||||
t.atoms.border_contrast_low,
|
||||
t.atoms.bg_contrast_25,
|
||||
a.w_full,
|
||||
a.flex_row,
|
||||
a.align_center,
|
||||
a.justify_start,
|
||||
a.gap_md,
|
||||
a.px_lg,
|
||||
a.py_md,
|
||||
]}>
|
||||
<Search size="lg" style={[t.atoms.text_contrast_medium]} />
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
ellipsizeMode="middle"
|
||||
style={[
|
||||
a.flex_1,
|
||||
a.text_md,
|
||||
a.font_bold,
|
||||
native({top: 2}),
|
||||
t.atoms.text_contrast_medium,
|
||||
]}>
|
||||
<Trans>
|
||||
See{' '}
|
||||
<Text style={[a.text_md, a.font_bold, t.atoms.text]}>
|
||||
{displayTag}
|
||||
</Text>{' '}
|
||||
posts
|
||||
</Trans>
|
||||
</Text>
|
||||
</View>
|
||||
</Link>
|
||||
|
||||
{authorHandle && !isInvalidHandle(authorHandle) && (
|
||||
<>
|
||||
<Divider />
|
||||
|
||||
<Link
|
||||
label={_(msg`View all posts with tag ${displayTag}`)}
|
||||
label={_(
|
||||
msg`View all posts by @${authorHandle} with tag ${displayTag}`,
|
||||
)}
|
||||
{...createStaticClick(() => {
|
||||
control.close(() => {
|
||||
navigation.push('Hashtag', {
|
||||
tag: encodeURIComponent(tag),
|
||||
author: authorHandle,
|
||||
})
|
||||
})
|
||||
})}>
|
||||
@@ -118,7 +189,7 @@ export function TagMenu({
|
||||
a.px_lg,
|
||||
a.py_md,
|
||||
]}>
|
||||
<Search size="lg" style={[t.atoms.text_contrast_medium]} />
|
||||
<Person size="lg" style={[t.atoms.text_contrast_medium]} />
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
ellipsizeMode="middle"
|
||||
@@ -134,143 +205,86 @@ export function TagMenu({
|
||||
<Text style={[a.text_md, a.font_bold, t.atoms.text]}>
|
||||
{displayTag}
|
||||
</Text>{' '}
|
||||
posts
|
||||
posts by this user
|
||||
</Trans>
|
||||
</Text>
|
||||
</View>
|
||||
</Link>
|
||||
</>
|
||||
)}
|
||||
|
||||
{authorHandle && !isInvalidHandle(authorHandle) && (
|
||||
<>
|
||||
<Divider />
|
||||
{preferences ? (
|
||||
<>
|
||||
<Divider />
|
||||
|
||||
<Link
|
||||
label={_(
|
||||
msg`View all posts by @${authorHandle} with tag ${displayTag}`,
|
||||
)}
|
||||
{...createStaticClick(() => {
|
||||
control.close(() => {
|
||||
navigation.push('Hashtag', {
|
||||
tag: encodeURIComponent(tag),
|
||||
author: authorHandle,
|
||||
})
|
||||
})
|
||||
})}>
|
||||
<View
|
||||
style={[
|
||||
a.w_full,
|
||||
a.flex_row,
|
||||
a.align_center,
|
||||
a.justify_start,
|
||||
a.gap_md,
|
||||
a.px_lg,
|
||||
a.py_md,
|
||||
]}>
|
||||
<Person
|
||||
size="lg"
|
||||
style={[t.atoms.text_contrast_medium]}
|
||||
/>
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
ellipsizeMode="middle"
|
||||
style={[
|
||||
a.flex_1,
|
||||
a.text_md,
|
||||
a.font_bold,
|
||||
native({top: 2}),
|
||||
t.atoms.text_contrast_medium,
|
||||
]}>
|
||||
<Trans>
|
||||
See{' '}
|
||||
<Text
|
||||
style={[a.text_md, a.font_bold, t.atoms.text]}>
|
||||
{displayTag}
|
||||
</Text>{' '}
|
||||
posts by this user
|
||||
</Trans>
|
||||
</Text>
|
||||
</View>
|
||||
</Link>
|
||||
</>
|
||||
)}
|
||||
|
||||
{preferences ? (
|
||||
<>
|
||||
<Divider />
|
||||
|
||||
<Button
|
||||
label={
|
||||
isMuted
|
||||
? _(msg`Unmute all ${displayTag} posts`)
|
||||
: _(msg`Mute all ${displayTag} posts`)
|
||||
<Button
|
||||
label={
|
||||
isMuted
|
||||
? _(msg`Unmute all ${displayTag} posts`)
|
||||
: _(msg`Mute all ${displayTag} posts`)
|
||||
}
|
||||
onPress={() => {
|
||||
control.close(() => {
|
||||
if (isMuted) {
|
||||
resetUpsert()
|
||||
removeMutedWords(removeableMuteWords)
|
||||
} else {
|
||||
resetRemove()
|
||||
upsertMutedWord([
|
||||
{
|
||||
value: tag,
|
||||
targets: ['tag'],
|
||||
actorTarget: 'all',
|
||||
},
|
||||
])
|
||||
}
|
||||
onPress={() => {
|
||||
control.close(() => {
|
||||
if (isMuted) {
|
||||
resetUpsert()
|
||||
removeMutedWords(removeableMuteWords)
|
||||
} else {
|
||||
resetRemove()
|
||||
upsertMutedWord([
|
||||
{
|
||||
value: tag,
|
||||
targets: ['tag'],
|
||||
actorTarget: 'all',
|
||||
},
|
||||
])
|
||||
}
|
||||
})
|
||||
}}>
|
||||
<View
|
||||
style={[
|
||||
a.w_full,
|
||||
a.flex_row,
|
||||
a.align_center,
|
||||
a.justify_start,
|
||||
a.gap_md,
|
||||
a.px_lg,
|
||||
a.py_md,
|
||||
]}>
|
||||
<Mute
|
||||
size="lg"
|
||||
style={[t.atoms.text_contrast_medium]}
|
||||
/>
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
ellipsizeMode="middle"
|
||||
style={[
|
||||
a.flex_1,
|
||||
a.text_md,
|
||||
a.font_bold,
|
||||
native({top: 2}),
|
||||
t.atoms.text_contrast_medium,
|
||||
]}>
|
||||
{isMuted ? _(msg`Unmute`) : _(msg`Mute`)}{' '}
|
||||
<Text style={[a.text_md, a.font_bold, t.atoms.text]}>
|
||||
{displayTag}
|
||||
</Text>{' '}
|
||||
<Trans>posts</Trans>
|
||||
</Text>
|
||||
</View>
|
||||
</Button>
|
||||
</>
|
||||
) : null}
|
||||
</View>
|
||||
})
|
||||
}}>
|
||||
<View
|
||||
style={[
|
||||
a.w_full,
|
||||
a.flex_row,
|
||||
a.align_center,
|
||||
a.justify_start,
|
||||
a.gap_md,
|
||||
a.px_lg,
|
||||
a.py_md,
|
||||
]}>
|
||||
<Mute size="lg" style={[t.atoms.text_contrast_medium]} />
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
ellipsizeMode="middle"
|
||||
style={[
|
||||
a.flex_1,
|
||||
a.text_md,
|
||||
a.font_bold,
|
||||
native({top: 2}),
|
||||
t.atoms.text_contrast_medium,
|
||||
]}>
|
||||
{isMuted ? _(msg`Unmute`) : _(msg`Mute`)}{' '}
|
||||
<Text style={[a.text_md, a.font_bold, t.atoms.text]}>
|
||||
{displayTag}
|
||||
</Text>{' '}
|
||||
<Trans>posts</Trans>
|
||||
</Text>
|
||||
</View>
|
||||
</Button>
|
||||
</>
|
||||
) : null}
|
||||
</View>
|
||||
|
||||
<Button
|
||||
label={_(msg`Close this dialog`)}
|
||||
size="small"
|
||||
variant="ghost"
|
||||
color="secondary"
|
||||
onPress={() => control.close()}>
|
||||
<ButtonText>
|
||||
<Trans>Cancel</Trans>
|
||||
</ButtonText>
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</Dialog.Inner>
|
||||
</Dialog.Outer>
|
||||
</>
|
||||
<Button
|
||||
label={_(msg`Close this dialog`)}
|
||||
size="small"
|
||||
variant="ghost"
|
||||
color="secondary"
|
||||
onPress={() => control.close()}>
|
||||
<ButtonText>
|
||||
<Trans>Cancel</Trans>
|
||||
</ButtonText>
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</Dialog.Inner>
|
||||
)
|
||||
}
|
||||
|
||||
+10
-139
@@ -1,140 +1,15 @@
|
||||
import React from 'react'
|
||||
import {StyleProp, TextProps as RNTextProps, TextStyle} from 'react-native'
|
||||
import {UITextView} from 'react-native-uitextview'
|
||||
import createEmojiRegex from 'emoji-regex'
|
||||
|
||||
import {logger} from '#/logger'
|
||||
import {isIOS, isNative} from '#/platform/detection'
|
||||
import {Alf, applyFonts, atoms, flatten, useAlf, useTheme, web} from '#/alf'
|
||||
import {atoms, flatten, useAlf, useTheme, web} from '#/alf'
|
||||
import {
|
||||
childHasEmoji,
|
||||
normalizeTextStyles,
|
||||
renderChildrenWithEmoji,
|
||||
TextProps,
|
||||
} from '#/alf/typography'
|
||||
import {IS_DEV} from '#/env'
|
||||
|
||||
export type StringChild = string | (string | null)[]
|
||||
|
||||
export type TextProps = Omit<RNTextProps, 'children'> & {
|
||||
/**
|
||||
* Lets the user select text, to use the native copy and paste functionality.
|
||||
*/
|
||||
selectable?: boolean
|
||||
/**
|
||||
* Provides `data-*` attributes to the underlying `UITextView` component on
|
||||
* web only.
|
||||
*/
|
||||
dataSet?: Record<string, string | number | undefined>
|
||||
/**
|
||||
* Appears as a small tooltip on web hover.
|
||||
*/
|
||||
title?: string
|
||||
} & (
|
||||
| {
|
||||
emoji?: true
|
||||
children: StringChild
|
||||
}
|
||||
| {
|
||||
emoji?: false
|
||||
children: RNTextProps['children']
|
||||
}
|
||||
)
|
||||
|
||||
const EMOJI = createEmojiRegex()
|
||||
|
||||
export function childHasEmoji(children: React.ReactNode) {
|
||||
return (Array.isArray(children) ? children : [children]).some(
|
||||
child => typeof child === 'string' && createEmojiRegex().test(child),
|
||||
)
|
||||
}
|
||||
|
||||
export function childIsString(
|
||||
children: React.ReactNode,
|
||||
): children is StringChild {
|
||||
return (
|
||||
typeof children === 'string' ||
|
||||
(Array.isArray(children) &&
|
||||
children.every(child => typeof child === 'string' || child === null))
|
||||
)
|
||||
}
|
||||
|
||||
export function renderChildrenWithEmoji(
|
||||
children: StringChild,
|
||||
props: Omit<TextProps, 'children'> = {},
|
||||
) {
|
||||
const normalized = Array.isArray(children) ? children : [children]
|
||||
|
||||
return (
|
||||
<UITextView {...props}>
|
||||
{normalized.map(child => {
|
||||
if (typeof child !== 'string') return child
|
||||
|
||||
const emojis = child.match(EMOJI)
|
||||
|
||||
if (emojis === null) {
|
||||
return child
|
||||
}
|
||||
|
||||
return child.split(EMOJI).map((stringPart, index) => (
|
||||
<UITextView key={index} {...props}>
|
||||
{stringPart}
|
||||
{emojis[index] ? (
|
||||
<UITextView
|
||||
{...props}
|
||||
style={[props?.style, {color: 'black', fontFamily: 'System'}]}>
|
||||
{emojis[index]}
|
||||
</UITextView>
|
||||
) : null}
|
||||
</UITextView>
|
||||
))
|
||||
})}
|
||||
</UITextView>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Util to calculate lineHeight from a text size atom and a leading atom
|
||||
*
|
||||
* Example:
|
||||
* `leading(atoms.text_md, atoms.leading_normal)` // => 24
|
||||
*/
|
||||
export function leading<
|
||||
Size extends {fontSize?: number},
|
||||
Leading extends {lineHeight?: number},
|
||||
>(textSize: Size, leading: Leading) {
|
||||
const size = textSize?.fontSize || atoms.text_md.fontSize
|
||||
const lineHeight = leading?.lineHeight || atoms.leading_normal.lineHeight
|
||||
return Math.round(size * lineHeight)
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that `lineHeight` defaults to a relative value of `1`, or applies
|
||||
* other relative leading atoms.
|
||||
*
|
||||
* If the `lineHeight` value is > 2, we assume it's an absolute value and
|
||||
* returns it as-is.
|
||||
*/
|
||||
export function normalizeTextStyles(
|
||||
styles: StyleProp<TextStyle>,
|
||||
{
|
||||
fontScale,
|
||||
fontFamily,
|
||||
}: {
|
||||
fontScale: number
|
||||
fontFamily: Alf['fonts']['family']
|
||||
} & Pick<Alf, 'flags'>,
|
||||
) {
|
||||
const s = flatten(styles)
|
||||
// should always be defined on these components
|
||||
s.fontSize = (s.fontSize || atoms.text_md.fontSize) * fontScale
|
||||
|
||||
if (s?.lineHeight) {
|
||||
if (s.lineHeight !== 0 && s.lineHeight <= 2) {
|
||||
s.lineHeight = Math.round(s.fontSize * s.lineHeight)
|
||||
}
|
||||
} else if (!isNative) {
|
||||
s.lineHeight = s.fontSize
|
||||
}
|
||||
|
||||
applyFonts(s, fontFamily)
|
||||
|
||||
return s
|
||||
}
|
||||
export type {TextProps}
|
||||
|
||||
/**
|
||||
* Our main text component. Use this most of the time.
|
||||
@@ -162,10 +37,6 @@ export function Text({
|
||||
`Text: emoji detected but emoji not enabled: "${children}"\n\nPlease add <Text emoji />'`,
|
||||
)
|
||||
}
|
||||
|
||||
if (emoji && !childIsString(children)) {
|
||||
logger.error('Text: when <Text emoji />, children can only be strings.')
|
||||
}
|
||||
}
|
||||
|
||||
const shared = {
|
||||
@@ -178,12 +49,12 @@ export function Text({
|
||||
|
||||
return (
|
||||
<UITextView {...shared}>
|
||||
{isIOS && emoji ? renderChildrenWithEmoji(children, shared) : children}
|
||||
{renderChildrenWithEmoji(children, shared, emoji ?? false)}
|
||||
</UITextView>
|
||||
)
|
||||
}
|
||||
|
||||
export function createHeadingElement({level}: {level: number}) {
|
||||
function createHeadingElement({level}: {level: number}) {
|
||||
return function HeadingElement({style, ...rest}: TextProps) {
|
||||
const attr =
|
||||
web({
|
||||
|
||||
@@ -32,21 +32,25 @@ export const AnimatedCheck = React.forwardRef<
|
||||
const checkAnim = useSharedValue(0)
|
||||
|
||||
const circleAnimatedProps = useAnimatedProps(() => ({
|
||||
strokeDashoffset: 166 - circleAnim.value * 166,
|
||||
strokeDashoffset: 166 - circleAnim.get() * 166,
|
||||
}))
|
||||
const checkAnimatedProps = useAnimatedProps(() => ({
|
||||
strokeDashoffset: 48 - 48 * checkAnim.value,
|
||||
strokeDashoffset: 48 - 48 * checkAnim.get(),
|
||||
}))
|
||||
|
||||
const play = React.useCallback(
|
||||
(cb?: () => void) => {
|
||||
circleAnim.value = 0
|
||||
checkAnim.value = 0
|
||||
circleAnim.set(0)
|
||||
checkAnim.set(0)
|
||||
|
||||
circleAnim.value = withTiming(1, {duration: 500, easing: Easing.linear})
|
||||
checkAnim.value = withDelay(
|
||||
500,
|
||||
withTiming(1, {duration: 300, easing: Easing.linear}, cb),
|
||||
circleAnim.set(() =>
|
||||
withTiming(1, {duration: 500, easing: Easing.linear}),
|
||||
)
|
||||
checkAnim.set(() =>
|
||||
withDelay(
|
||||
500,
|
||||
withTiming(1, {duration: 300, easing: Easing.linear}, cb),
|
||||
),
|
||||
)
|
||||
},
|
||||
[circleAnim, checkAnim],
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -256,6 +256,9 @@ export function PostInteractionSettingsForm({
|
||||
} else {
|
||||
newSelected.splice(i, 1)
|
||||
}
|
||||
if (newSelected.length === 0) {
|
||||
newSelected.push({type: 'everybody'})
|
||||
}
|
||||
|
||||
onChangeThreadgateAllowUISettings(newSelected)
|
||||
}
|
||||
@@ -306,7 +309,7 @@ export function PostInteractionSettingsForm({
|
||||
}
|
||||
value={quotesEnabled}
|
||||
onChange={onChangeQuotesEnabled}
|
||||
style={[, a.justify_between, a.pt_xs]}>
|
||||
style={[a.justify_between, a.pt_xs]}>
|
||||
<Text style={[t.atoms.text_contrast_medium]}>
|
||||
{quotesEnabled ? (
|
||||
<Trans>Quote posts enabled</Trans>
|
||||
@@ -483,7 +486,7 @@ function Selectable({
|
||||
a.justify_between,
|
||||
a.rounded_sm,
|
||||
a.p_md,
|
||||
{height: 40}, // for consistency with checkmark icon visible or not
|
||||
{minHeight: 40}, // for consistency with checkmark icon visible or not
|
||||
t.atoms.bg_contrast_50,
|
||||
(hovered || focused) && t.atoms.bg_contrast_100,
|
||||
isSelected && {
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -18,8 +18,14 @@ import {Text} from '#/components/Typography'
|
||||
|
||||
export function VerifyEmailDialog({
|
||||
control,
|
||||
onCloseWithoutVerifying,
|
||||
onCloseAfterVerifying,
|
||||
reasonText,
|
||||
}: {
|
||||
control: Dialog.DialogControlProps
|
||||
onCloseWithoutVerifying?: () => void
|
||||
onCloseAfterVerifying?: () => void
|
||||
reasonText?: string
|
||||
}) {
|
||||
const agent = useAgent()
|
||||
|
||||
@@ -30,18 +36,24 @@ export function VerifyEmailDialog({
|
||||
control={control}
|
||||
onClose={async () => {
|
||||
if (!didVerify) {
|
||||
onCloseWithoutVerifying?.()
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await agent.resumeSession(agent.session!)
|
||||
onCloseAfterVerifying?.()
|
||||
} catch (e: unknown) {
|
||||
logger.error(String(e))
|
||||
return
|
||||
}
|
||||
}}>
|
||||
<Dialog.Handle />
|
||||
<Inner control={control} setDidVerify={setDidVerify} />
|
||||
<Inner
|
||||
control={control}
|
||||
setDidVerify={setDidVerify}
|
||||
reasonText={reasonText}
|
||||
/>
|
||||
</Dialog.Outer>
|
||||
)
|
||||
}
|
||||
@@ -49,9 +61,11 @@ export function VerifyEmailDialog({
|
||||
export function Inner({
|
||||
control,
|
||||
setDidVerify,
|
||||
reasonText,
|
||||
}: {
|
||||
control: Dialog.DialogControlProps
|
||||
setDidVerify: (value: boolean) => void
|
||||
reasonText?: string
|
||||
}) {
|
||||
const {_} = useLingui()
|
||||
const {currentAccount} = useSession()
|
||||
@@ -132,34 +146,63 @@ export function Inner({
|
||||
<ErrorMessage message={error} />
|
||||
</View>
|
||||
) : null}
|
||||
<Text style={[a.text_md, a.leading_snug]}>
|
||||
{currentStep === 'StepOne' ? (
|
||||
<>
|
||||
<Trans>
|
||||
You'll receive an email at{' '}
|
||||
<Text style={[a.text_md, a.leading_snug, a.font_bold]}>
|
||||
{currentAccount?.email}
|
||||
</Text>{' '}
|
||||
to verify it's you.
|
||||
</Trans>{' '}
|
||||
<InlineLinkText
|
||||
to="#"
|
||||
label={_(msg`Change email address`)}
|
||||
style={[a.text_md, a.leading_snug]}
|
||||
onPress={e => {
|
||||
e.preventDefault()
|
||||
control.close(() => {
|
||||
openModal({name: 'change-email'})
|
||||
})
|
||||
return false
|
||||
}}>
|
||||
<Trans>Need to change it?</Trans>
|
||||
</InlineLinkText>
|
||||
</>
|
||||
) : (
|
||||
uiStrings[currentStep].message
|
||||
)}
|
||||
</Text>
|
||||
{currentStep === 'StepOne' ? (
|
||||
<View>
|
||||
{reasonText ? (
|
||||
<View style={[a.gap_sm]}>
|
||||
<Text style={[a.text_md, a.leading_snug]}>{reasonText}</Text>
|
||||
<Text style={[a.text_md, a.leading_snug]}>
|
||||
Don't have access to{' '}
|
||||
<Text style={[a.text_md, a.leading_snug, a.font_bold]}>
|
||||
{currentAccount?.email}
|
||||
</Text>
|
||||
?{' '}
|
||||
<InlineLinkText
|
||||
to="#"
|
||||
label={_(msg`Change email address`)}
|
||||
style={[a.text_md, a.leading_snug]}
|
||||
onPress={e => {
|
||||
e.preventDefault()
|
||||
control.close(() => {
|
||||
openModal({name: 'change-email'})
|
||||
})
|
||||
return false
|
||||
}}>
|
||||
<Trans>Change your email address</Trans>
|
||||
</InlineLinkText>
|
||||
.
|
||||
</Text>
|
||||
</View>
|
||||
) : (
|
||||
<Text style={[a.text_md, a.leading_snug]}>
|
||||
<Trans>
|
||||
You'll receive an email at{' '}
|
||||
<Text style={[a.text_md, a.leading_snug, a.font_bold]}>
|
||||
{currentAccount?.email}
|
||||
</Text>{' '}
|
||||
to verify it's you.
|
||||
</Trans>{' '}
|
||||
<InlineLinkText
|
||||
to="#"
|
||||
label={_(msg`Change email address`)}
|
||||
style={[a.text_md, a.leading_snug]}
|
||||
onPress={e => {
|
||||
e.preventDefault()
|
||||
control.close(() => {
|
||||
openModal({name: 'change-email'})
|
||||
})
|
||||
return false
|
||||
}}>
|
||||
<Trans>Need to change it?</Trans>
|
||||
</InlineLinkText>
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
) : (
|
||||
<Text style={[a.text_md, a.leading_snug]}>
|
||||
{uiStrings[currentStep].message}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
{currentStep === 'StepTwo' ? (
|
||||
<View>
|
||||
|
||||
@@ -1,117 +0,0 @@
|
||||
import React from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {AppearanceToggleButtonGroup} from '#/screens/Settings/AppearanceSettings'
|
||||
import {atoms as a, useAlf, useTheme} from '#/alf'
|
||||
import * as Dialog from '#/components/Dialog'
|
||||
import {useNuxDialogContext} from '#/components/dialogs/nuxs'
|
||||
import {Divider} from '#/components/Divider'
|
||||
import {TextSize_Stroke2_Corner0_Rounded as TextSize} from '#/components/icons/TextSize'
|
||||
import {TitleCase_Stroke2_Corner0_Rounded as Aa} from '#/components/icons/TitleCase'
|
||||
import {Text} from '#/components/Typography'
|
||||
|
||||
export function NeueTypography() {
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
const nuxDialogs = useNuxDialogContext()
|
||||
const control = Dialog.useDialogControl()
|
||||
const {fonts} = useAlf()
|
||||
|
||||
Dialog.useAutoOpen(control, 3e3)
|
||||
|
||||
const onClose = React.useCallback(() => {
|
||||
nuxDialogs.dismissActiveNux()
|
||||
}, [nuxDialogs])
|
||||
|
||||
const onChangeFontFamily = React.useCallback(
|
||||
(values: string[]) => {
|
||||
const next = values[0] === 'system' ? 'system' : 'theme'
|
||||
fonts.setFontFamily(next)
|
||||
},
|
||||
[fonts],
|
||||
)
|
||||
|
||||
const onChangeFontScale = React.useCallback(
|
||||
(values: string[]) => {
|
||||
const next = values[0] || ('0' as any)
|
||||
fonts.setFontScale(next)
|
||||
},
|
||||
[fonts],
|
||||
)
|
||||
|
||||
return (
|
||||
<Dialog.Outer control={control} onClose={onClose}>
|
||||
<Dialog.Handle />
|
||||
<Dialog.ScrollableInner label={_(msg`Introducing new font settings`)}>
|
||||
<View style={[a.gap_xl]}>
|
||||
<View style={[a.gap_md]}>
|
||||
<Text style={[a.text_3xl, a.font_heavy]}>
|
||||
<Trans>New font settings ✨</Trans>
|
||||
</Text>
|
||||
<Text style={[a.text_lg, a.leading_snug, {maxWidth: 400}]}>
|
||||
<Trans>
|
||||
We're introducing a new theme font, along with adjustable font
|
||||
sizing.
|
||||
</Trans>
|
||||
</Text>
|
||||
<Text
|
||||
style={[a.text_sm, a.leading_snug, t.atoms.text_contrast_medium]}>
|
||||
<Trans>
|
||||
You can adjust these in your Appearance Settings later.
|
||||
</Trans>
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<Divider />
|
||||
|
||||
<View style={[a.gap_lg]}>
|
||||
<AppearanceToggleButtonGroup
|
||||
title={_(msg`Font`)}
|
||||
description={_(
|
||||
msg`For the best experience, we recommend using the theme font.`,
|
||||
)}
|
||||
icon={Aa}
|
||||
items={[
|
||||
{
|
||||
label: _(msg`System`),
|
||||
name: 'system',
|
||||
},
|
||||
{
|
||||
label: _(msg`Theme`),
|
||||
name: 'theme',
|
||||
},
|
||||
]}
|
||||
values={[fonts.family]}
|
||||
onChange={onChangeFontFamily}
|
||||
/>
|
||||
|
||||
<AppearanceToggleButtonGroup
|
||||
title={_(msg`Font size`)}
|
||||
icon={TextSize}
|
||||
items={[
|
||||
{
|
||||
label: _(msg`Smaller`),
|
||||
name: '-1',
|
||||
},
|
||||
{
|
||||
label: _(msg`Default`),
|
||||
name: '0',
|
||||
},
|
||||
{
|
||||
label: _(msg`Larger`),
|
||||
name: '1',
|
||||
},
|
||||
]}
|
||||
values={[fonts.scale]}
|
||||
onChange={onChangeFontScale}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<Dialog.Close />
|
||||
</Dialog.ScrollableInner>
|
||||
</Dialog.Outer>
|
||||
)
|
||||
}
|
||||
@@ -19,7 +19,6 @@ import {useOnboardingState} from '#/state/shell'
|
||||
/*
|
||||
* NUXs
|
||||
*/
|
||||
import {NeueTypography} from '#/components/dialogs/nuxs/NeueTypography'
|
||||
import {isSnoozed, snooze, unsnooze} from '#/components/dialogs/nuxs/snoozing'
|
||||
import {IS_DEV} from '#/env'
|
||||
|
||||
@@ -36,19 +35,7 @@ const queuedNuxs: {
|
||||
currentProfile: AppBskyActorDefs.ProfileViewDetailed
|
||||
preferences: UsePreferencesQueryResponse
|
||||
}) => boolean
|
||||
}[] = [
|
||||
{
|
||||
id: Nux.NeueTypography,
|
||||
enabled(props) {
|
||||
if (props.currentProfile.createdAt) {
|
||||
if (new Date(props.currentProfile.createdAt) < new Date('2024-10-09')) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
},
|
||||
},
|
||||
]
|
||||
}[] = []
|
||||
|
||||
const Context = React.createContext<Context>({
|
||||
activeNux: undefined,
|
||||
@@ -66,7 +53,14 @@ export function NuxDialogs() {
|
||||
const onboardingActive = useOnboardingState().isActive
|
||||
|
||||
const isLoading =
|
||||
!currentAccount || !preferences || !profile || onboardingActive
|
||||
onboardingActive ||
|
||||
!currentAccount ||
|
||||
!preferences ||
|
||||
!profile ||
|
||||
// Profile isn't legit ready until createdAt is a real date.
|
||||
!profile.createdAt ||
|
||||
profile.createdAt === '0001-01-01T00:00:00.000Z' // TODO: Fix this in AppView.
|
||||
|
||||
return !isLoading ? (
|
||||
<Inner
|
||||
currentAccount={currentAccount}
|
||||
@@ -174,7 +168,7 @@ function Inner({
|
||||
|
||||
return (
|
||||
<Context.Provider value={ctx}>
|
||||
{activeNux === Nux.NeueTypography && <NeueTypography />}
|
||||
{/*For example, activeNux === Nux.NeueTypography && <NeueTypography />*/}
|
||||
</Context.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ export function ActionsWrapper({
|
||||
const scale = useSharedValue(1)
|
||||
|
||||
const animatedStyle = useAnimatedStyle(() => ({
|
||||
transform: [{scale: scale.value}],
|
||||
transform: [{scale: scale.get()}],
|
||||
}))
|
||||
|
||||
const open = React.useCallback(() => {
|
||||
@@ -46,7 +46,7 @@ export function ActionsWrapper({
|
||||
const shrink = React.useCallback(() => {
|
||||
'worklet'
|
||||
cancelAnimation(scale)
|
||||
scale.value = withTiming(1, {duration: 200})
|
||||
scale.set(() => withTiming(1, {duration: 200}))
|
||||
}, [scale])
|
||||
|
||||
const doubleTapGesture = Gesture.Tap()
|
||||
@@ -58,11 +58,13 @@ export function ActionsWrapper({
|
||||
const pressAndHoldGesture = Gesture.LongPress()
|
||||
.onStart(() => {
|
||||
'worklet'
|
||||
scale.value = withTiming(1.05, {duration: 200}, finished => {
|
||||
if (!finished) return
|
||||
runOnJS(open)()
|
||||
shrink()
|
||||
})
|
||||
scale.set(() =>
|
||||
withTiming(1.05, {duration: 200}, finished => {
|
||||
if (!finished) return
|
||||
runOnJS(open)()
|
||||
shrink()
|
||||
}),
|
||||
)
|
||||
})
|
||||
.onTouchesUp(shrink)
|
||||
.onTouchesMove(shrink)
|
||||
|
||||
@@ -42,12 +42,12 @@ export function ChatEmptyPill() {
|
||||
|
||||
const onPressIn = React.useCallback(() => {
|
||||
if (isWeb) return
|
||||
scale.value = withTiming(1.075, {duration: 100})
|
||||
scale.set(() => withTiming(1.075, {duration: 100}))
|
||||
}, [scale])
|
||||
|
||||
const onPressOut = React.useCallback(() => {
|
||||
if (isWeb) return
|
||||
scale.value = withTiming(1, {duration: 100})
|
||||
scale.set(() => withTiming(1, {duration: 100}))
|
||||
}, [scale])
|
||||
|
||||
const onPress = React.useCallback(() => {
|
||||
@@ -61,7 +61,7 @@ export function ChatEmptyPill() {
|
||||
}, [playHaptic, prompts.length])
|
||||
|
||||
const animatedStyle = useAnimatedStyle(() => ({
|
||||
transform: [{scale: scale.value}],
|
||||
transform: [{scale: scale.get()}],
|
||||
}))
|
||||
|
||||
return (
|
||||
|
||||
@@ -115,7 +115,7 @@ let ConvoMenu = ({
|
||||
{...props}
|
||||
onPress={() => {
|
||||
Keyboard.dismiss()
|
||||
// eslint-disable-next-line react/prop-types -- eslint is confused by the name `props`
|
||||
|
||||
props.onPress()
|
||||
}}
|
||||
style={[
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {useNavigation} from '@react-navigation/native'
|
||||
|
||||
@@ -19,10 +19,11 @@ import {ConvoItem} from '#/state/messages/convo/types'
|
||||
import {useSession} from '#/state/session'
|
||||
import {TimeElapsed} from '#/view/com/util/TimeElapsed'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {isOnlyEmoji} from '#/alf/typography'
|
||||
import {ActionsWrapper} from '#/components/dms/ActionsWrapper'
|
||||
import {InlineLinkText} from '#/components/Link'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {isOnlyEmoji, RichText} from '../RichText'
|
||||
import {RichText} from '../RichText'
|
||||
import {DateDivider} from './DateDivider'
|
||||
import {MessageItemEmbed} from './MessageItemEmbed'
|
||||
import {localDateString} from './util'
|
||||
|
||||
@@ -62,7 +62,7 @@ export let MessageMenu = ({
|
||||
message.text,
|
||||
langPrefs.primaryLanguage,
|
||||
)
|
||||
openLink(translatorUrl)
|
||||
openLink(translatorUrl, true)
|
||||
}, [langPrefs.primaryLanguage, message.text, openLink])
|
||||
|
||||
const onDelete = React.useCallback(() => {
|
||||
|
||||
@@ -3,14 +3,18 @@ import {View} from 'react-native'
|
||||
import {AppBskyActorDefs} from '@atproto/api'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {useNavigation} from '@react-navigation/native'
|
||||
|
||||
import {useEmail} from '#/lib/hooks/useEmail'
|
||||
import {NavigationProp} from '#/lib/routes/types'
|
||||
import {logEvent} from '#/lib/statsig/statsig'
|
||||
import {useMaybeConvoForUser} from '#/state/queries/messages/get-convo-for-members'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {ButtonIcon} from '#/components/Button'
|
||||
import {Button, ButtonIcon} from '#/components/Button'
|
||||
import {canBeMessaged} from '#/components/dms/util'
|
||||
import {Message_Stroke2_Corner0_Rounded as Message} from '#/components/icons/Message'
|
||||
import {Link} from '#/components/Link'
|
||||
import {useDialogControl} from '../Dialog'
|
||||
import {VerifyEmailDialog} from '../dialogs/VerifyEmailDialog'
|
||||
|
||||
export function MessageProfileButton({
|
||||
profile,
|
||||
@@ -19,15 +23,29 @@ export function MessageProfileButton({
|
||||
}) {
|
||||
const {_} = useLingui()
|
||||
const t = useTheme()
|
||||
const navigation = useNavigation<NavigationProp>()
|
||||
const {needsEmailVerification} = useEmail()
|
||||
const verifyEmailControl = useDialogControl()
|
||||
|
||||
const {data: convo, isPending} = useMaybeConvoForUser(profile.did)
|
||||
|
||||
const onPress = React.useCallback(() => {
|
||||
if (!convo?.id) {
|
||||
return
|
||||
}
|
||||
|
||||
if (needsEmailVerification) {
|
||||
verifyEmailControl.open()
|
||||
return
|
||||
}
|
||||
|
||||
if (convo && !convo.lastMessage) {
|
||||
logEvent('chat:create', {logContext: 'ProfileHeader'})
|
||||
}
|
||||
logEvent('chat:open', {logContext: 'ProfileHeader'})
|
||||
}, [convo])
|
||||
|
||||
navigation.navigate('MessagesConversation', {conversation: convo.id})
|
||||
}, [needsEmailVerification, verifyEmailControl, convo, navigation])
|
||||
|
||||
if (isPending) {
|
||||
// show pending state based on declaration
|
||||
@@ -53,18 +71,26 @@ export function MessageProfileButton({
|
||||
|
||||
if (convo) {
|
||||
return (
|
||||
<Link
|
||||
testID="dmBtn"
|
||||
size="small"
|
||||
color="secondary"
|
||||
variant="solid"
|
||||
shape="round"
|
||||
label={_(msg`Message ${profile.handle}`)}
|
||||
to={`/messages/${convo.id}`}
|
||||
style={[a.justify_center]}
|
||||
onPress={onPress}>
|
||||
<ButtonIcon icon={Message} size="md" />
|
||||
</Link>
|
||||
<>
|
||||
<Button
|
||||
accessibilityRole="button"
|
||||
testID="dmBtn"
|
||||
size="small"
|
||||
color="secondary"
|
||||
variant="solid"
|
||||
shape="round"
|
||||
label={_(msg`Message ${profile.handle}`)}
|
||||
style={[a.justify_center]}
|
||||
onPress={onPress}>
|
||||
<ButtonIcon icon={Message} size="md" />
|
||||
</Button>
|
||||
<VerifyEmailDialog
|
||||
reasonText={_(
|
||||
msg`Before you may message another user, you must first verify your email.`,
|
||||
)}
|
||||
control={verifyEmailControl}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
} else {
|
||||
return null
|
||||
|
||||
@@ -35,12 +35,12 @@ export function NewMessagesPill({
|
||||
|
||||
const onPressIn = React.useCallback(() => {
|
||||
if (isWeb) return
|
||||
scale.value = withTiming(1.075, {duration: 100})
|
||||
scale.set(() => withTiming(1.075, {duration: 100}))
|
||||
}, [scale])
|
||||
|
||||
const onPressOut = React.useCallback(() => {
|
||||
if (isWeb) return
|
||||
scale.value = withTiming(1, {duration: 100})
|
||||
scale.set(() => withTiming(1, {duration: 100}))
|
||||
}, [scale])
|
||||
|
||||
const onPress = React.useCallback(() => {
|
||||
@@ -49,7 +49,7 @@ export function NewMessagesPill({
|
||||
}, [onPressInner, playHaptic])
|
||||
|
||||
const animatedStyle = useAnimatedStyle(() => ({
|
||||
transform: [{scale: scale.value}],
|
||||
transform: [{scale: scale.get()}],
|
||||
}))
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import React, {useCallback} from 'react'
|
||||
import {useCallback} from 'react'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {useEmail} from '#/lib/hooks/useEmail'
|
||||
import {logEvent} from '#/lib/statsig/statsig'
|
||||
import {logger} from '#/logger'
|
||||
import {useGetConvoForMembers} from '#/state/queries/messages/get-convo-for-members'
|
||||
@@ -9,6 +10,8 @@ import {FAB} from '#/view/com/util/fab/FAB'
|
||||
import * as Toast from '#/view/com/util/Toast'
|
||||
import {useTheme} from '#/alf'
|
||||
import * as Dialog from '#/components/Dialog'
|
||||
import {useDialogControl} from '#/components/Dialog'
|
||||
import {VerifyEmailDialog} from '#/components/dialogs/VerifyEmailDialog'
|
||||
import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus'
|
||||
import {SearchablePeopleList} from './SearchablePeopleList'
|
||||
|
||||
@@ -21,6 +24,8 @@ export function NewChat({
|
||||
}) {
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
const {needsEmailVerification} = useEmail()
|
||||
const verifyEmailControl = useDialogControl()
|
||||
|
||||
const {mutate: createChat} = useGetConvoForMembers({
|
||||
onSuccess: data => {
|
||||
@@ -48,7 +53,13 @@ export function NewChat({
|
||||
<>
|
||||
<FAB
|
||||
testID="newChatFAB"
|
||||
onPress={control.open}
|
||||
onPress={() => {
|
||||
if (needsEmailVerification) {
|
||||
verifyEmailControl.open()
|
||||
} else {
|
||||
control.open()
|
||||
}
|
||||
}}
|
||||
icon={<Plus size="lg" fill={t.palette.white} />}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={_(msg`New chat`)}
|
||||
@@ -62,6 +73,13 @@ export function NewChat({
|
||||
onSelectChat={onCreateChat}
|
||||
/>
|
||||
</Dialog.Outer>
|
||||
|
||||
<VerifyEmailDialog
|
||||
reasonText={_(
|
||||
msg`Before you may message another user, you must first verify your email.`,
|
||||
)}
|
||||
control={verifyEmailControl}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -278,7 +278,7 @@ export function SearchablePeopleList({
|
||||
) : null}
|
||||
</View>
|
||||
|
||||
<View style={[, web([a.pt_xs])]}>
|
||||
<View style={web([a.pt_xs])}>
|
||||
<SearchInput
|
||||
inputRef={inputRef}
|
||||
value={searchText}
|
||||
@@ -313,6 +313,7 @@ export function SearchablePeopleList({
|
||||
web([a.py_0, {height: '100vh', maxHeight: 600}, a.px_0]),
|
||||
native({height: '100%'}),
|
||||
]}
|
||||
webInnerContentContainerStyle={a.py_0}
|
||||
webInnerStyle={[a.py_0, {maxWidth: 500, minWidth: 200}]}
|
||||
keyboardDismissMode="on-drag"
|
||||
/>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, {useCallback} from 'react'
|
||||
import {useCallback} from 'react'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react'
|
||||
import {Pressable, View} from 'react-native'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react'
|
||||
import {View} from 'react-native'
|
||||
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
es,
|
||||
fi,
|
||||
fr,
|
||||
gl,
|
||||
hi,
|
||||
hu,
|
||||
id,
|
||||
@@ -23,11 +24,13 @@ import {
|
||||
ja,
|
||||
ko,
|
||||
nl,
|
||||
pl,
|
||||
ptBR,
|
||||
ru,
|
||||
th,
|
||||
tr,
|
||||
uk,
|
||||
vi,
|
||||
zhCN,
|
||||
zhHK,
|
||||
zhTW,
|
||||
@@ -48,6 +51,7 @@ const locales: Record<AppLanguage, Locale | undefined> = {
|
||||
fi,
|
||||
fr,
|
||||
ga: undefined,
|
||||
gl,
|
||||
hi,
|
||||
hu,
|
||||
id,
|
||||
@@ -55,11 +59,13 @@ const locales: Record<AppLanguage, Locale | undefined> = {
|
||||
ja,
|
||||
ko,
|
||||
nl,
|
||||
pl,
|
||||
['pt-BR']: ptBR,
|
||||
ru,
|
||||
th,
|
||||
tr,
|
||||
uk,
|
||||
vi,
|
||||
['zh-CN']: zhCN,
|
||||
['zh-HK']: zhHK,
|
||||
['zh-TW']: zhTW,
|
||||
|
||||
@@ -15,8 +15,8 @@ export function useFollowMethods({
|
||||
logContext,
|
||||
}: {
|
||||
profile: Shadow<AppBskyActorDefs.ProfileViewBasic>
|
||||
logContext: LogEvents['profile:follow:sampled']['logContext'] &
|
||||
LogEvents['profile:unfollow:sampled']['logContext']
|
||||
logContext: LogEvents['profile:follow']['logContext'] &
|
||||
LogEvents['profile:unfollow']['logContext']
|
||||
}) {
|
||||
const {_} = useLingui()
|
||||
const requireAuth = useRequireAuth()
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import {createSinglePathSVG} from './TEMPLATE'
|
||||
|
||||
export const CalendarClock_Stroke2_Corner0_Rounded = createSinglePathSVG({
|
||||
path: 'M15.439 3.148a1 1 0 0 1 .41.645l.568 3.22a7 7 0 1 1-6.174 10.97L4.32 19.027a1 1 0 0 1-1.159-.811L1.078 6.398a1 1 0 0 1 .81-1.158l12.803-2.258a1 1 0 0 1 .748.166ZM9.325 16.114A7 7 0 0 1 9 14c0-1.56.51-3 1.372-4.164l-6.456 1.139 1.041 5.909 4.368-.77ZM3.568 9.005l10.833-1.91-.347-1.97L3.22 7.036l.347 1.97ZM16 9a5 5 0 1 0 0 10 5 5 0 0 0 0-10Zm0 2a1 1 0 0 1 1 1v1.586l1.374 1.374a1 1 0 0 1-1.414 1.414l-1.667-1.667A1 1 0 0 1 15 14v-2a1 1 0 0 1 1-1Z',
|
||||
})
|
||||
@@ -0,0 +1,5 @@
|
||||
import {createSinglePathSVG} from './TEMPLATE'
|
||||
|
||||
export const ChainLink3_Stroke2_Corner0_Rounded = createSinglePathSVG({
|
||||
path: 'M18.535 5.465a5.003 5.003 0 0 0-7.076 0l-.005.005-.752.742a1 1 0 1 1-1.404-1.424l.749-.74a7.003 7.003 0 0 1 9.904 9.905l-.002.003-.737.746a1 1 0 1 1-1.424-1.404l.747-.757a5.003 5.003 0 0 0 0-7.076ZM6.202 9.288a1 1 0 0 1 .01 1.414l-.747.757a5.003 5.003 0 1 0 7.076 7.076l.005-.005.752-.742a1 1 0 1 1 1.404 1.424l-.746.737-.003.002a7.003 7.003 0 0 1-9.904-9.904l.74-.75a1 1 0 0 1 1.413-.009Zm8.505.005a1 1 0 0 1 0 1.414l-4 4a1 1 0 0 1-1.414-1.414l4-4a1 1 0 0 1 1.414 0Z',
|
||||
})
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react'
|
||||
import {StyleSheet, TextProps} from 'react-native'
|
||||
import type {PathProps, SvgProps} from 'react-native-svg'
|
||||
import {Defs, LinearGradient, Stop} from 'react-native-svg'
|
||||
|
||||
@@ -31,6 +31,37 @@ export function ContentHider({
|
||||
ignoreMute?: boolean
|
||||
style?: StyleProp<ViewStyle>
|
||||
childContainerStyle?: StyleProp<ViewStyle>
|
||||
}>) {
|
||||
const blur = modui?.blurs[0]
|
||||
if (!blur || (ignoreMute && isJustAMute(modui))) {
|
||||
return (
|
||||
<View testID={testID} style={style}>
|
||||
{children}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<ContentHiderActive
|
||||
testID={testID}
|
||||
modui={modui}
|
||||
style={style}
|
||||
childContainerStyle={childContainerStyle}>
|
||||
{children}
|
||||
</ContentHiderActive>
|
||||
)
|
||||
}
|
||||
|
||||
function ContentHiderActive({
|
||||
testID,
|
||||
modui,
|
||||
style,
|
||||
childContainerStyle,
|
||||
children,
|
||||
}: React.PropsWithChildren<{
|
||||
testID?: string
|
||||
modui: ModerationUI
|
||||
style?: StyleProp<ViewStyle>
|
||||
childContainerStyle?: StyleProp<ViewStyle>
|
||||
}>) {
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
@@ -40,7 +71,6 @@ export function ContentHider({
|
||||
const {labelDefs} = useLabelDefinitions()
|
||||
const globalLabelStrings = useGlobalLabelStrings()
|
||||
const {i18n} = useLingui()
|
||||
|
||||
const blur = modui?.blurs[0]
|
||||
const desc = useModerationCauseDescription(blur)
|
||||
|
||||
@@ -99,14 +129,6 @@ export function ContentHider({
|
||||
globalLabelStrings,
|
||||
])
|
||||
|
||||
if (!blur || (ignoreMute && isJustAMute(modui))) {
|
||||
return (
|
||||
<View testID={testID} style={style}>
|
||||
{children}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<View testID={testID} style={[a.overflow_hidden, style]}>
|
||||
<ModerationDetailsDialog control={control} modcause={blur} />
|
||||
|
||||
@@ -22,7 +22,7 @@ export function Outer({children}: React.PropsWithChildren<{}>) {
|
||||
<View
|
||||
style={[
|
||||
a.flex_row,
|
||||
a.gap_md,
|
||||
a.gap_sm,
|
||||
a.px_lg,
|
||||
a.py_lg,
|
||||
a.justify_between,
|
||||
@@ -74,10 +74,9 @@ export function Buttons({
|
||||
hideLabel?: string
|
||||
}) {
|
||||
const {_} = useLingui()
|
||||
const {gtPhone} = useBreakpoints()
|
||||
|
||||
return (
|
||||
<View style={[{minHeight: 35}, gtPhone ? undefined : a.w_full]}>
|
||||
<View style={[{minHeight: 35}, a.w_full]}>
|
||||
<ToggleButton.Group
|
||||
label={_(
|
||||
msg`Configure content filtering setting for category: ${name}`,
|
||||
@@ -259,7 +258,7 @@ export function LabelerLabelPreference({
|
||||
</Content>
|
||||
|
||||
{showConfig && (
|
||||
<View style={[gtPhone ? undefined : a.w_full]}>
|
||||
<>
|
||||
{cantConfigure ? (
|
||||
<View
|
||||
style={[
|
||||
@@ -290,7 +289,7 @@ export function LabelerLabelPreference({
|
||||
hideLabel={hideLabel}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
</Outer>
|
||||
)
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react'
|
||||
import {StyleProp, View, ViewStyle} from 'react-native'
|
||||
import {AppBskyFeedDefs, ComAtprotoLabelDefs} from '@atproto/api'
|
||||
import {msg, Plural} from '@lingui/macro'
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {ModerationCause} from '@atproto/api'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react'
|
||||
import {StyleProp, ViewStyle} from 'react-native'
|
||||
import {ModerationCause, ModerationUI} from '@atproto/api'
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react'
|
||||
import {StyleProp, ViewStyle} from 'react-native'
|
||||
import {ModerationDecision} from '@atproto/api'
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react'
|
||||
import {View} from 'react-native'
|
||||
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
@@ -10,39 +9,23 @@ export function PlayButtonIcon({size = 32}: {size?: number}) {
|
||||
const fg = t.name === 'light' ? t.palette.contrast_975 : t.palette.contrast_25
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
a.rounded_full,
|
||||
a.overflow_hidden,
|
||||
a.align_center,
|
||||
a.justify_center,
|
||||
t.atoms.shadow_lg,
|
||||
{
|
||||
width: size + size / 1.5,
|
||||
height: size + size / 1.5,
|
||||
},
|
||||
]}>
|
||||
<>
|
||||
<View
|
||||
style={[
|
||||
a.absolute,
|
||||
a.inset_0,
|
||||
a.rounded_full,
|
||||
{
|
||||
backgroundColor: bg,
|
||||
shadowColor: 'black',
|
||||
shadowRadius: 32,
|
||||
shadowOpacity: 0.5,
|
||||
elevation: 24,
|
||||
width: size + size / 1.5,
|
||||
height: size + size / 1.5,
|
||||
opacity: 0.7,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<PlayIcon
|
||||
width={size}
|
||||
fill={fg}
|
||||
style={[
|
||||
a.relative,
|
||||
a.z_10,
|
||||
{
|
||||
left: size / 50,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</View>
|
||||
<PlayIcon width={size} fill={fg} style={a.absolute} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user