Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1dd047fb85 | |||
| 58cdc6589a | |||
| 4529d13558 | |||
| 16699d98a5 |
@@ -36,7 +36,7 @@
|
||||
},
|
||||
"src/components/Dialog/index.web.tsx": {
|
||||
"@typescript-eslint/no-explicit-any": {
|
||||
"count": 4
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"src/components/DraggableList/index.web.tsx": {
|
||||
|
||||
@@ -222,7 +222,6 @@
|
||||
"react-native-gesture-handler": "~2.28.0",
|
||||
"react-native-keyboard-controller": "^1.21.7",
|
||||
"react-native-pager-view": "6.8.0",
|
||||
"react-native-progress": "bluesky-social/react-native-progress",
|
||||
"react-native-qrcode-styled": "^0.3.3",
|
||||
"react-native-reanimated": "3.19.1",
|
||||
"react-native-safe-area-context": "~5.6.0",
|
||||
|
||||
Generated
-14
@@ -454,9 +454,6 @@ importers:
|
||||
react-native-pager-view:
|
||||
specifier: 6.8.0
|
||||
version: 6.8.0(patch_hash=0979d6fe1e2fa43b2784cc0b5e0ff0117d53b53423e85ee5855eefdc41a00108)(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||
react-native-progress:
|
||||
specifier: bluesky-social/react-native-progress
|
||||
version: https://codeload.github.com/bluesky-social/react-native-progress/tar.gz/5a372f4f2ce5feb26f4f47b6a4d187ab9b923ab4(react-native-svg@15.12.1(patch_hash=31401fa6ff01498773afb51a7b066821c471eb3e71b0764a10017938beed49e9)(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))
|
||||
react-native-qrcode-styled:
|
||||
specifier: ^0.3.3
|
||||
version: 0.3.3(react-native-svg@15.12.1(patch_hash=31401fa6ff01498773afb51a7b066821c471eb3e71b0764a10017938beed49e9)(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||
@@ -8686,12 +8683,6 @@ packages:
|
||||
react: '*'
|
||||
react-native: '*'
|
||||
|
||||
react-native-progress@https://codeload.github.com/bluesky-social/react-native-progress/tar.gz/5a372f4f2ce5feb26f4f47b6a4d187ab9b923ab4:
|
||||
resolution: {gitHosted: true, integrity: sha512-gDJJirvdhK5uUuI2OkiuleTbmeCyjkf1G6kPd4HAMc1pW8pUUeA0wKyPxZaI+s9htw8IDH6mFlmBB1ob8QMB4g==, tarball: https://codeload.github.com/bluesky-social/react-native-progress/tar.gz/5a372f4f2ce5feb26f4f47b6a4d187ab9b923ab4}
|
||||
version: 5.0.0
|
||||
peerDependencies:
|
||||
react-native-svg: '*'
|
||||
|
||||
react-native-qrcode-styled@0.3.3:
|
||||
resolution: {integrity: sha512-DjxFhFAJBY3y2FEBbWSpwtPaSjsEc5RutTHejxzfRNuyTzg1f5thplGyON8yx0kbi6h0h+57s8cH4aSFGj1mQg==}
|
||||
engines: {node: '>= 16.0.0'}
|
||||
@@ -19954,11 +19945,6 @@ snapshots:
|
||||
react: 19.1.0
|
||||
react-native: 0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0)
|
||||
|
||||
react-native-progress@https://codeload.github.com/bluesky-social/react-native-progress/tar.gz/5a372f4f2ce5feb26f4f47b6a4d187ab9b923ab4(react-native-svg@15.12.1(patch_hash=31401fa6ff01498773afb51a7b066821c471eb3e71b0764a10017938beed49e9)(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)):
|
||||
dependencies:
|
||||
prop-types: 15.8.1
|
||||
react-native-svg: 15.12.1(patch_hash=31401fa6ff01498773afb51a7b066821c471eb3e71b0764a10017938beed49e9)(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||
|
||||
react-native-qrcode-styled@0.3.3(react-native-svg@15.12.1(patch_hash=31401fa6ff01498773afb51a7b066821c471eb3e71b0764a10017938beed49e9)(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0):
|
||||
dependencies:
|
||||
qrcode: 1.5.4
|
||||
|
||||
@@ -15,7 +15,6 @@ import {
|
||||
Pressable,
|
||||
ScrollView,
|
||||
type StyleProp,
|
||||
TextInput,
|
||||
View,
|
||||
type ViewStyle,
|
||||
} from 'react-native'
|
||||
@@ -42,7 +41,7 @@ import {
|
||||
type DialogInnerProps,
|
||||
type DialogOuterProps,
|
||||
} from '#/components/Dialog/types'
|
||||
import {createInput} from '#/components/forms/TextField'
|
||||
import {Input} from '#/components/forms/TextField'
|
||||
import {useOnKeyboard} from '#/components/hooks/useOnKeyboard'
|
||||
import {IS_ANDROID, IS_IOS, IS_LIQUID_GLASS} from '#/env'
|
||||
import {BottomSheet, BottomSheetSnapPoint} from '../../../modules/bottom-sheet'
|
||||
@@ -51,13 +50,17 @@ import {
|
||||
type BottomSheetStateChangeEvent,
|
||||
} from '../../../modules/bottom-sheet/src/BottomSheet.types'
|
||||
import {type BottomSheetNativeComponent} from '../../../modules/bottom-sheet/src/BottomSheetNativeComponent'
|
||||
|
||||
export {useDialogContext, useDialogControl} from '#/components/Dialog/context'
|
||||
export * from '#/components/Dialog/shared'
|
||||
export * from '#/components/Dialog/types'
|
||||
export * from '#/components/Dialog/utils'
|
||||
|
||||
export const Input = createInput(TextInput)
|
||||
export {
|
||||
/**
|
||||
* @deprecated use `TextField.Input` from `#/components/forms/TextField` instead
|
||||
*/
|
||||
Input,
|
||||
}
|
||||
|
||||
export function Outer({
|
||||
children,
|
||||
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
type DialogInnerProps,
|
||||
type DialogOuterProps,
|
||||
} from '#/components/Dialog/types'
|
||||
import {Input} from '#/components/forms/TextField'
|
||||
import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times'
|
||||
import {Portal} from '#/components/Portal'
|
||||
|
||||
@@ -39,13 +40,20 @@ export {useDialogContext, useDialogControl} from '#/components/Dialog/context'
|
||||
export * from '#/components/Dialog/shared'
|
||||
export * from '#/components/Dialog/types'
|
||||
export * from '#/components/Dialog/utils'
|
||||
export {Input} from '#/components/forms/TextField'
|
||||
|
||||
export {
|
||||
/**
|
||||
* @deprecated use `TextField.Input` from `#/components/forms/TextField` instead
|
||||
*/
|
||||
Input,
|
||||
}
|
||||
|
||||
// 100 minus 10vh of paddingVertical
|
||||
export const WEB_DIALOG_HEIGHT = '80vh'
|
||||
|
||||
const stopPropagation = (e: any) => e.stopPropagation()
|
||||
const preventDefault = (e: any) => e.preventDefault()
|
||||
const stopPropagation = (e: {stopPropagation: () => void}) =>
|
||||
e.stopPropagation()
|
||||
const preventDefault = (e: {preventDefault: () => void}) => e.preventDefault()
|
||||
|
||||
export function Outer({
|
||||
children,
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
import {useEffect} from 'react'
|
||||
import Animated, {
|
||||
Easing,
|
||||
useAnimatedProps,
|
||||
useSharedValue,
|
||||
withTiming,
|
||||
} from 'react-native-reanimated'
|
||||
import Svg, {Circle as SvgCircle} from 'react-native-svg'
|
||||
|
||||
import {clamp01} from './shared'
|
||||
|
||||
const AnimatedCircle = Animated.createAnimatedComponent(SvgCircle)
|
||||
|
||||
export function ProgressCircle({
|
||||
progress,
|
||||
size,
|
||||
color,
|
||||
thickness = 3,
|
||||
borderWidth = 1,
|
||||
borderColor,
|
||||
unfilledColor,
|
||||
}: {
|
||||
progress: number
|
||||
size: number
|
||||
color: string
|
||||
thickness?: number
|
||||
borderWidth?: number
|
||||
borderColor?: string
|
||||
unfilledColor?: string
|
||||
}) {
|
||||
const stroke = borderColor ?? color
|
||||
const r = (size - Math.max(borderWidth, thickness)) / 2
|
||||
const cx = size / 2
|
||||
const circumference = 2 * Math.PI * r
|
||||
const sv = useSharedValue(clamp01(progress))
|
||||
|
||||
useEffect(() => {
|
||||
sv.set(
|
||||
withTiming(clamp01(progress), {
|
||||
duration: 200,
|
||||
easing: Easing.out(Easing.quad),
|
||||
}),
|
||||
)
|
||||
}, [progress, sv])
|
||||
|
||||
const animatedProps = useAnimatedProps(() => ({
|
||||
strokeDashoffset: circumference * (1 - sv.get()),
|
||||
}))
|
||||
|
||||
return (
|
||||
<Svg width={size} height={size}>
|
||||
{borderWidth > 0 && (
|
||||
<SvgCircle
|
||||
cx={cx}
|
||||
cy={cx}
|
||||
r={r}
|
||||
stroke={stroke}
|
||||
strokeWidth={borderWidth}
|
||||
fill="none"
|
||||
/>
|
||||
)}
|
||||
{unfilledColor && (
|
||||
<SvgCircle
|
||||
cx={cx}
|
||||
cy={cx}
|
||||
r={r}
|
||||
stroke={unfilledColor}
|
||||
strokeWidth={thickness}
|
||||
fill="none"
|
||||
/>
|
||||
)}
|
||||
<AnimatedCircle
|
||||
cx={cx}
|
||||
cy={cx}
|
||||
r={r}
|
||||
stroke={color}
|
||||
strokeWidth={thickness}
|
||||
fill="none"
|
||||
strokeDasharray={circumference}
|
||||
animatedProps={animatedProps}
|
||||
transform={`rotate(-90 ${cx} ${cx})`}
|
||||
/>
|
||||
</Svg>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import {useEffect} from 'react'
|
||||
import Animated, {
|
||||
Easing,
|
||||
useAnimatedProps,
|
||||
useSharedValue,
|
||||
withTiming,
|
||||
} from 'react-native-reanimated'
|
||||
import Svg, {Circle as SvgCircle, Path} from 'react-native-svg'
|
||||
|
||||
import {clamp01} from './shared'
|
||||
|
||||
const AnimatedPath = Animated.createAnimatedComponent(Path)
|
||||
|
||||
function describeWedge(cx: number, cy: number, r: number, progress: number) {
|
||||
'worklet'
|
||||
// avoid degenerate path at 360deg where start and end points collide
|
||||
const p = progress >= 1 ? 0.9999 : progress
|
||||
if (p <= 0) return ''
|
||||
const angle = p * 2 * Math.PI - Math.PI / 2
|
||||
const x = cx + r * Math.cos(angle)
|
||||
const y = cy + r * Math.sin(angle)
|
||||
const largeArc = p > 0.5 ? 1 : 0
|
||||
return `M ${cx} ${cy} L ${cx} ${cy - r} A ${r} ${r} 0 ${largeArc} 1 ${x} ${y} Z`
|
||||
}
|
||||
|
||||
export function ProgressPie({
|
||||
progress,
|
||||
size,
|
||||
color,
|
||||
borderWidth = 1,
|
||||
borderColor,
|
||||
}: {
|
||||
progress: number
|
||||
size: number
|
||||
color: string
|
||||
borderWidth?: number
|
||||
borderColor?: string
|
||||
}) {
|
||||
const stroke = borderColor ?? color
|
||||
const r = (size - borderWidth) / 2
|
||||
const cx = size / 2
|
||||
const sv = useSharedValue(clamp01(progress))
|
||||
|
||||
useEffect(() => {
|
||||
sv.set(
|
||||
withTiming(clamp01(progress), {
|
||||
duration: 200,
|
||||
easing: Easing.out(Easing.quad),
|
||||
}),
|
||||
)
|
||||
}, [progress, sv])
|
||||
|
||||
const animatedProps = useAnimatedProps(() => ({
|
||||
d: describeWedge(cx, cx, r, sv.get()),
|
||||
}))
|
||||
|
||||
return (
|
||||
<Svg width={size} height={size}>
|
||||
<SvgCircle
|
||||
cx={cx}
|
||||
cy={cx}
|
||||
r={r}
|
||||
stroke={stroke}
|
||||
strokeWidth={borderWidth}
|
||||
fill="none"
|
||||
/>
|
||||
<AnimatedPath fill={color} animatedProps={animatedProps} />
|
||||
</Svg>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export {ProgressCircle} from './Circle'
|
||||
export {ProgressPie} from './Pie'
|
||||
@@ -0,0 +1,6 @@
|
||||
export function clamp01(n: number) {
|
||||
'worklet'
|
||||
if (n < 0) return 0
|
||||
if (n > 1) return 1
|
||||
return n
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import {View} from 'react-native'
|
||||
import * as Progress from 'react-native-progress'
|
||||
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {AnimatedCheck} from '../anim/AnimatedCheck'
|
||||
import {ProgressCircle} from '../Progress'
|
||||
import {Text} from '../Typography'
|
||||
|
||||
export function ProgressGuideTask({
|
||||
@@ -25,7 +25,7 @@ export function ProgressGuideTask({
|
||||
{current === total ? (
|
||||
<AnimatedCheck playOnMount fill={t.palette.primary_500} width={20} />
|
||||
) : (
|
||||
<Progress.Circle
|
||||
<ProgressCircle
|
||||
progress={current / total}
|
||||
color={t.palette.primary_400}
|
||||
size={20}
|
||||
|
||||
@@ -24,6 +24,7 @@ import * as Admonition from '#/components/Admonition'
|
||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||
import * as Dialog from '#/components/Dialog'
|
||||
import {useGlobalDialogsControlContext} from '#/components/dialogs/Context'
|
||||
import * as TextField from '#/components/forms/TextField'
|
||||
import {useDelayedLoading} from '#/components/hooks/useDelayedLoading'
|
||||
import {ArrowRotateCounterClockwise_Stroke2_Corner0_Rounded as Retry} from '#/components/icons/ArrowRotate'
|
||||
import {
|
||||
@@ -540,30 +541,34 @@ function Inner(props: ReportDialogProps) {
|
||||
|
||||
{state.detailsOpen && (
|
||||
<View>
|
||||
<Dialog.Input
|
||||
testID="report:details"
|
||||
multiline
|
||||
value={state.details}
|
||||
onChangeText={details => {
|
||||
dispatch({type: 'setDetails', details})
|
||||
}}
|
||||
label={_(msg`Additional details (limit 300 characters)`)}
|
||||
style={{paddingRight: 60}}
|
||||
numberOfLines={4}
|
||||
/>
|
||||
<TextField.Root>
|
||||
<TextField.Input
|
||||
testID="report:details"
|
||||
multiline
|
||||
value={state.details}
|
||||
onChangeText={details => {
|
||||
dispatch({type: 'setDetails', details})
|
||||
}}
|
||||
label={_(
|
||||
msg`Additional details (limit 300 characters)`,
|
||||
)}
|
||||
style={{paddingRight: 50}}
|
||||
numberOfLines={4}
|
||||
/>
|
||||
</TextField.Root>
|
||||
|
||||
<View
|
||||
style={[
|
||||
a.absolute,
|
||||
a.flex_row,
|
||||
a.align_center,
|
||||
a.pr_md,
|
||||
a.pb_sm,
|
||||
{
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
},
|
||||
a.bottom_0,
|
||||
a.right_0,
|
||||
]}>
|
||||
<CharProgress count={state.details?.length || 0} />
|
||||
<CharProgress
|
||||
size={30}
|
||||
count={state.details?.length || 0}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
|
||||
@@ -177,14 +177,10 @@ export function Takendown() {
|
||||
<View
|
||||
style={[
|
||||
a.absolute,
|
||||
a.flex_row,
|
||||
a.align_center,
|
||||
a.pr_md,
|
||||
a.pb_sm,
|
||||
{
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
},
|
||||
a.bottom_0,
|
||||
a.right_0,
|
||||
]}>
|
||||
<CharProgress
|
||||
count={reasonGraphemeLength}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
import {View} from 'react-native'
|
||||
|
||||
import {MAX_ALT_TEXT} from '#/lib/constants'
|
||||
import {CharProgress} from '#/view/com/composer/char-progress/CharProgress'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
|
||||
export function AltTextCounterWrapper({
|
||||
altText,
|
||||
children,
|
||||
}: {
|
||||
altText?: string
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
const t = useTheme()
|
||||
return (
|
||||
<View style={[a.flex_row]}>
|
||||
<CharProgress
|
||||
style={[
|
||||
a.flex_col_reverse,
|
||||
a.align_center,
|
||||
a.mr_xs,
|
||||
{minWidth: 50, gap: 1},
|
||||
]}
|
||||
textStyle={[{marginRight: 0}, a.text_sm, t.atoms.text_contrast_medium]}
|
||||
size={26}
|
||||
count={altText?.length || 0}
|
||||
max={MAX_ALT_TEXT}
|
||||
/>
|
||||
{children}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
@@ -21,8 +21,6 @@ import {
|
||||
View,
|
||||
type ViewStyle,
|
||||
} from 'react-native'
|
||||
// @ts-expect-error no type definition
|
||||
import ProgressCircle from 'react-native-progress/Circle'
|
||||
import Animated, {
|
||||
type AnimatedRef,
|
||||
Easing,
|
||||
@@ -129,6 +127,7 @@ import {EmojiArc_Stroke2_Corner0_Rounded as EmojiSmileIcon} from '#/components/i
|
||||
import {PlusLarge_Stroke2_Corner0_Rounded as PlusIcon} from '#/components/icons/Plus'
|
||||
import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times'
|
||||
import {LazyQuoteEmbed} from '#/components/Post/Embed/LazyQuoteEmbed'
|
||||
import {ProgressCircle} from '#/components/Progress'
|
||||
import * as Prompt from '#/components/Prompt'
|
||||
import * as Toast from '#/components/Toast'
|
||||
import {Text} from '#/components/Typography'
|
||||
@@ -2072,10 +2071,7 @@ function ComposerFooter({
|
||||
onSelectLanguage={onSelectLanguage}
|
||||
nudgeAt={languageNudgeAt}
|
||||
/>
|
||||
<CharProgress
|
||||
count={post.shortenedGraphemeLength}
|
||||
style={{width: 65}}
|
||||
/>
|
||||
<CharProgress count={post.shortenedGraphemeLength} />
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
@@ -2413,7 +2409,7 @@ function ToolbarWrapper({
|
||||
if (IS_WEB) return children
|
||||
return (
|
||||
<Animated.View
|
||||
style={style}
|
||||
style={[style, {minHeight: 40}]}
|
||||
entering={FadeIn.duration(400)}
|
||||
exiting={FadeOut.duration(400)}>
|
||||
{children}
|
||||
@@ -2488,12 +2484,12 @@ function VideoUploadToolbar({state}: {state: VideoState}) {
|
||||
}
|
||||
|
||||
return (
|
||||
<ToolbarWrapper style={[a.flex_row, a.align_center, {paddingVertical: 5}]}>
|
||||
<ToolbarWrapper style={[a.flex_row, a.align_center]}>
|
||||
<Animated.View style={[animatedStyle]}>
|
||||
<ProgressCircle
|
||||
size={30}
|
||||
borderWidth={1}
|
||||
borderColor={t.atoms.border_contrast_low.borderColor}
|
||||
size={32}
|
||||
borderWidth={3}
|
||||
borderColor={t.palette.contrast_50}
|
||||
color={
|
||||
state.status === 'error'
|
||||
? t.palette.negative_500
|
||||
|
||||
@@ -3,6 +3,7 @@ import {TouchableOpacity, View} from 'react-native'
|
||||
import {msg} from '@lingui/core/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {Plural, Trans} from '@lingui/react/macro'
|
||||
import {countGraphemes} from 'unicode-segmenter/grapheme'
|
||||
|
||||
import {HITSLOP_10, MAX_ALT_TEXT} from '#/lib/constants'
|
||||
import {parseAltFromGIFDescription} from '#/lib/gif-alt-text'
|
||||
@@ -11,7 +12,6 @@ import {
|
||||
parseEmbedPlayerFromUrl,
|
||||
} from '#/lib/strings/embed-player'
|
||||
import {useResolveGifQuery} from '#/state/queries/resolve-link'
|
||||
import {AltTextCounterWrapper} from '#/view/com/composer/AltTextCounterWrapper'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {Admonition} from '#/components/Admonition'
|
||||
import {Button, ButtonText} from '#/components/Button'
|
||||
@@ -146,6 +146,8 @@ function AltTextInner({
|
||||
const t = useTheme()
|
||||
const {_, i18n} = useLingui()
|
||||
|
||||
const altTextLength = countGraphemes(altText)
|
||||
|
||||
return (
|
||||
<Dialog.ScrollableInner label={_(msg`Add alt text`)}>
|
||||
<View style={a.flex_col_reverse}>
|
||||
@@ -153,7 +155,9 @@ function AltTextInner({
|
||||
<View style={[a.gap_sm]}>
|
||||
<View style={[a.relative]}>
|
||||
<TextField.LabelText>
|
||||
<Trans>Descriptive alt text</Trans>
|
||||
<Trans>
|
||||
Descriptive alt text ({altTextLength}/{MAX_ALT_TEXT})
|
||||
</Trans>
|
||||
</TextField.LabelText>
|
||||
<TextField.Root>
|
||||
<Dialog.Input
|
||||
@@ -173,9 +177,9 @@ function AltTextInner({
|
||||
</TextField.Root>
|
||||
</View>
|
||||
|
||||
{altText.length > MAX_ALT_TEXT && (
|
||||
{altTextLength > MAX_ALT_TEXT && (
|
||||
<View style={[a.pb_sm, a.flex_row, a.gap_xs]}>
|
||||
<CircleInfo fill={t.palette.negative_500} />
|
||||
<CircleInfo fill={t.palette.negative_500} size="sm" />
|
||||
<Text
|
||||
style={[
|
||||
a.italic,
|
||||
@@ -194,21 +198,17 @@ function AltTextInner({
|
||||
)}
|
||||
</View>
|
||||
|
||||
<AltTextCounterWrapper altText={altText}>
|
||||
<Button
|
||||
label={_(msg`Save`)}
|
||||
size="large"
|
||||
color="primary"
|
||||
variant="solid"
|
||||
onPress={() => {
|
||||
control.close()
|
||||
}}
|
||||
style={[a.flex_grow]}>
|
||||
<ButtonText>
|
||||
<Trans>Save</Trans>
|
||||
</ButtonText>
|
||||
</Button>
|
||||
</AltTextCounterWrapper>
|
||||
<Button
|
||||
label={_(msg`Save`)}
|
||||
size="large"
|
||||
color="primary"
|
||||
onPress={() => {
|
||||
control.close()
|
||||
}}>
|
||||
<ButtonText>
|
||||
<Trans>Save</Trans>
|
||||
</ButtonText>
|
||||
</Button>
|
||||
</View>
|
||||
{/* below the text input to force tab order */}
|
||||
<View>
|
||||
|
||||
@@ -4,18 +4,16 @@ import {
|
||||
View,
|
||||
type ViewStyle,
|
||||
} from 'react-native'
|
||||
// @ts-ignore no type definition -prf
|
||||
import ProgressCircle from 'react-native-progress/Circle'
|
||||
// @ts-ignore no type definition -prf
|
||||
import ProgressPie from 'react-native-progress/Pie'
|
||||
|
||||
import {MAX_GRAPHEME_LENGTH} from '#/lib/constants'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {ProgressCircle} from '#/components/Progress'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {IS_WEB} from '#/env'
|
||||
|
||||
export function CharProgress({
|
||||
count,
|
||||
max,
|
||||
max = MAX_GRAPHEME_LENGTH,
|
||||
style,
|
||||
textStyle,
|
||||
size,
|
||||
@@ -26,40 +24,63 @@ export function CharProgress({
|
||||
textStyle?: StyleProp<TextStyle>
|
||||
size?: number
|
||||
}) {
|
||||
const maxLength = max || MAX_GRAPHEME_LENGTH
|
||||
const t = useTheme()
|
||||
const textColor = count > maxLength ? '#e60000' : t.atoms.text.color
|
||||
const circleColor = count > maxLength ? '#e60000' : t.palette.primary_500
|
||||
const textColor = count > max ? '#e60000' : t.atoms.text.color
|
||||
const circleColor = count > max ? '#e60000' : t.palette.primary_500
|
||||
|
||||
//
|
||||
if (IS_WEB && count > max) {
|
||||
return (
|
||||
<View style={[style, {minHeight: size}]}>
|
||||
<Text
|
||||
style={[
|
||||
{color: textColor, fontVariant: ['tabular-nums']},
|
||||
a.text_center,
|
||||
a.text_xs,
|
||||
{maxWidth: '100%'},
|
||||
textStyle,
|
||||
]}
|
||||
maxFontSizeMultiplier={1}
|
||||
numberOfLines={1}>
|
||||
{max - count}
|
||||
</Text>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[a.flex_row, a.align_center, a.justify_between, a.gap_sm, style]}>
|
||||
<Text
|
||||
style={[
|
||||
{color: textColor, fontVariant: ['tabular-nums']},
|
||||
a.flex_grow,
|
||||
a.text_right,
|
||||
textStyle,
|
||||
]}
|
||||
maxFontSizeMultiplier={1}>
|
||||
{maxLength - count}
|
||||
</Text>
|
||||
{count > maxLength ? (
|
||||
<ProgressPie
|
||||
size={size ?? 30}
|
||||
borderWidth={4}
|
||||
borderColor={circleColor}
|
||||
color={circleColor}
|
||||
progress={Math.min((count - maxLength) / maxLength, 1)}
|
||||
/>
|
||||
) : (
|
||||
<ProgressCircle
|
||||
size={size ?? 30}
|
||||
borderWidth={1}
|
||||
borderColor={t.atoms.border_contrast_low.borderColor}
|
||||
color={circleColor}
|
||||
progress={count / maxLength}
|
||||
/>
|
||||
<View style={[style]}>
|
||||
{max - count < 100 && (
|
||||
<View
|
||||
style={[
|
||||
a.absolute,
|
||||
a.inset_0,
|
||||
a.justify_center,
|
||||
a.align_center,
|
||||
a.px_xs,
|
||||
]}>
|
||||
<Text
|
||||
style={[
|
||||
{color: textColor, fontVariant: ['tabular-nums']},
|
||||
a.text_center,
|
||||
a.text_xs,
|
||||
{maxWidth: '100%'},
|
||||
textStyle,
|
||||
]}
|
||||
maxFontSizeMultiplier={1}
|
||||
adjustsFontSizeToFit
|
||||
numberOfLines={1}>
|
||||
{max - count}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
<ProgressCircle
|
||||
size={size ?? 32}
|
||||
borderWidth={3}
|
||||
borderColor={t.palette.contrast_50}
|
||||
color={circleColor}
|
||||
progress={Math.min(count / max, 1)}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,11 +4,11 @@ import {Image} from 'expo-image'
|
||||
import {msg} from '@lingui/core/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {Plural, Trans} from '@lingui/react/macro'
|
||||
import {countGraphemes} from 'unicode-segmenter'
|
||||
|
||||
import {MAX_ALT_TEXT} from '#/lib/constants'
|
||||
import {enforceLen} from '#/lib/strings/helpers'
|
||||
import {type ComposerImage} from '#/state/gallery'
|
||||
import {AltTextCounterWrapper} from '#/view/com/composer/AltTextCounterWrapper'
|
||||
import {atoms as a, tokens, useTheme} from '#/alf'
|
||||
import {Button, ButtonText} from '#/components/Button'
|
||||
import * as Dialog from '#/components/Dialog'
|
||||
@@ -91,6 +91,8 @@ const ImageAltTextInner = ({
|
||||
}
|
||||
}, [image, screenWidth])
|
||||
|
||||
const altTextLength = countGraphemes(altText)
|
||||
|
||||
return (
|
||||
<Dialog.ScrollableInner label={_(msg`Add alt text`)}>
|
||||
<Dialog.Close />
|
||||
@@ -121,7 +123,9 @@ const ImageAltTextInner = ({
|
||||
<View style={[a.gap_sm]}>
|
||||
<View style={[a.relative, {width: '100%'}]}>
|
||||
<TextField.LabelText>
|
||||
<Trans>Descriptive alt text</Trans>
|
||||
<Trans>
|
||||
Descriptive alt text ({altTextLength}/{MAX_ALT_TEXT})
|
||||
</Trans>
|
||||
</TextField.LabelText>
|
||||
<TextField.Root>
|
||||
<Dialog.Input
|
||||
@@ -137,9 +141,9 @@ const ImageAltTextInner = ({
|
||||
</TextField.Root>
|
||||
</View>
|
||||
|
||||
{altText.length > MAX_ALT_TEXT && (
|
||||
{altTextLength > MAX_ALT_TEXT && (
|
||||
<View style={[a.pb_sm, a.flex_row, a.gap_xs]}>
|
||||
<CircleInfo fill={t.palette.negative_500} />
|
||||
<CircleInfo fill={t.palette.negative_500} size="sm" />
|
||||
<Text
|
||||
style={[
|
||||
a.italic,
|
||||
@@ -158,22 +162,20 @@ const ImageAltTextInner = ({
|
||||
)}
|
||||
</View>
|
||||
|
||||
<AltTextCounterWrapper altText={altText}>
|
||||
<Button
|
||||
label={_(msg`Save`)}
|
||||
disabled={altText === image.alt}
|
||||
size="large"
|
||||
color="primary"
|
||||
variant="solid"
|
||||
onPress={() => {
|
||||
control.close()
|
||||
}}
|
||||
style={[a.flex_grow]}>
|
||||
<ButtonText>
|
||||
<Trans>Save</Trans>
|
||||
</ButtonText>
|
||||
</Button>
|
||||
</AltTextCounterWrapper>
|
||||
<Button
|
||||
label={_(msg`Save`)}
|
||||
disabled={altText === image.alt}
|
||||
size="large"
|
||||
color="primary"
|
||||
variant="solid"
|
||||
onPress={() => {
|
||||
control.close()
|
||||
}}
|
||||
style={[a.flex_grow]}>
|
||||
<ButtonText>
|
||||
<Trans>Save</Trans>
|
||||
</ButtonText>
|
||||
</Button>
|
||||
</View>
|
||||
</Dialog.ScrollableInner>
|
||||
)
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import {View} from 'react-native'
|
||||
// @ts-expect-error no type definition
|
||||
import ProgressPie from 'react-native-progress/Pie'
|
||||
import {type ImagePickerAsset} from 'expo-image-picker'
|
||||
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {ConstrainedImage} from '#/components/images/AutoSizedImage'
|
||||
import {ProgressPie} from '#/components/Progress'
|
||||
import {IS_WEB} from '#/env'
|
||||
import {ExternalEmbedRemoveBtn} from '../ExternalEmbedRemoveBtn'
|
||||
import {VideoTranscodeBackdrop} from './VideoTranscodeBackdrop'
|
||||
|
||||
Reference in New Issue
Block a user