Merge branch 'main' into safelink
This commit is contained in:
@@ -58,6 +58,7 @@ buck-out/
|
|||||||
# Ruby / CocoaPods
|
# Ruby / CocoaPods
|
||||||
/ios/Pods/
|
/ios/Pods/
|
||||||
/vendor/bundle/
|
/vendor/bundle/
|
||||||
|
Gemfile.lock
|
||||||
|
|
||||||
# Testing
|
# Testing
|
||||||
coverage/
|
coverage/
|
||||||
|
|||||||
+1
-1
@@ -146,7 +146,7 @@
|
|||||||
"expo-image": "^2.4.0",
|
"expo-image": "^2.4.0",
|
||||||
"expo-image-crop-tool": "^0.1.8",
|
"expo-image-crop-tool": "^0.1.8",
|
||||||
"expo-image-manipulator": "~13.1.7",
|
"expo-image-manipulator": "~13.1.7",
|
||||||
"expo-image-picker": "~16.1.4",
|
"expo-image-picker": "^17.0.2",
|
||||||
"expo-intent-launcher": "^12.1.5",
|
"expo-intent-launcher": "^12.1.5",
|
||||||
"expo-linear-gradient": "~14.1.5",
|
"expo-linear-gradient": "~14.1.5",
|
||||||
"expo-linking": "~7.1.5",
|
"expo-linking": "~7.1.5",
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
diff --git a/node_modules/expo-image-picker/android/src/main/java/expo/modules/imagepicker/MediaHandler.kt b/node_modules/expo-image-picker/android/src/main/java/expo/modules/imagepicker/MediaHandler.kt
|
|
||||||
index c863fb8..cde8859 100644
|
|
||||||
--- a/node_modules/expo-image-picker/android/src/main/java/expo/modules/imagepicker/MediaHandler.kt
|
|
||||||
+++ b/node_modules/expo-image-picker/android/src/main/java/expo/modules/imagepicker/MediaHandler.kt
|
|
||||||
@@ -101,16 +101,30 @@ internal class MediaHandler(
|
|
||||||
val fileData = getAdditionalFileData(sourceUri)
|
|
||||||
val mimeType = getType(context.contentResolver, sourceUri)
|
|
||||||
|
|
||||||
+ // Extract basic metadata
|
|
||||||
+ var width = metadataRetriever.extractInt(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH)
|
|
||||||
+ var height = metadataRetriever.extractInt(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT)
|
|
||||||
+ val rotation = metadataRetriever.extractInt(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION)
|
|
||||||
+
|
|
||||||
+ // Android returns the encoded width/height which do not take the display rotation into
|
|
||||||
+ // account. For videos recorded in portrait mode the encoded dimensions are often landscape
|
|
||||||
+ // (e.g. 1920x1080) paired with a 90°/270° rotation flag. iOS adjusts these values before
|
|
||||||
+ // reporting them, so to keep the behaviour consistent across platforms we swap the width
|
|
||||||
+ // and height when the rotation indicates the video should be displayed in portrait.
|
|
||||||
+ if (rotation % 180 != 0) {
|
|
||||||
+ width = height.also { height = width }
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
return ImagePickerAsset(
|
|
||||||
type = MediaType.VIDEO,
|
|
||||||
uri = outputUri.toString(),
|
|
||||||
- width = metadataRetriever.extractInt(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH),
|
|
||||||
- height = metadataRetriever.extractInt(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT),
|
|
||||||
+ width = width,
|
|
||||||
+ height = height,
|
|
||||||
fileName = fileData?.fileName,
|
|
||||||
fileSize = fileData?.fileSize,
|
|
||||||
mimeType = mimeType,
|
|
||||||
duration = metadataRetriever.extractInt(MediaMetadataRetriever.METADATA_KEY_DURATION),
|
|
||||||
- rotation = metadataRetriever.extractInt(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION),
|
|
||||||
+ rotation = rotation,
|
|
||||||
assetId = sourceUri.getMediaStoreAssetId()
|
|
||||||
)
|
|
||||||
} catch (cause: FailedToExtractVideoMetadataException) {
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
# Expo Image Picker patch
|
|
||||||
|
|
||||||
Cherry-picked https://github.com/expo/expo/pull/37849
|
|
||||||
|
|
||||||
Remove when we update to a version that includes this commit.
|
|
||||||
@@ -146,6 +146,8 @@ export function Scrubber({
|
|||||||
const progress = scrubberActive ? seekPosition : currentTime
|
const progress = scrubberActive ? seekPosition : currentTime
|
||||||
const progressPercent = (progress / duration) * 100
|
const progressPercent = (progress / duration) * 100
|
||||||
|
|
||||||
|
if (duration < 3) return null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
testID="scrubber"
|
testID="scrubber"
|
||||||
|
|||||||
@@ -373,6 +373,7 @@ export function Controls({
|
|||||||
onPress={onPressPlayPause}
|
onPress={onPressPlayPause}
|
||||||
/>
|
/>
|
||||||
<View style={a.flex_1} />
|
<View style={a.flex_1} />
|
||||||
|
{Math.round(duration) > 0 && (
|
||||||
<Text
|
<Text
|
||||||
style={[
|
style={[
|
||||||
a.px_xs,
|
a.px_xs,
|
||||||
@@ -380,6 +381,7 @@ export function Controls({
|
|||||||
]}>
|
]}>
|
||||||
{formatTime(currentTime)} / {formatTime(duration)}
|
{formatTime(currentTime)} / {formatTime(duration)}
|
||||||
</Text>
|
</Text>
|
||||||
|
)}
|
||||||
{hasSubtitleTrack && (
|
{hasSubtitleTrack && (
|
||||||
<ControlButton
|
<ControlButton
|
||||||
active={subtitlesEnabled}
|
active={subtitlesEnabled}
|
||||||
|
|||||||
@@ -181,6 +181,10 @@ export const VIDEO_SERVICE = 'https://video.bsky.app'
|
|||||||
export const VIDEO_SERVICE_DID = 'did:web:video.bsky.app'
|
export const VIDEO_SERVICE_DID = 'did:web:video.bsky.app'
|
||||||
|
|
||||||
export const VIDEO_MAX_DURATION_MS = 3 * 60 * 1000 // 3 minutes in milliseconds
|
export const VIDEO_MAX_DURATION_MS = 3 * 60 * 1000 // 3 minutes in milliseconds
|
||||||
|
/**
|
||||||
|
* Maximum size of a video in megabytes, _not_ mebibytes. Backend uses
|
||||||
|
* ISO megabytes.
|
||||||
|
*/
|
||||||
export const VIDEO_MAX_SIZE = 1000 * 1000 * 100 // 100mb
|
export const VIDEO_MAX_SIZE = 1000 * 1000 * 100 // 100mb
|
||||||
|
|
||||||
export const SUPPORTED_MIME_TYPES = [
|
export const SUPPORTED_MIME_TYPES = [
|
||||||
|
|||||||
+2
-2
@@ -4,7 +4,6 @@ import {impactAsync, ImpactFeedbackStyle} from 'expo-haptics'
|
|||||||
|
|
||||||
import {isIOS, isWeb} from '#/platform/detection'
|
import {isIOS, isWeb} from '#/platform/detection'
|
||||||
import {useHapticsDisabled} from '#/state/preferences/disable-haptics'
|
import {useHapticsDisabled} from '#/state/preferences/disable-haptics'
|
||||||
import * as Toast from '#/view/com/util/Toast'
|
|
||||||
|
|
||||||
export function useHaptics() {
|
export function useHaptics() {
|
||||||
const isHapticsDisabled = useHapticsDisabled()
|
const isHapticsDisabled = useHapticsDisabled()
|
||||||
@@ -23,7 +22,8 @@ export function useHaptics() {
|
|||||||
|
|
||||||
// DEV ONLY - show a toast when a haptic is meant to fire on simulator
|
// DEV ONLY - show a toast when a haptic is meant to fire on simulator
|
||||||
if (__DEV__ && !Device.isDevice) {
|
if (__DEV__ && !Device.isDevice) {
|
||||||
Toast.show(`Buzzz!`)
|
// disabled because it's annoying
|
||||||
|
// Toast.show(`Buzzz!`)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[isHapticsDisabled],
|
[isHapticsDisabled],
|
||||||
|
|||||||
@@ -17,16 +17,12 @@ export async function openPicker(opts?: ImagePickerOptions) {
|
|||||||
exif: false,
|
exif: false,
|
||||||
mediaTypes: ['images'],
|
mediaTypes: ['images'],
|
||||||
quality: 1,
|
quality: 1,
|
||||||
|
selectionLimit: 1,
|
||||||
...opts,
|
...opts,
|
||||||
legacy: true,
|
legacy: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (response.assets && response.assets.length > 4) {
|
|
||||||
Toast.show(t`You may only select up to 4 images`, 'exclamation-circle')
|
|
||||||
}
|
|
||||||
|
|
||||||
return (response.assets ?? [])
|
return (response.assets ?? [])
|
||||||
.slice(0, 4)
|
|
||||||
.filter(asset => {
|
.filter(asset => {
|
||||||
if (asset.mimeType?.startsWith('image/')) return true
|
if (asset.mimeType?.startsWith('image/')) return true
|
||||||
Toast.show(t`Only image files are supported`, 'exclamation-circle')
|
Toast.show(t`Only image files are supported`, 'exclamation-circle')
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import {getVideoMetaData, Video} from 'react-native-compressor'
|
import {getVideoMetaData, Video} from 'react-native-compressor'
|
||||||
import {ImagePickerAsset} from 'expo-image-picker'
|
import {type ImagePickerAsset} from 'expo-image-picker'
|
||||||
|
|
||||||
import {SUPPORTED_MIME_TYPES, SupportedMimeTypes} from '#/lib/constants'
|
import {SUPPORTED_MIME_TYPES, type SupportedMimeTypes} from '#/lib/constants'
|
||||||
import {CompressedVideo} from './types'
|
import {type CompressedVideo} from './types'
|
||||||
import {extToMime} from './util'
|
import {extToMime} from './util'
|
||||||
|
|
||||||
const MIN_SIZE_FOR_COMPRESSION = 25 // 25mb
|
const MIN_SIZE_FOR_COMPRESSION = 25 // 25mb
|
||||||
@@ -20,6 +20,13 @@ export async function compressVideo(
|
|||||||
file.mimeType as SupportedMimeTypes,
|
file.mimeType as SupportedMimeTypes,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (file.mimeType === 'image/gif') {
|
||||||
|
// let's hope they're small enough that they don't need compression!
|
||||||
|
// this compression library doesn't support gifs
|
||||||
|
// worst case - server rejects them. I think that's fine -sfn
|
||||||
|
return {uri: file.uri, size: file.fileSize ?? -1, mimeType: 'image/gif'}
|
||||||
|
}
|
||||||
|
|
||||||
const minimumFileSizeForCompress = isAcceptableFormat
|
const minimumFileSizeForCompress = isAcceptableFormat
|
||||||
? MIN_SIZE_FOR_COMPRESSION
|
? MIN_SIZE_FOR_COMPRESSION
|
||||||
: 0
|
: 0
|
||||||
|
|||||||
+140
-120
@@ -153,7 +153,7 @@ msgstr ""
|
|||||||
msgid "{0} joined this week"
|
msgid "{0} joined this week"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/Scrubber.tsx:202
|
#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/Scrubber.tsx:204
|
||||||
msgid "{0} of {1}"
|
msgid "{0} of {1}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -687,7 +687,7 @@ msgstr ""
|
|||||||
msgid "Add another account"
|
msgid "Add another account"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/composer/Composer.tsx:780
|
#: src/view/com/composer/Composer.tsx:793
|
||||||
msgid "Add another post"
|
msgid "Add another post"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -705,6 +705,11 @@ msgstr ""
|
|||||||
msgid "Add emoji reaction"
|
msgid "Add emoji reaction"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#. Accessibility label for button in composer to add photos or a video to a post
|
||||||
|
#: src/view/com/composer/SelectMediaButton.tsx:482
|
||||||
|
msgid "Add media to post"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/moderation/ReportDialog/index.tsx:403
|
#: src/components/moderation/ReportDialog/index.tsx:403
|
||||||
#: src/components/moderation/ReportDialog/index.tsx:407
|
#: src/components/moderation/ReportDialog/index.tsx:407
|
||||||
msgid "Add more details (optional)"
|
msgid "Add more details (optional)"
|
||||||
@@ -718,7 +723,7 @@ msgstr ""
|
|||||||
msgid "Add muted words and tags"
|
msgid "Add muted words and tags"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/composer/Composer.tsx:1344
|
#: src/view/com/composer/Composer.tsx:1426
|
||||||
msgid "Add new post"
|
msgid "Add new post"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -788,7 +793,7 @@ msgstr ""
|
|||||||
msgid "Adult Content"
|
msgid "Adult Content"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/screens/Moderation/index.tsx:404
|
#: src/screens/Moderation/index.tsx:423
|
||||||
msgid "Adult content can only be enabled via the Web at <0>bsky.app</0>."
|
msgid "Adult content can only be enabled via the Web at <0>bsky.app</0>."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -801,7 +806,7 @@ msgstr ""
|
|||||||
msgid "Adult Content labels"
|
msgid "Adult Content labels"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/screens/Moderation/index.tsx:454
|
#: src/screens/Moderation/index.tsx:473
|
||||||
msgid "Advanced"
|
msgid "Advanced"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -888,7 +893,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: src/screens/Settings/AccessibilitySettings.tsx:54
|
#: src/screens/Settings/AccessibilitySettings.tsx:54
|
||||||
#: src/view/com/composer/GifAltText.tsx:154
|
#: src/view/com/composer/GifAltText.tsx:154
|
||||||
#: src/view/com/composer/photos/ImageAltTextDialog.tsx:118
|
#: src/view/com/composer/photos/ImageAltTextDialog.tsx:117
|
||||||
#: src/view/com/composer/videos/SubtitleDialog.tsx:40
|
#: src/view/com/composer/videos/SubtitleDialog.tsx:40
|
||||||
#: src/view/com/composer/videos/SubtitleDialog.tsx:55
|
#: src/view/com/composer/videos/SubtitleDialog.tsx:55
|
||||||
#: src/view/com/composer/videos/SubtitleDialog.tsx:101
|
#: src/view/com/composer/videos/SubtitleDialog.tsx:101
|
||||||
@@ -905,7 +910,7 @@ msgid "Alt text describes images for blind and low-vision users, and helps give
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/composer/GifAltText.tsx:179
|
#: src/view/com/composer/GifAltText.tsx:179
|
||||||
#: src/view/com/composer/photos/ImageAltTextDialog.tsx:139
|
#: src/view/com/composer/photos/ImageAltTextDialog.tsx:138
|
||||||
msgid "Alt text will be truncated. {MAX_ALT_TEXT, plural, other {Limit: {0} characters.}}"
|
msgid "Alt text will be truncated. {MAX_ALT_TEXT, plural, other {Limit: {0} characters.}}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -917,7 +922,7 @@ msgstr ""
|
|||||||
msgid "An error has occurred"
|
msgid "An error has occurred"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:420
|
#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:422
|
||||||
msgid "An error occurred"
|
msgid "An error occurred"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -945,10 +950,6 @@ msgstr ""
|
|||||||
msgid "An error occurred while saving the QR code!"
|
msgid "An error occurred while saving the QR code!"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/composer/videos/SelectVideoBtn.tsx:63
|
|
||||||
msgid "An error occurred while selecting the video"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/screens/StarterPack/StarterPackScreen.tsx:352
|
#: src/screens/StarterPack/StarterPackScreen.tsx:352
|
||||||
#: src/screens/StarterPack/StarterPackScreen.tsx:374
|
#: src/screens/StarterPack/StarterPackScreen.tsx:374
|
||||||
msgid "An error occurred while trying to follow all"
|
msgid "An error occurred while trying to follow all"
|
||||||
@@ -1164,11 +1165,11 @@ msgstr ""
|
|||||||
msgid "Are you sure you want to remove this from your feeds?"
|
msgid "Are you sure you want to remove this from your feeds?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/composer/Composer.tsx:729
|
#: src/view/com/composer/Composer.tsx:741
|
||||||
msgid "Are you sure you'd like to discard this draft?"
|
msgid "Are you sure you'd like to discard this draft?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/composer/Composer.tsx:914
|
#: src/view/com/composer/Composer.tsx:932
|
||||||
msgid "Are you sure you'd like to discard this post?"
|
msgid "Are you sure you'd like to discard this post?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1337,7 +1338,7 @@ msgstr ""
|
|||||||
msgid "Blocked"
|
msgid "Blocked"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/screens/Moderation/index.tsx:282
|
#: src/screens/Moderation/index.tsx:301
|
||||||
msgid "Blocked accounts"
|
msgid "Blocked accounts"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1557,8 +1558,8 @@ msgstr ""
|
|||||||
#: src/screens/Settings/Settings.tsx:289
|
#: src/screens/Settings/Settings.tsx:289
|
||||||
#: src/screens/Takendown.tsx:99
|
#: src/screens/Takendown.tsx:99
|
||||||
#: src/screens/Takendown.tsx:102
|
#: src/screens/Takendown.tsx:102
|
||||||
#: src/view/com/composer/Composer.tsx:969
|
#: src/view/com/composer/Composer.tsx:987
|
||||||
#: src/view/com/composer/Composer.tsx:980
|
#: src/view/com/composer/Composer.tsx:998
|
||||||
#: src/view/com/composer/photos/EditImageDialog.web.tsx:43
|
#: src/view/com/composer/photos/EditImageDialog.web.tsx:43
|
||||||
#: src/view/com/composer/photos/EditImageDialog.web.tsx:52
|
#: src/view/com/composer/photos/EditImageDialog.web.tsx:52
|
||||||
#: src/view/com/modals/ChangePassword.tsx:279
|
#: src/view/com/modals/ChangePassword.tsx:279
|
||||||
@@ -1904,7 +1905,7 @@ msgstr ""
|
|||||||
msgid "Close image"
|
msgid "Close image"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/lightbox/Lightbox.web.tsx:109
|
#: src/view/com/lightbox/Lightbox.web.tsx:110
|
||||||
msgid "Close image viewer"
|
msgid "Close image viewer"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1922,7 +1923,7 @@ msgstr ""
|
|||||||
msgid "Closes password update alert"
|
msgid "Closes password update alert"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/composer/Composer.tsx:977
|
#: src/view/com/composer/Composer.tsx:995
|
||||||
msgid "Closes post composer and discards post draft"
|
msgid "Closes post composer and discards post draft"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1980,7 +1981,7 @@ msgstr ""
|
|||||||
msgid "Compose new post"
|
msgid "Compose new post"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/composer/Composer.tsx:878
|
#: src/view/com/composer/Composer.tsx:896
|
||||||
msgid "Compose posts up to {0, plural, other {# characters}} in length"
|
msgid "Compose posts up to {0, plural, other {# characters}} in length"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1988,7 +1989,7 @@ msgstr ""
|
|||||||
msgid "Compose reply"
|
msgid "Compose reply"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/composer/Composer.tsx:1738
|
#: src/view/com/composer/Composer.tsx:1820
|
||||||
msgid "Compressing video..."
|
msgid "Compressing video..."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -2015,11 +2016,11 @@ msgstr ""
|
|||||||
msgid "Confirm delete account"
|
msgid "Confirm delete account"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/screens/Moderation/index.tsx:330
|
#: src/screens/Moderation/index.tsx:349
|
||||||
msgid "Confirm your age:"
|
msgid "Confirm your age:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/screens/Moderation/index.tsx:321
|
#: src/screens/Moderation/index.tsx:340
|
||||||
msgid "Confirm your birthdate"
|
msgid "Confirm your birthdate"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -2072,8 +2073,8 @@ msgstr ""
|
|||||||
msgid "Content Blocked"
|
msgid "Content Blocked"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/screens/Moderation/index.tsx:317
|
#: src/screens/Moderation/index.tsx:336
|
||||||
#: src/screens/Moderation/index.tsx:351
|
#: src/screens/Moderation/index.tsx:370
|
||||||
msgid "Content filters"
|
msgid "Content filters"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -2518,7 +2519,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: src/components/PostControls/PostMenu/PostMenuItems.tsx:678
|
#: src/components/PostControls/PostMenu/PostMenuItems.tsx:678
|
||||||
#: src/components/PostControls/PostMenu/PostMenuItems.tsx:680
|
#: src/components/PostControls/PostMenu/PostMenuItems.tsx:680
|
||||||
#: src/view/com/composer/Composer.tsx:888
|
#: src/view/com/composer/Composer.tsx:906
|
||||||
msgid "Delete post"
|
msgid "Delete post"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -2566,7 +2567,7 @@ msgid "Description"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/composer/GifAltText.tsx:150
|
#: src/view/com/composer/GifAltText.tsx:150
|
||||||
#: src/view/com/composer/photos/ImageAltTextDialog.tsx:114
|
#: src/view/com/composer/photos/ImageAltTextDialog.tsx:113
|
||||||
msgid "Descriptive alt text"
|
msgid "Descriptive alt text"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -2620,7 +2621,7 @@ msgstr ""
|
|||||||
msgid "Disable haptic feedback"
|
msgid "Disable haptic feedback"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:386
|
#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:388
|
||||||
msgid "Disable subtitles"
|
msgid "Disable subtitles"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -2629,13 +2630,13 @@ msgstr ""
|
|||||||
#: src/lib/moderation/useLabelBehaviorDescription.ts:68
|
#: src/lib/moderation/useLabelBehaviorDescription.ts:68
|
||||||
#: src/screens/Messages/Settings.tsx:155
|
#: src/screens/Messages/Settings.tsx:155
|
||||||
#: src/screens/Messages/Settings.tsx:158
|
#: src/screens/Messages/Settings.tsx:158
|
||||||
#: src/screens/Moderation/index.tsx:394
|
#: src/screens/Moderation/index.tsx:413
|
||||||
msgid "Disabled"
|
msgid "Disabled"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/screens/Profile/Header/EditProfileDialog.tsx:89
|
#: src/screens/Profile/Header/EditProfileDialog.tsx:89
|
||||||
#: src/view/com/composer/Composer.tsx:731
|
#: src/view/com/composer/Composer.tsx:743
|
||||||
#: src/view/com/composer/Composer.tsx:921
|
#: src/view/com/composer/Composer.tsx:939
|
||||||
msgid "Discard"
|
msgid "Discard"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -2643,11 +2644,11 @@ msgstr ""
|
|||||||
msgid "Discard changes?"
|
msgid "Discard changes?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/composer/Composer.tsx:728
|
#: src/view/com/composer/Composer.tsx:740
|
||||||
msgid "Discard draft?"
|
msgid "Discard draft?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/composer/Composer.tsx:913
|
#: src/view/com/composer/Composer.tsx:931
|
||||||
msgid "Discard post?"
|
msgid "Discard post?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -2673,7 +2674,7 @@ msgstr ""
|
|||||||
msgid "Dismiss"
|
msgid "Dismiss"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/composer/Composer.tsx:1662
|
#: src/view/com/composer/Composer.tsx:1744
|
||||||
msgid "Dismiss error"
|
msgid "Dismiss error"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -3002,7 +3003,7 @@ msgstr ""
|
|||||||
msgid "Enable {0} only"
|
msgid "Enable {0} only"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/screens/Moderation/index.tsx:381
|
#: src/screens/Moderation/index.tsx:400
|
||||||
msgid "Enable adult content"
|
msgid "Enable adult content"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -3028,7 +3029,7 @@ msgstr ""
|
|||||||
msgid "Enable push notifications"
|
msgid "Enable push notifications"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:387
|
#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:389
|
||||||
msgid "Enable subtitles"
|
msgid "Enable subtitles"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -3048,7 +3049,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: src/screens/Messages/Settings.tsx:146
|
#: src/screens/Messages/Settings.tsx:146
|
||||||
#: src/screens/Messages/Settings.tsx:149
|
#: src/screens/Messages/Settings.tsx:149
|
||||||
#: src/screens/Moderation/index.tsx:392
|
#: src/screens/Moderation/index.tsx:411
|
||||||
msgid "Enabled"
|
msgid "Enabled"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -3074,7 +3075,7 @@ msgstr ""
|
|||||||
msgid "Enter code"
|
msgid "Enter code"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:405
|
#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:407
|
||||||
msgid "Enter fullscreen"
|
msgid "Enter fullscreen"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -3119,7 +3120,7 @@ msgstr ""
|
|||||||
msgid "Entertainment"
|
msgid "Entertainment"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/composer/Composer.tsx:1747
|
#: src/view/com/composer/Composer.tsx:1829
|
||||||
#: src/view/com/util/error/ErrorScreen.tsx:42
|
#: src/view/com/util/error/ErrorScreen.tsx:42
|
||||||
msgid "Error"
|
msgid "Error"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -3194,7 +3195,7 @@ msgstr ""
|
|||||||
msgid "Excludes users you follow"
|
msgid "Excludes users you follow"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:404
|
#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:406
|
||||||
msgid "Exit fullscreen"
|
msgid "Exit fullscreen"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -3206,11 +3207,11 @@ msgstr ""
|
|||||||
msgid "Exits image cropping process"
|
msgid "Exits image cropping process"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/lightbox/Lightbox.web.tsx:110
|
#: src/view/com/lightbox/Lightbox.web.tsx:111
|
||||||
msgid "Exits image view"
|
msgid "Exits image view"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/lightbox/Lightbox.web.tsx:184
|
#: src/view/com/lightbox/Lightbox.web.tsx:185
|
||||||
msgid "Expand alt text"
|
msgid "Expand alt text"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -3810,10 +3811,6 @@ msgctxt "from-feed"
|
|||||||
msgid "From <0/>"
|
msgid "From <0/>"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/composer/photos/SelectPhotoBtn.tsx:50
|
|
||||||
msgid "Gallery"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/components/StarterPack/ProfileStarterPacks.tsx:307
|
#: src/components/StarterPack/ProfileStarterPacks.tsx:307
|
||||||
msgid "Generate a starter pack"
|
msgid "Generate a starter pack"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -3967,6 +3964,7 @@ msgstr ""
|
|||||||
#: src/components/ageAssurance/AgeAssuranceAdmonition.tsx:89
|
#: src/components/ageAssurance/AgeAssuranceAdmonition.tsx:89
|
||||||
#: src/components/ageAssurance/AgeRestrictedScreen.tsx:75
|
#: src/components/ageAssurance/AgeRestrictedScreen.tsx:75
|
||||||
#: src/components/ageAssurance/AgeRestrictedScreen.tsx:84
|
#: src/components/ageAssurance/AgeRestrictedScreen.tsx:84
|
||||||
|
#: src/screens/Moderation/index.tsx:214
|
||||||
msgid "Go to account settings"
|
msgid "Go to account settings"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -4176,7 +4174,7 @@ msgstr ""
|
|||||||
msgid "Hmm, we're having trouble finding this feed. It may have been deleted."
|
msgid "Hmm, we're having trouble finding this feed. It may have been deleted."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/screens/Moderation/index.tsx:59
|
#: src/screens/Moderation/index.tsx:60
|
||||||
msgid "Hmmmm, it seems we're having trouble loading this data. See below for more details. If this issue persists, please contact us."
|
msgid "Hmmmm, it seems we're having trouble loading this data. See below for more details. If this issue persists, please contact us."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -4236,7 +4234,7 @@ msgstr ""
|
|||||||
msgid "I understand"
|
msgid "I understand"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/lightbox/Lightbox.web.tsx:186
|
#: src/view/com/lightbox/Lightbox.web.tsx:187
|
||||||
msgid "If alt text is long, toggles alt text expanded state"
|
msgid "If alt text is long, toggles alt text expanded state"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -4382,7 +4380,7 @@ msgstr ""
|
|||||||
msgid "Interaction limited"
|
msgid "Interaction limited"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/screens/Moderation/index.tsx:222
|
#: src/screens/Moderation/index.tsx:241
|
||||||
msgid "Interaction settings"
|
msgid "Interaction settings"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -4451,7 +4449,7 @@ msgstr ""
|
|||||||
msgid "It's just you right now! Add more people to your starter pack by searching above."
|
msgid "It's just you right now! Add more people to your starter pack by searching above."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/composer/Composer.tsx:1681
|
#: src/view/com/composer/Composer.tsx:1763
|
||||||
msgid "Job ID: {0}"
|
msgid "Job ID: {0}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -4927,7 +4925,7 @@ msgstr ""
|
|||||||
msgid "Manage saved feeds"
|
msgid "Manage saved feeds"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/screens/Moderation/index.tsx:292
|
#: src/screens/Moderation/index.tsx:311
|
||||||
msgid "Manage verification settings"
|
msgid "Manage verification settings"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -5045,7 +5043,7 @@ msgid "Misleading Post"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Navigation.tsx:176
|
#: src/Navigation.tsx:176
|
||||||
#: src/screens/Moderation/index.tsx:99
|
#: src/screens/Moderation/index.tsx:100
|
||||||
#: src/screens/Settings/Settings.tsx:188
|
#: src/screens/Settings/Settings.tsx:188
|
||||||
#: src/screens/Settings/Settings.tsx:191
|
#: src/screens/Settings/Settings.tsx:191
|
||||||
msgid "Moderation"
|
msgid "Moderation"
|
||||||
@@ -5079,7 +5077,7 @@ msgctxt "toast"
|
|||||||
msgid "Moderation list updated"
|
msgid "Moderation list updated"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/screens/Moderation/index.tsx:252
|
#: src/screens/Moderation/index.tsx:271
|
||||||
msgid "Moderation lists"
|
msgid "Moderation lists"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -5096,7 +5094,7 @@ msgstr ""
|
|||||||
msgid "Moderation states"
|
msgid "Moderation states"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/screens/Moderation/index.tsx:206
|
#: src/screens/Moderation/index.tsx:225
|
||||||
msgid "Moderation tools"
|
msgid "Moderation tools"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -5211,7 +5209,7 @@ msgstr ""
|
|||||||
msgid "Mute words & tags"
|
msgid "Mute words & tags"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/screens/Moderation/index.tsx:267
|
#: src/screens/Moderation/index.tsx:286
|
||||||
msgid "Muted accounts"
|
msgid "Muted accounts"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -5228,7 +5226,7 @@ msgstr ""
|
|||||||
msgid "Muted by \"{0}\""
|
msgid "Muted by \"{0}\""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/screens/Moderation/index.tsx:237
|
#: src/screens/Moderation/index.tsx:256
|
||||||
msgid "Muted words & tags"
|
msgid "Muted words & tags"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -5446,7 +5444,7 @@ msgstr ""
|
|||||||
msgid "Next"
|
msgid "Next"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/lightbox/Lightbox.web.tsx:169
|
#: src/view/com/lightbox/Lightbox.web.tsx:170
|
||||||
msgid "Next image"
|
msgid "Next image"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -5722,15 +5720,23 @@ msgstr ""
|
|||||||
msgid "Onboarding reset"
|
msgid "Onboarding reset"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/composer/Composer.tsx:347
|
#: src/view/com/composer/Composer.tsx:355
|
||||||
msgid "One or more GIFs is missing alt text."
|
msgid "One or more GIFs is missing alt text."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/composer/Composer.tsx:344
|
#: src/view/com/composer/Composer.tsx:352
|
||||||
msgid "One or more images is missing alt text."
|
msgid "One or more images is missing alt text."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/composer/Composer.tsx:354
|
#: src/view/com/composer/SelectMediaButton.tsx:390
|
||||||
|
msgid "One or more of your selected files are not supported."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/view/com/composer/SelectMediaButton.tsx:413
|
||||||
|
msgid "One or more of your selected files is too large. Maximum size is 100 MB."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/view/com/composer/Composer.tsx:362
|
||||||
msgid "One or more videos is missing alt text."
|
msgid "One or more videos is missing alt text."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -5748,7 +5754,7 @@ msgstr ""
|
|||||||
msgid "Only followers who I follow"
|
msgid "Only followers who I follow"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/lib/media/picker.shared.ts:32
|
#: src/lib/media/picker.shared.ts:28
|
||||||
msgid "Only image files are supported"
|
msgid "Only image files are supported"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -5787,7 +5793,7 @@ msgid "Open drawer menu"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/screens/Messages/components/MessageInput.web.tsx:181
|
#: src/screens/Messages/components/MessageInput.web.tsx:181
|
||||||
#: src/view/com/composer/Composer.tsx:1329
|
#: src/view/com/composer/Composer.tsx:1411
|
||||||
msgid "Open emoji picker"
|
msgid "Open emoji picker"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -5816,7 +5822,7 @@ msgstr ""
|
|||||||
msgid "Open moderation debug page"
|
msgid "Open moderation debug page"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/screens/Moderation/index.tsx:233
|
#: src/screens/Moderation/index.tsx:252
|
||||||
msgid "Open muted words and tags settings"
|
msgid "Open muted words and tags settings"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -5886,11 +5892,17 @@ msgstr ""
|
|||||||
msgid "Opens composer"
|
msgid "Opens composer"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/composer/photos/SelectPhotoBtn.tsx:51
|
#. Accessibility hint on web for button in composer to add images, a video, or a GIF to a post. Maximum number of images that can be selected is currently 4 but may change.
|
||||||
msgid "Opens device photo gallery"
|
#: src/view/com/composer/SelectMediaButton.tsx:501
|
||||||
|
msgid "Opens device gallery to select up to {MAX_IMAGES, plural, other {# images}}, or a single video or GIF."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/composer/Composer.tsx:1330
|
#. Accessibility hint on native for button in composer to add images or a video to a post. Maximum number of images that can be selected is currently 4 but may change.
|
||||||
|
#: src/view/com/composer/SelectMediaButton.tsx:490
|
||||||
|
msgid "Opens device gallery to select up to {MAX_IMAGES, plural, other {# images}}, or a single video."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/view/com/composer/Composer.tsx:1412
|
||||||
msgid "Opens emoji picker"
|
msgid "Opens emoji picker"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -5933,10 +5945,6 @@ msgstr ""
|
|||||||
msgid "Opens this profile"
|
msgid "Opens this profile"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/composer/videos/SelectVideoBtn.tsx:75
|
|
||||||
msgid "Opens video picker"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/components/dms/ReportDialog.tsx:221
|
#: src/components/dms/ReportDialog.tsx:221
|
||||||
#: src/components/ReportDialog/SubmitView.tsx:168
|
#: src/components/ReportDialog/SubmitView.tsx:168
|
||||||
msgid "Optionally provide additional information below:"
|
msgid "Optionally provide additional information below:"
|
||||||
@@ -6310,12 +6318,12 @@ msgctxt "description"
|
|||||||
msgid "Post"
|
msgid "Post"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/composer/Composer.tsx:1040
|
#: src/view/com/composer/Composer.tsx:1058
|
||||||
msgctxt "action"
|
msgctxt "action"
|
||||||
msgid "Post"
|
msgid "Post"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/composer/Composer.tsx:1038
|
#: src/view/com/composer/Composer.tsx:1056
|
||||||
msgctxt "action"
|
msgctxt "action"
|
||||||
msgid "Post All"
|
msgid "Post All"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -6443,7 +6451,7 @@ msgstr ""
|
|||||||
msgid "Press to view followers of this account that you also follow"
|
msgid "Press to view followers of this account that you also follow"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/lightbox/Lightbox.web.tsx:150
|
#: src/view/com/lightbox/Lightbox.web.tsx:151
|
||||||
msgid "Previous image"
|
msgid "Previous image"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -6490,7 +6498,7 @@ msgstr ""
|
|||||||
msgid "Privacy Policy"
|
msgid "Privacy Policy"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/composer/Composer.tsx:1744
|
#: src/view/com/composer/Composer.tsx:1826
|
||||||
msgid "Processing video..."
|
msgid "Processing video..."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -6529,22 +6537,22 @@ msgid "Public, sharable lists which can be used to drive feeds."
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#. Accessibility label for button to publish a single post
|
#. Accessibility label for button to publish a single post
|
||||||
#: src/view/com/composer/Composer.tsx:1020
|
#: src/view/com/composer/Composer.tsx:1038
|
||||||
msgid "Publish post"
|
msgid "Publish post"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#. Accessibility label for button to publish multiple posts in a thread
|
#. Accessibility label for button to publish multiple posts in a thread
|
||||||
#: src/view/com/composer/Composer.tsx:1013
|
#: src/view/com/composer/Composer.tsx:1031
|
||||||
msgid "Publish posts"
|
msgid "Publish posts"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#. Accessibility label for button to publish multiple replies in a thread
|
#. Accessibility label for button to publish multiple replies in a thread
|
||||||
#: src/view/com/composer/Composer.tsx:998
|
#: src/view/com/composer/Composer.tsx:1016
|
||||||
msgid "Publish replies"
|
msgid "Publish replies"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#. Accessibility label for button to publish a single reply
|
#. Accessibility label for button to publish a single reply
|
||||||
#: src/view/com/composer/Composer.tsx:1005
|
#: src/view/com/composer/Composer.tsx:1023
|
||||||
msgid "Publish reply"
|
msgid "Publish reply"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -6911,7 +6919,7 @@ msgstr ""
|
|||||||
msgid "Replies to this post are disabled."
|
msgid "Replies to this post are disabled."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/composer/Composer.tsx:1036
|
#: src/view/com/composer/Composer.tsx:1054
|
||||||
msgctxt "action"
|
msgctxt "action"
|
||||||
msgid "Reply"
|
msgid "Reply"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -7239,8 +7247,8 @@ msgstr ""
|
|||||||
#: src/view/com/composer/GifAltText.tsx:202
|
#: src/view/com/composer/GifAltText.tsx:202
|
||||||
#: src/view/com/composer/photos/EditImageDialog.web.tsx:62
|
#: src/view/com/composer/photos/EditImageDialog.web.tsx:62
|
||||||
#: src/view/com/composer/photos/EditImageDialog.web.tsx:75
|
#: src/view/com/composer/photos/EditImageDialog.web.tsx:75
|
||||||
#: src/view/com/composer/photos/ImageAltTextDialog.tsx:153
|
#: src/view/com/composer/photos/ImageAltTextDialog.tsx:152
|
||||||
#: src/view/com/composer/photos/ImageAltTextDialog.tsx:163
|
#: src/view/com/composer/photos/ImageAltTextDialog.tsx:162
|
||||||
#: src/view/com/modals/CreateOrEditList.tsx:315
|
#: src/view/com/modals/CreateOrEditList.tsx:315
|
||||||
#: src/view/screens/SavedFeeds.tsx:117
|
#: src/view/screens/SavedFeeds.tsx:117
|
||||||
msgid "Save"
|
msgid "Save"
|
||||||
@@ -7440,7 +7448,7 @@ msgstr ""
|
|||||||
msgid "See this guide"
|
msgid "See this guide"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/Scrubber.tsx:195
|
#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/Scrubber.tsx:197
|
||||||
msgid "Seek slider. Use the arrow keys to seek forwards and backwards, and space to play/pause"
|
msgid "Seek slider. Use the arrow keys to seek forwards and backwards, and space to play/pause"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -7539,10 +7547,6 @@ msgstr ""
|
|||||||
msgid "Select the moderation service(s) to report to"
|
msgid "Select the moderation service(s) to report to"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/composer/videos/SelectVideoBtn.tsx:74
|
|
||||||
msgid "Select video"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/components/dialogs/MutedWords.tsx:242
|
#: src/components/dialogs/MutedWords.tsx:242
|
||||||
msgid "Select what content this mute word should apply to."
|
msgid "Select what content this mute word should apply to."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -7572,6 +7576,10 @@ msgstr ""
|
|||||||
msgid "Select your preferred notification channels"
|
msgid "Select your preferred notification channels"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/view/com/composer/SelectMediaButton.tsx:393
|
||||||
|
msgid "Selecting multiple media types is not supported."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/util/forms/DropdownButton.tsx:302
|
#: src/view/com/util/forms/DropdownButton.tsx:302
|
||||||
msgid "Selects option {0} of {numItems}"
|
msgid "Selects option {0} of {numItems}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -7649,7 +7657,7 @@ msgstr ""
|
|||||||
msgid "Set app icon to {0}"
|
msgid "Set app icon to {0}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/screens/Moderation/index.tsx:333
|
#: src/screens/Moderation/index.tsx:352
|
||||||
msgid "Set birthdate"
|
msgid "Set birthdate"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -8077,7 +8085,7 @@ msgid "Something went wrong, please try again"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/ReportDialog/index.tsx:54
|
#: src/components/ReportDialog/index.tsx:54
|
||||||
#: src/screens/Moderation/index.tsx:111
|
#: src/screens/Moderation/index.tsx:112
|
||||||
#: src/screens/Profile/Sections/Labels.tsx:184
|
#: src/screens/Profile/Sections/Labels.tsx:184
|
||||||
msgid "Something went wrong, please try again."
|
msgid "Something went wrong, please try again."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -8482,7 +8490,7 @@ msgstr ""
|
|||||||
msgid "The author of this thread has hidden this reply."
|
msgid "The author of this thread has hidden this reply."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/screens/Moderation/index.tsx:407
|
#: src/screens/Moderation/index.tsx:426
|
||||||
msgid "The Bluesky web application"
|
msgid "The Bluesky web application"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -8840,7 +8848,7 @@ msgstr ""
|
|||||||
msgid "This post will be hidden from feeds and threads. This cannot be undone."
|
msgid "This post will be hidden from feeds and threads. This cannot be undone."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/composer/Composer.tsx:463
|
#: src/view/com/composer/Composer.tsx:471
|
||||||
msgid "This post's author has disabled quote posts."
|
msgid "This post's author has disabled quote posts."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -8974,7 +8982,7 @@ msgstr ""
|
|||||||
msgid "Toggle dropdown"
|
msgid "Toggle dropdown"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/screens/Moderation/index.tsx:384
|
#: src/screens/Moderation/index.tsx:403
|
||||||
msgid "Toggle to enable or disable adult content"
|
msgid "Toggle to enable or disable adult content"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -9256,12 +9264,8 @@ msgstr ""
|
|||||||
msgid "Unsubscribed from list"
|
msgid "Unsubscribed from list"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/composer/Composer.tsx:818
|
#: src/view/com/composer/Composer.tsx:834
|
||||||
msgid "Unsupported video type"
|
msgid "Unsupported video type: {mimeType}"
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/view/com/composer/videos/SelectVideoBtn.tsx:48
|
|
||||||
msgid "Unsupported video type: {0}"
|
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:77
|
#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:77
|
||||||
@@ -9346,7 +9350,7 @@ msgstr ""
|
|||||||
msgid "Uploading link thumbnail..."
|
msgid "Uploading link thumbnail..."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/composer/Composer.tsx:1741
|
#: src/view/com/composer/Composer.tsx:1823
|
||||||
msgid "Uploading video..."
|
msgid "Uploading video..."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -9482,7 +9486,7 @@ msgstr ""
|
|||||||
msgid "Verification failed, please try again."
|
msgid "Verification failed, please try again."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/screens/Moderation/index.tsx:297
|
#: src/screens/Moderation/index.tsx:316
|
||||||
msgid "Verification settings"
|
msgid "Verification settings"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -9608,7 +9612,7 @@ msgstr ""
|
|||||||
msgid "Video settings"
|
msgid "Video settings"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/composer/Composer.tsx:1751
|
#: src/view/com/composer/Composer.tsx:1833
|
||||||
msgid "Video uploaded"
|
msgid "Video uploaded"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -9620,9 +9624,8 @@ msgstr ""
|
|||||||
msgid "Videos"
|
msgid "Videos"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/composer/videos/SelectVideoBtn.tsx:42
|
#: src/view/com/composer/SelectMediaButton.tsx:407
|
||||||
#: src/view/com/composer/videos/SelectVideoBtn.tsx:55
|
msgid "Videos must be less than 3 minutes long."
|
||||||
msgid "Videos must be less than 3 minutes long"
|
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/screens/Profile/Header/Shell.tsx:229
|
#: src/screens/Profile/Header/Shell.tsx:229
|
||||||
@@ -9707,11 +9710,11 @@ msgstr ""
|
|||||||
msgid "View video"
|
msgid "View video"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/screens/Moderation/index.tsx:277
|
#: src/screens/Moderation/index.tsx:296
|
||||||
msgid "View your blocked accounts"
|
msgid "View your blocked accounts"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/screens/Moderation/index.tsx:217
|
#: src/screens/Moderation/index.tsx:236
|
||||||
msgid "View your default post interaction settings"
|
msgid "View your default post interaction settings"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -9720,11 +9723,11 @@ msgstr ""
|
|||||||
msgid "View your feeds and explore more"
|
msgid "View your feeds and explore more"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/screens/Moderation/index.tsx:247
|
#: src/screens/Moderation/index.tsx:266
|
||||||
msgid "View your moderation lists"
|
msgid "View your moderation lists"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/screens/Moderation/index.tsx:262
|
#: src/screens/Moderation/index.tsx:281
|
||||||
msgid "View your muted accounts"
|
msgid "View your muted accounts"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -9829,7 +9832,7 @@ msgstr ""
|
|||||||
msgid "We were unable to load your birth date preferences. Please try again."
|
msgid "We were unable to load your birth date preferences. Please try again."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/screens/Moderation/index.tsx:464
|
#: src/screens/Moderation/index.tsx:483
|
||||||
msgid "We were unable to load your configured labelers at this time."
|
msgid "We were unable to load your configured labelers at this time."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -9894,7 +9897,7 @@ msgstr ""
|
|||||||
msgid "We're sorry, but your search could not be completed. Please try again in a few minutes."
|
msgid "We're sorry, but your search could not be completed. Please try again in a few minutes."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/composer/Composer.tsx:460
|
#: src/view/com/composer/Composer.tsx:468
|
||||||
msgid "We're sorry! The post you are replying to has been deleted."
|
msgid "We're sorry! The post you are replying to has been deleted."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -9945,7 +9948,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: src/view/com/auth/SplashScreen.tsx:38
|
#: src/view/com/auth/SplashScreen.tsx:38
|
||||||
#: src/view/com/auth/SplashScreen.web.tsx:99
|
#: src/view/com/auth/SplashScreen.web.tsx:99
|
||||||
#: src/view/com/composer/Composer.tsx:781
|
#: src/view/com/composer/Composer.tsx:794
|
||||||
msgid "What's up?"
|
msgid "What's up?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -10027,11 +10030,11 @@ msgstr ""
|
|||||||
msgid "Write a message"
|
msgid "Write a message"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/composer/Composer.tsx:876
|
#: src/view/com/composer/Composer.tsx:894
|
||||||
msgid "Write post"
|
msgid "Write post"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/composer/Composer.tsx:779
|
#: src/view/com/composer/Composer.tsx:792
|
||||||
#: src/view/com/post-thread/PostThreadComposePrompt.tsx:90
|
#: src/view/com/post-thread/PostThreadComposePrompt.tsx:90
|
||||||
msgid "Write your reply"
|
msgid "Write your reply"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -10170,10 +10173,23 @@ msgstr ""
|
|||||||
msgid "You can now sign in with your new password."
|
msgid "You can now sign in with your new password."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/view/com/composer/SelectMediaButton.tsx:410
|
||||||
|
msgid "You can only select one GIF at a time."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/view/com/composer/SelectMediaButton.tsx:404
|
||||||
|
msgid "You can only select one video at a time."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/screens/Deactivated.tsx:133
|
#: src/screens/Deactivated.tsx:133
|
||||||
msgid "You can reactivate your account to continue logging in. Your profile and posts will be visible to other users."
|
msgid "You can reactivate your account to continue logging in. Your profile and posts will be visible to other users."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#. Error message for maximum number of images that can be selected to add to a post, currently 4 but may change.
|
||||||
|
#: src/view/com/composer/SelectMediaButton.tsx:396
|
||||||
|
msgid "You can select up to {MAX_IMAGES, plural, other {# images}} in total."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/dialogs/PostInteractionSettingsDialog.tsx:85
|
#: src/components/dialogs/PostInteractionSettingsDialog.tsx:85
|
||||||
msgid "You can set default interaction settings in <0>Settings → Moderation → Interaction settings</0>."
|
msgid "You can set default interaction settings in <0>Settings → Moderation → Interaction settings</0>."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -10316,10 +10332,6 @@ msgstr ""
|
|||||||
msgid "You may only add up to 3 feeds"
|
msgid "You may only add up to 3 feeds"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/lib/media/picker.shared.ts:25
|
|
||||||
msgid "You may only select up to 4 images"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/screens/Signup/StepInfo/Policies.tsx:136
|
#: src/screens/Signup/StepInfo/Policies.tsx:136
|
||||||
msgid "You must be 13 years of age or older to create an account."
|
msgid "You must be 13 years of age or older to create an account."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -10328,7 +10340,7 @@ msgstr ""
|
|||||||
msgid "You must be following at least seven other people to generate a starter pack."
|
msgid "You must be following at least seven other people to generate a starter pack."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/screens/Moderation/index.tsx:355
|
#: src/screens/Moderation/index.tsx:374
|
||||||
msgid "You must complete age assurance in order to access the settings below."
|
msgid "You must complete age assurance in order to access the settings below."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -10349,6 +10361,10 @@ msgstr ""
|
|||||||
msgid "You must sign in to view this post."
|
msgid "You must sign in to view this post."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/view/com/composer/SelectMediaButton.tsx:439
|
||||||
|
msgid "You need to allow access to your media library."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/dialogs/EmailDialog/screens/Manage2FA/index.tsx:23
|
#: src/components/dialogs/EmailDialog/screens/Manage2FA/index.tsx:23
|
||||||
msgid "You need to verify your email address before you can enable email 2FA."
|
msgid "You need to verify your email address before you can enable email 2FA."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -10524,6 +10540,10 @@ msgstr ""
|
|||||||
msgid "Your current handle <0>{0}</0> will automatically remain reserved for you. You can switch back to it at any time from this account."
|
msgid "Your current handle <0>{0}</0> will automatically remain reserved for you. You can switch back to it at any time from this account."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/screens/Moderation/index.tsx:208
|
||||||
|
msgid "Your declared age is under 18. Some settings below may be disabled. If this was a mistake, you may edit your bithdate in your <0>account settings</0>."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/ageAssurance/AgeAssuranceInitDialog.tsx:253
|
#: src/components/ageAssurance/AgeAssuranceInitDialog.tsx:253
|
||||||
#: src/components/ageAssurance/AgeAssuranceInitDialog.tsx:257
|
#: src/components/ageAssurance/AgeAssuranceInitDialog.tsx:257
|
||||||
#: src/components/ageAssurance/AgeAssuranceInitDialog.tsx:258
|
#: src/components/ageAssurance/AgeAssuranceInitDialog.tsx:258
|
||||||
@@ -10586,11 +10606,11 @@ msgstr ""
|
|||||||
msgid "Your password must be at least 8 characters long."
|
msgid "Your password must be at least 8 characters long."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/composer/Composer.tsx:522
|
#: src/view/com/composer/Composer.tsx:530
|
||||||
msgid "Your post has been published"
|
msgid "Your post has been published"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/composer/Composer.tsx:519
|
#: src/view/com/composer/Composer.tsx:527
|
||||||
msgid "Your posts have been published"
|
msgid "Your posts have been published"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -10606,7 +10626,7 @@ msgstr ""
|
|||||||
msgid "Your profile, posts, feeds, and lists will no longer be visible to other Bluesky users. You can reactivate your account at any time by logging in."
|
msgid "Your profile, posts, feeds, and lists will no longer be visible to other Bluesky users. You can reactivate your account at any time by logging in."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/composer/Composer.tsx:521
|
#: src/view/com/composer/Composer.tsx:529
|
||||||
msgid "Your reply has been published"
|
msgid "Your reply has been published"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import {
|
|||||||
import {isNonConfigurableModerationAuthority} from '#/state/session/additional-moderation-authorities'
|
import {isNonConfigurableModerationAuthority} from '#/state/session/additional-moderation-authorities'
|
||||||
import {useSetMinimalShellMode} from '#/state/shell'
|
import {useSetMinimalShellMode} from '#/state/shell'
|
||||||
import {atoms as a, useBreakpoints, useTheme, type ViewStyleProp} from '#/alf'
|
import {atoms as a, useBreakpoints, useTheme, type ViewStyleProp} from '#/alf'
|
||||||
|
import {Admonition} from '#/components/Admonition'
|
||||||
import {AgeAssuranceAdmonition} from '#/components/ageAssurance/AgeAssuranceAdmonition'
|
import {AgeAssuranceAdmonition} from '#/components/ageAssurance/AgeAssuranceAdmonition'
|
||||||
import {Button, ButtonText} from '#/components/Button'
|
import {Button, ButtonText} from '#/components/Button'
|
||||||
import * as Dialog from '#/components/Dialog'
|
import * as Dialog from '#/components/Dialog'
|
||||||
@@ -201,6 +202,24 @@ export function ModerationScreenInner({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[a.pt_2xl, a.px_lg, gtMobile && a.px_2xl]}>
|
<View style={[a.pt_2xl, a.px_lg, gtMobile && a.px_2xl]}>
|
||||||
|
{isDeclaredUnderage && (
|
||||||
|
<View style={[a.pb_2xl]}>
|
||||||
|
<Admonition type="tip" style={[a.pb_md]}>
|
||||||
|
<Trans>
|
||||||
|
Your declared age is under 18. Some settings below may be
|
||||||
|
disabled. If this was a mistake, you may edit your bithdate in
|
||||||
|
your{' '}
|
||||||
|
<InlineLinkText
|
||||||
|
to="/settings/account"
|
||||||
|
label={_(msg`Go to account settings`)}>
|
||||||
|
account settings
|
||||||
|
</InlineLinkText>
|
||||||
|
.
|
||||||
|
</Trans>
|
||||||
|
</Admonition>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
|
||||||
<Text
|
<Text
|
||||||
style={[a.text_md, a.font_bold, a.pb_md, t.atoms.text_contrast_high]}>
|
style={[a.text_md, a.font_bold, a.pb_md, t.atoms.text_contrast_high]}>
|
||||||
<Trans>Moderation tools</Trans>
|
<Trans>Moderation tools</Trans>
|
||||||
|
|||||||
@@ -307,9 +307,16 @@ export function sortAndAnnotateThreadItems(
|
|||||||
metadata.isPartOfLastBranchFromDepth = metadata.depth
|
metadata.isPartOfLastBranchFromDepth = metadata.depth
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the parent is part of the last branch of the sub-tree, so is the child.
|
* If the parent is part of the last branch of the sub-tree, so
|
||||||
|
* is the child. However, if the child is also a last sibling,
|
||||||
|
* then we need to start tracking `isPartOfLastBranchFromDepth`
|
||||||
|
* from this point onwards, always updating it to the depth of
|
||||||
|
* the last sibling as we go down.
|
||||||
*/
|
*/
|
||||||
if (metadata.parentMetadata.isPartOfLastBranchFromDepth) {
|
if (
|
||||||
|
!metadata.isLastSibling &&
|
||||||
|
metadata.parentMetadata.isPartOfLastBranchFromDepth
|
||||||
|
) {
|
||||||
metadata.isPartOfLastBranchFromDepth =
|
metadata.isPartOfLastBranchFromDepth =
|
||||||
metadata.parentMetadata.isPartOfLastBranchFromDepth
|
metadata.parentMetadata.isPartOfLastBranchFromDepth
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -151,8 +151,8 @@ export type TraversalMetadata = {
|
|||||||
*/
|
*/
|
||||||
isLastChild: boolean
|
isLastChild: boolean
|
||||||
/**
|
/**
|
||||||
* Indicates if the post is the left/lower-most branch of the reply tree.
|
* Indicates if the post is the left-most AND lower-most branch of the reply
|
||||||
* Value corresponds to the depth at which this branch started.
|
* tree. Value corresponds to the depth at which this branch started.
|
||||||
*/
|
*/
|
||||||
isPartOfLastBranchFromDepth?: number
|
isPartOfLastBranchFromDepth?: number
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ import Animated, {
|
|||||||
ZoomIn,
|
ZoomIn,
|
||||||
ZoomOut,
|
ZoomOut,
|
||||||
} from 'react-native-reanimated'
|
} from 'react-native-reanimated'
|
||||||
|
import {RootSiblingParent} from 'react-native-root-siblings'
|
||||||
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
||||||
import {type ImagePickerAsset} from 'expo-image-picker'
|
import {type ImagePickerAsset} from 'expo-image-picker'
|
||||||
import {
|
import {
|
||||||
@@ -77,7 +78,11 @@ import {logger} from '#/logger'
|
|||||||
import {isAndroid, isIOS, isNative, isWeb} from '#/platform/detection'
|
import {isAndroid, isIOS, isNative, isWeb} from '#/platform/detection'
|
||||||
import {useDialogStateControlContext} from '#/state/dialogs'
|
import {useDialogStateControlContext} from '#/state/dialogs'
|
||||||
import {emitPostCreated} from '#/state/events'
|
import {emitPostCreated} from '#/state/events'
|
||||||
import {type ComposerImage, pasteImage} from '#/state/gallery'
|
import {
|
||||||
|
type ComposerImage,
|
||||||
|
createComposerImage,
|
||||||
|
pasteImage,
|
||||||
|
} from '#/state/gallery'
|
||||||
import {useModalControls} from '#/state/modals'
|
import {useModalControls} from '#/state/modals'
|
||||||
import {useRequireAltTextEnabled} from '#/state/preferences'
|
import {useRequireAltTextEnabled} from '#/state/preferences'
|
||||||
import {
|
import {
|
||||||
@@ -103,7 +108,6 @@ import {LabelsBtn} from '#/view/com/composer/labels/LabelsBtn'
|
|||||||
import {Gallery} from '#/view/com/composer/photos/Gallery'
|
import {Gallery} from '#/view/com/composer/photos/Gallery'
|
||||||
import {OpenCameraBtn} from '#/view/com/composer/photos/OpenCameraBtn'
|
import {OpenCameraBtn} from '#/view/com/composer/photos/OpenCameraBtn'
|
||||||
import {SelectGifBtn} from '#/view/com/composer/photos/SelectGifBtn'
|
import {SelectGifBtn} from '#/view/com/composer/photos/SelectGifBtn'
|
||||||
import {SelectPhotoBtn} from '#/view/com/composer/photos/SelectPhotoBtn'
|
|
||||||
import {SelectLangBtn} from '#/view/com/composer/select-language/SelectLangBtn'
|
import {SelectLangBtn} from '#/view/com/composer/select-language/SelectLangBtn'
|
||||||
import {SuggestedLanguage} from '#/view/com/composer/select-language/SuggestedLanguage'
|
import {SuggestedLanguage} from '#/view/com/composer/select-language/SuggestedLanguage'
|
||||||
// TODO: Prevent naming components that coincide with RN primitives
|
// TODO: Prevent naming components that coincide with RN primitives
|
||||||
@@ -113,12 +117,10 @@ import {
|
|||||||
type TextInputRef,
|
type TextInputRef,
|
||||||
} from '#/view/com/composer/text-input/TextInput'
|
} from '#/view/com/composer/text-input/TextInput'
|
||||||
import {ThreadgateBtn} from '#/view/com/composer/threadgate/ThreadgateBtn'
|
import {ThreadgateBtn} from '#/view/com/composer/threadgate/ThreadgateBtn'
|
||||||
import {SelectVideoBtn} from '#/view/com/composer/videos/SelectVideoBtn'
|
|
||||||
import {SubtitleDialogBtn} from '#/view/com/composer/videos/SubtitleDialog'
|
import {SubtitleDialogBtn} from '#/view/com/composer/videos/SubtitleDialog'
|
||||||
import {VideoPreview} from '#/view/com/composer/videos/VideoPreview'
|
import {VideoPreview} from '#/view/com/composer/videos/VideoPreview'
|
||||||
import {VideoTranscodeProgress} from '#/view/com/composer/videos/VideoTranscodeProgress'
|
import {VideoTranscodeProgress} from '#/view/com/composer/videos/VideoTranscodeProgress'
|
||||||
import {Text} from '#/view/com/util/text/Text'
|
import {Text} from '#/view/com/util/text/Text'
|
||||||
import * as Toast from '#/view/com/util/Toast'
|
|
||||||
import {UserAvatar} from '#/view/com/util/UserAvatar'
|
import {UserAvatar} from '#/view/com/util/UserAvatar'
|
||||||
import {atoms as a, native, useTheme, web} from '#/alf'
|
import {atoms as a, native, useTheme, web} from '#/alf'
|
||||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||||
@@ -127,8 +129,14 @@ import {EmojiArc_Stroke2_Corner0_Rounded as EmojiSmile} from '#/components/icons
|
|||||||
import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times'
|
import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times'
|
||||||
import {LazyQuoteEmbed} from '#/components/Post/Embed/LazyQuoteEmbed'
|
import {LazyQuoteEmbed} from '#/components/Post/Embed/LazyQuoteEmbed'
|
||||||
import * as Prompt from '#/components/Prompt'
|
import * as Prompt from '#/components/Prompt'
|
||||||
|
import * as toast from '#/components/Toast'
|
||||||
import {Text as NewText} from '#/components/Typography'
|
import {Text as NewText} from '#/components/Typography'
|
||||||
import {BottomSheetPortalProvider} from '../../../../modules/bottom-sheet'
|
import {BottomSheetPortalProvider} from '../../../../modules/bottom-sheet'
|
||||||
|
import {
|
||||||
|
type AssetType,
|
||||||
|
SelectMediaButton,
|
||||||
|
type SelectMediaButtonProps,
|
||||||
|
} from './SelectMediaButton'
|
||||||
import {
|
import {
|
||||||
type ComposerAction,
|
type ComposerAction,
|
||||||
composerReducer,
|
composerReducer,
|
||||||
@@ -514,12 +522,13 @@ export const ComposePost = ({
|
|||||||
onPostSuccess?.(postSuccessData)
|
onPostSuccess?.(postSuccessData)
|
||||||
}
|
}
|
||||||
onClose()
|
onClose()
|
||||||
Toast.show(
|
toast.show(
|
||||||
thread.posts.length > 1
|
thread.posts.length > 1
|
||||||
? _(msg`Your posts have been published`)
|
? _(msg`Your posts have been published`)
|
||||||
: replyTo
|
: replyTo
|
||||||
? _(msg`Your reply has been published`)
|
? _(msg`Your reply has been published`)
|
||||||
: _(msg`Your post has been published`),
|
: _(msg`Your post has been published`),
|
||||||
|
{type: 'success'},
|
||||||
)
|
)
|
||||||
}, [
|
}, [
|
||||||
_,
|
_,
|
||||||
@@ -654,6 +663,7 @@ export const ComposePost = ({
|
|||||||
const isWebFooterSticky = !isNative && thread.posts.length > 1
|
const isWebFooterSticky = !isNative && thread.posts.length > 1
|
||||||
return (
|
return (
|
||||||
<BottomSheetPortalProvider>
|
<BottomSheetPortalProvider>
|
||||||
|
<RootSiblingParent>
|
||||||
<KeyboardAvoidingView
|
<KeyboardAvoidingView
|
||||||
testID="composePostView"
|
testID="composePostView"
|
||||||
behavior={isIOS ? 'padding' : 'height'}
|
behavior={isIOS ? 'padding' : 'height'}
|
||||||
@@ -663,6 +673,7 @@ export const ComposePost = ({
|
|||||||
style={[a.flex_1, viewStyles]}
|
style={[a.flex_1, viewStyles]}
|
||||||
aria-modal
|
aria-modal
|
||||||
accessibilityViewIsModal>
|
accessibilityViewIsModal>
|
||||||
|
<RootSiblingParent>
|
||||||
<ComposerTopBar
|
<ComposerTopBar
|
||||||
canPost={canPost}
|
canPost={canPost}
|
||||||
isReply={!!replyTo}
|
isReply={!!replyTo}
|
||||||
@@ -721,6 +732,7 @@ export const ComposePost = ({
|
|||||||
))}
|
))}
|
||||||
</Animated.ScrollView>
|
</Animated.ScrollView>
|
||||||
{!isWebFooterSticky && footer}
|
{!isWebFooterSticky && footer}
|
||||||
|
</RootSiblingParent>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<Prompt.Basic
|
<Prompt.Basic
|
||||||
@@ -732,6 +744,7 @@ export const ComposePost = ({
|
|||||||
confirmButtonColor="negative"
|
confirmButtonColor="negative"
|
||||||
/>
|
/>
|
||||||
</KeyboardAvoidingView>
|
</KeyboardAvoidingView>
|
||||||
|
</RootSiblingParent>
|
||||||
</BottomSheetPortalProvider>
|
</BottomSheetPortalProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -811,11 +824,16 @@ let ComposerPost = React.memo(function ComposerPost({
|
|||||||
|
|
||||||
const onPhotoPasted = useCallback(
|
const onPhotoPasted = useCallback(
|
||||||
async (uri: string) => {
|
async (uri: string) => {
|
||||||
if (uri.startsWith('data:video/') || uri.startsWith('data:image/gif')) {
|
if (
|
||||||
|
uri.startsWith('data:video/') ||
|
||||||
|
(isWeb && uri.startsWith('data:image/gif'))
|
||||||
|
) {
|
||||||
if (isNative) return // web only
|
if (isNative) return // web only
|
||||||
const [mimeType] = uri.slice('data:'.length).split(';')
|
const [mimeType] = uri.slice('data:'.length).split(';')
|
||||||
if (!SUPPORTED_MIME_TYPES.includes(mimeType as SupportedMimeTypes)) {
|
if (!SUPPORTED_MIME_TYPES.includes(mimeType as SupportedMimeTypes)) {
|
||||||
Toast.show(_(msg`Unsupported video type`), 'xmark')
|
toast.show(_(msg`Unsupported video type: ${mimeType}`), {
|
||||||
|
type: 'error',
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const name = `pasted.${mimeToExt(mimeType)}`
|
const name = `pasted.${mimeToExt(mimeType)}`
|
||||||
@@ -1251,7 +1269,6 @@ function ComposerFooter({
|
|||||||
dispatch,
|
dispatch,
|
||||||
showAddButton,
|
showAddButton,
|
||||||
onEmojiButtonPress,
|
onEmojiButtonPress,
|
||||||
onError,
|
|
||||||
onSelectVideo,
|
onSelectVideo,
|
||||||
onAddPost,
|
onAddPost,
|
||||||
}: {
|
}: {
|
||||||
@@ -1266,11 +1283,32 @@ function ComposerFooter({
|
|||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const {isMobile} = useWebMediaQueries()
|
const {isMobile} = useWebMediaQueries()
|
||||||
|
/*
|
||||||
|
* Once we've allowed a certain type of asset to be selected, we don't allow
|
||||||
|
* other types of media to be selected.
|
||||||
|
*/
|
||||||
|
const [selectedAssetsType, setSelectedAssetsType] = useState<
|
||||||
|
AssetType | undefined
|
||||||
|
>(undefined)
|
||||||
|
|
||||||
const media = post.embed.media
|
const media = post.embed.media
|
||||||
const images = media?.type === 'images' ? media.images : []
|
const images = media?.type === 'images' ? media.images : []
|
||||||
const video = media?.type === 'video' ? media.video : null
|
const video = media?.type === 'video' ? media.video : null
|
||||||
const isMaxImages = images.length >= MAX_IMAGES
|
const isMaxImages = images.length >= MAX_IMAGES
|
||||||
|
const isMaxVideos = !!video
|
||||||
|
|
||||||
|
let selectedAssetsCount = 0
|
||||||
|
let isMediaSelectionDisabled = false
|
||||||
|
|
||||||
|
if (media?.type === 'images') {
|
||||||
|
isMediaSelectionDisabled = isMaxImages
|
||||||
|
selectedAssetsCount = images.length
|
||||||
|
} else if (media?.type === 'video') {
|
||||||
|
isMediaSelectionDisabled = isMaxVideos
|
||||||
|
selectedAssetsCount = 1
|
||||||
|
} else {
|
||||||
|
isMediaSelectionDisabled = !!media
|
||||||
|
}
|
||||||
|
|
||||||
const onImageAdd = useCallback(
|
const onImageAdd = useCallback(
|
||||||
(next: ComposerImage[]) => {
|
(next: ComposerImage[]) => {
|
||||||
@@ -1289,6 +1327,54 @@ function ComposerFooter({
|
|||||||
[dispatch],
|
[dispatch],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Reset if the user clears any selected media
|
||||||
|
*/
|
||||||
|
if (selectedAssetsType !== undefined && !media) {
|
||||||
|
setSelectedAssetsType(undefined)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onSelectAssets = useCallback<SelectMediaButtonProps['onSelectAssets']>(
|
||||||
|
async ({type, assets, errors}) => {
|
||||||
|
setSelectedAssetsType(type)
|
||||||
|
|
||||||
|
if (assets.length) {
|
||||||
|
if (type === 'image') {
|
||||||
|
const images: ComposerImage[] = []
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
assets.map(async image => {
|
||||||
|
const composerImage = await createComposerImage({
|
||||||
|
path: image.uri,
|
||||||
|
width: image.width,
|
||||||
|
height: image.height,
|
||||||
|
mime: image.mimeType!,
|
||||||
|
})
|
||||||
|
images.push(composerImage)
|
||||||
|
}),
|
||||||
|
).catch(e => {
|
||||||
|
logger.error(`createComposerImage failed`, {
|
||||||
|
safeMessage: e.message,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
onImageAdd(images)
|
||||||
|
} else if (type === 'video') {
|
||||||
|
onSelectVideo(post.id, assets[0])
|
||||||
|
} else if (type === 'gif') {
|
||||||
|
onSelectVideo(post.id, assets[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
errors.map(error => {
|
||||||
|
toast.show(error, {
|
||||||
|
type: 'warning',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
[post.id, onSelectVideo, onImageAdd],
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
@@ -1307,15 +1393,11 @@ function ComposerFooter({
|
|||||||
<VideoUploadToolbar state={video} />
|
<VideoUploadToolbar state={video} />
|
||||||
) : (
|
) : (
|
||||||
<ToolbarWrapper style={[a.flex_row, a.align_center, a.gap_xs]}>
|
<ToolbarWrapper style={[a.flex_row, a.align_center, a.gap_xs]}>
|
||||||
<SelectPhotoBtn
|
<SelectMediaButton
|
||||||
size={images.length}
|
disabled={isMediaSelectionDisabled}
|
||||||
disabled={media?.type === 'images' ? isMaxImages : !!media}
|
allowedAssetTypes={selectedAssetsType}
|
||||||
onAdd={onImageAdd}
|
selectedAssetsCount={selectedAssetsCount}
|
||||||
/>
|
onSelectAssets={onSelectAssets}
|
||||||
<SelectVideoBtn
|
|
||||||
onSelectVideo={asset => onSelectVideo(post.id, asset)}
|
|
||||||
disabled={!!media}
|
|
||||||
setError={onError}
|
|
||||||
/>
|
/>
|
||||||
<OpenCameraBtn
|
<OpenCameraBtn
|
||||||
disabled={media?.type === 'images' ? isMaxImages : !!media}
|
disabled={media?.type === 'images' ? isMaxImages : !!media}
|
||||||
|
|||||||
@@ -0,0 +1,524 @@
|
|||||||
|
import {useCallback} from 'react'
|
||||||
|
import {Keyboard} from 'react-native'
|
||||||
|
import {
|
||||||
|
type ImagePickerAsset,
|
||||||
|
launchImageLibraryAsync,
|
||||||
|
UIImagePickerPreferredAssetRepresentationMode,
|
||||||
|
} from 'expo-image-picker'
|
||||||
|
import {msg, plural} from '@lingui/macro'
|
||||||
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
|
import {VIDEO_MAX_DURATION_MS, VIDEO_MAX_SIZE} from '#/lib/constants'
|
||||||
|
import {
|
||||||
|
usePhotoLibraryPermission,
|
||||||
|
useVideoLibraryPermission,
|
||||||
|
} from '#/lib/hooks/usePermissions'
|
||||||
|
import {extractDataUriMime} from '#/lib/media/util'
|
||||||
|
import {isIOS, isNative, isWeb} from '#/platform/detection'
|
||||||
|
import {MAX_IMAGES} from '#/view/com/composer/state/composer'
|
||||||
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
|
import {Button} from '#/components/Button'
|
||||||
|
import {useSheetWrapper} from '#/components/Dialog/sheet-wrapper'
|
||||||
|
import {Image_Stroke2_Corner0_Rounded as ImageIcon} from '#/components/icons/Image'
|
||||||
|
import * as toast from '#/components/Toast'
|
||||||
|
|
||||||
|
export type SelectMediaButtonProps = {
|
||||||
|
disabled?: boolean
|
||||||
|
/**
|
||||||
|
* If set, this limits the types of assets that can be selected.
|
||||||
|
*/
|
||||||
|
allowedAssetTypes: AssetType | undefined
|
||||||
|
selectedAssetsCount: number
|
||||||
|
onSelectAssets: (props: {
|
||||||
|
type: AssetType
|
||||||
|
assets: ImagePickerAsset[]
|
||||||
|
errors: string[]
|
||||||
|
}) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic asset classes, or buckets, that we support.
|
||||||
|
*/
|
||||||
|
export type AssetType = 'video' | 'image' | 'gif'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shadows `ImagePickerAsset` from `expo-image-picker`, but with a guaranteed `mimeType`
|
||||||
|
*/
|
||||||
|
type ValidatedImagePickerAsset = Omit<ImagePickerAsset, 'mimeType'> & {
|
||||||
|
mimeType: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Codes for known validation states
|
||||||
|
*/
|
||||||
|
enum SelectedAssetError {
|
||||||
|
Unsupported = 'Unsupported',
|
||||||
|
MixedTypes = 'MixedTypes',
|
||||||
|
MaxImages = 'MaxImages',
|
||||||
|
MaxVideos = 'MaxVideos',
|
||||||
|
VideoTooLong = 'VideoTooLong',
|
||||||
|
FileTooBig = 'FileTooBig',
|
||||||
|
MaxGIFs = 'MaxGIFs',
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Supported video mime types. This differs slightly from
|
||||||
|
* `SUPPORTED_MIME_TYPES` from `#/lib/constants` because we only care about
|
||||||
|
* videos here.
|
||||||
|
*/
|
||||||
|
const SUPPORTED_VIDEO_MIME_TYPES = [
|
||||||
|
'video/mp4',
|
||||||
|
'video/mpeg',
|
||||||
|
'video/webm',
|
||||||
|
'video/quicktime',
|
||||||
|
] as const
|
||||||
|
type SupportedVideoMimeType = (typeof SUPPORTED_VIDEO_MIME_TYPES)[number]
|
||||||
|
function isSupportedVideoMimeType(
|
||||||
|
mimeType: string,
|
||||||
|
): mimeType is SupportedVideoMimeType {
|
||||||
|
return SUPPORTED_VIDEO_MIME_TYPES.includes(mimeType as SupportedVideoMimeType)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Supported image mime types.
|
||||||
|
*/
|
||||||
|
const SUPPORTED_IMAGE_MIME_TYPES = (
|
||||||
|
[
|
||||||
|
'image/gif',
|
||||||
|
'image/jpeg',
|
||||||
|
'image/png',
|
||||||
|
'image/svg+xml',
|
||||||
|
'image/webp',
|
||||||
|
'image/avif',
|
||||||
|
isNative && 'image/heic',
|
||||||
|
] as const
|
||||||
|
).filter(Boolean)
|
||||||
|
type SupportedImageMimeType = Exclude<
|
||||||
|
(typeof SUPPORTED_IMAGE_MIME_TYPES)[number],
|
||||||
|
boolean
|
||||||
|
>
|
||||||
|
function isSupportedImageMimeType(
|
||||||
|
mimeType: string,
|
||||||
|
): mimeType is SupportedImageMimeType {
|
||||||
|
return SUPPORTED_IMAGE_MIME_TYPES.includes(mimeType as SupportedImageMimeType)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a last-ditch effort type thing here, try not to rely on this.
|
||||||
|
*/
|
||||||
|
const extensionToMimeType: Record<
|
||||||
|
string,
|
||||||
|
SupportedVideoMimeType | SupportedImageMimeType
|
||||||
|
> = {
|
||||||
|
mp4: 'video/mp4',
|
||||||
|
mov: 'video/quicktime',
|
||||||
|
webm: 'video/webm',
|
||||||
|
webp: 'image/webp',
|
||||||
|
gif: 'image/gif',
|
||||||
|
jpg: 'image/jpeg',
|
||||||
|
jpeg: 'image/jpeg',
|
||||||
|
png: 'image/png',
|
||||||
|
svg: 'image/svg+xml',
|
||||||
|
heic: 'image/heic',
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to bucket the given asset into one of our known types based on its
|
||||||
|
* `mimeType`. If `mimeType` is not available, we try to infer it through
|
||||||
|
* various means.
|
||||||
|
*/
|
||||||
|
function classifyImagePickerAsset(asset: ImagePickerAsset):
|
||||||
|
| {
|
||||||
|
success: true
|
||||||
|
type: AssetType
|
||||||
|
mimeType: string
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
success: false
|
||||||
|
type: undefined
|
||||||
|
mimeType: undefined
|
||||||
|
} {
|
||||||
|
/*
|
||||||
|
* Try to use the `mimeType` reported by `expo-image-picker` first.
|
||||||
|
*/
|
||||||
|
let mimeType = asset.mimeType
|
||||||
|
|
||||||
|
if (!mimeType) {
|
||||||
|
/*
|
||||||
|
* We can try to infer this from the data-uri.
|
||||||
|
*/
|
||||||
|
const maybeMimeType = extractDataUriMime(asset.uri)
|
||||||
|
|
||||||
|
if (
|
||||||
|
maybeMimeType.startsWith('image/') ||
|
||||||
|
maybeMimeType.startsWith('video/')
|
||||||
|
) {
|
||||||
|
mimeType = maybeMimeType
|
||||||
|
} else if (maybeMimeType.startsWith('file/')) {
|
||||||
|
/*
|
||||||
|
* On the off-chance we get a `file/*` mime, try to infer from the
|
||||||
|
* extension.
|
||||||
|
*/
|
||||||
|
const extension = asset.uri.split('.').pop()?.toLowerCase()
|
||||||
|
mimeType = extensionToMimeType[extension || '']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mimeType) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
type: undefined,
|
||||||
|
mimeType: undefined,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Distill this down into a type "class".
|
||||||
|
*/
|
||||||
|
let type: AssetType | undefined
|
||||||
|
if (mimeType === 'image/gif') {
|
||||||
|
type = 'gif'
|
||||||
|
} else if (mimeType?.startsWith('video/')) {
|
||||||
|
type = 'video'
|
||||||
|
} else if (mimeType?.startsWith('image/')) {
|
||||||
|
type = 'image'
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If we weren't able to find a valid type, we don't support this asset.
|
||||||
|
*/
|
||||||
|
if (!type) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
type: undefined,
|
||||||
|
mimeType: undefined,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
type,
|
||||||
|
mimeType,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes in raw assets from `expo-image-picker` and applies validation. Returns
|
||||||
|
* the dominant `AssetType`, any valid assets, and any errors encountered along
|
||||||
|
* the way.
|
||||||
|
*/
|
||||||
|
async function processImagePickerAssets(
|
||||||
|
assets: ImagePickerAsset[],
|
||||||
|
{
|
||||||
|
selectionCountRemaining,
|
||||||
|
allowedAssetTypes,
|
||||||
|
}: {
|
||||||
|
selectionCountRemaining: number
|
||||||
|
allowedAssetTypes: AssetType | undefined
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
/*
|
||||||
|
* A deduped set of error codes, which we'll use later
|
||||||
|
*/
|
||||||
|
const errors = new Set<SelectedAssetError>()
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We only support selecting a single type of media at a time, so this gets
|
||||||
|
* set to whatever the first valid asset type is, OR to whatever
|
||||||
|
* `allowedAssetTypes` is set to.
|
||||||
|
*/
|
||||||
|
let selectableAssetType: AssetType | undefined
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This will hold the assets that we can actually use, after filtering
|
||||||
|
*/
|
||||||
|
let supportedAssets: ValidatedImagePickerAsset[] = []
|
||||||
|
|
||||||
|
for (const asset of assets) {
|
||||||
|
const {success, type, mimeType} = classifyImagePickerAsset(asset)
|
||||||
|
|
||||||
|
if (!success) {
|
||||||
|
errors.add(SelectedAssetError.Unsupported)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If we have an `allowedAssetTypes` prop, constrain to that. Otherwise,
|
||||||
|
* set this to the first valid asset type we see, and then use that to
|
||||||
|
* constrain all remaining selected assets.
|
||||||
|
*/
|
||||||
|
selectableAssetType = allowedAssetTypes || selectableAssetType || type
|
||||||
|
|
||||||
|
// ignore mixed types
|
||||||
|
if (type !== selectableAssetType) {
|
||||||
|
errors.add(SelectedAssetError.MixedTypes)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'video') {
|
||||||
|
/**
|
||||||
|
* We don't care too much about mimeType at this point on native,
|
||||||
|
* since the `processVideo` step later on will convert to `.mp4`.
|
||||||
|
*/
|
||||||
|
if (isWeb && !isSupportedVideoMimeType(mimeType)) {
|
||||||
|
errors.add(SelectedAssetError.Unsupported)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Filesize appears to be stable across all platforms, so we can use it
|
||||||
|
* to filter out large files on web. On native, we compress these anyway,
|
||||||
|
* so we only check on web.
|
||||||
|
*/
|
||||||
|
if (isWeb && asset.fileSize && asset.fileSize > VIDEO_MAX_SIZE) {
|
||||||
|
errors.add(SelectedAssetError.FileTooBig)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'image') {
|
||||||
|
if (!isSupportedImageMimeType(mimeType)) {
|
||||||
|
errors.add(SelectedAssetError.Unsupported)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'gif') {
|
||||||
|
/*
|
||||||
|
* Filesize appears to be stable across all platforms, so we can use it
|
||||||
|
* to filter out large files on web. On native, we compress GIFs as
|
||||||
|
* videos anyway, so we only check on web.
|
||||||
|
*/
|
||||||
|
if (isWeb && asset.fileSize && asset.fileSize > VIDEO_MAX_SIZE) {
|
||||||
|
errors.add(SelectedAssetError.FileTooBig)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* All validations passed, we have an asset!
|
||||||
|
*/
|
||||||
|
supportedAssets.push({
|
||||||
|
mimeType,
|
||||||
|
...asset,
|
||||||
|
/*
|
||||||
|
* In `expo-image-picker` >= v17, `uri` is now a `blob:` URL, not a
|
||||||
|
* data-uri. Our handling elsewhere in the app (for web) relies on the
|
||||||
|
* base64 data-uri, so we construct it here for web only.
|
||||||
|
*/
|
||||||
|
uri:
|
||||||
|
isWeb && asset.base64
|
||||||
|
? `data:${mimeType};base64,${asset.base64}`
|
||||||
|
: asset.uri,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (supportedAssets.length > 0) {
|
||||||
|
if (selectableAssetType === 'image') {
|
||||||
|
if (supportedAssets.length > selectionCountRemaining) {
|
||||||
|
errors.add(SelectedAssetError.MaxImages)
|
||||||
|
supportedAssets = supportedAssets.slice(0, selectionCountRemaining)
|
||||||
|
}
|
||||||
|
} else if (selectableAssetType === 'video') {
|
||||||
|
if (supportedAssets.length > 1) {
|
||||||
|
errors.add(SelectedAssetError.MaxVideos)
|
||||||
|
supportedAssets = supportedAssets.slice(0, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (supportedAssets[0].duration) {
|
||||||
|
if (isWeb) {
|
||||||
|
/*
|
||||||
|
* Web reports duration as seconds
|
||||||
|
*/
|
||||||
|
supportedAssets[0].duration = supportedAssets[0].duration * 1000
|
||||||
|
}
|
||||||
|
|
||||||
|
if (supportedAssets[0].duration > VIDEO_MAX_DURATION_MS) {
|
||||||
|
errors.add(SelectedAssetError.VideoTooLong)
|
||||||
|
supportedAssets = []
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
errors.add(SelectedAssetError.Unsupported)
|
||||||
|
supportedAssets = []
|
||||||
|
}
|
||||||
|
} else if (selectableAssetType === 'gif') {
|
||||||
|
if (supportedAssets.length > 1) {
|
||||||
|
errors.add(SelectedAssetError.MaxGIFs)
|
||||||
|
supportedAssets = supportedAssets.slice(0, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: selectableAssetType!, // set above
|
||||||
|
assets: supportedAssets,
|
||||||
|
errors,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SelectMediaButton({
|
||||||
|
disabled,
|
||||||
|
allowedAssetTypes,
|
||||||
|
selectedAssetsCount,
|
||||||
|
onSelectAssets,
|
||||||
|
}: SelectMediaButtonProps) {
|
||||||
|
const {_} = useLingui()
|
||||||
|
const {requestPhotoAccessIfNeeded} = usePhotoLibraryPermission()
|
||||||
|
const {requestVideoAccessIfNeeded} = useVideoLibraryPermission()
|
||||||
|
const sheetWrapper = useSheetWrapper()
|
||||||
|
const t = useTheme()
|
||||||
|
|
||||||
|
const selectionCountRemaining = MAX_IMAGES - selectedAssetsCount
|
||||||
|
|
||||||
|
const processSelectedAssets = useCallback(
|
||||||
|
async (rawAssets: ImagePickerAsset[]) => {
|
||||||
|
const {
|
||||||
|
type,
|
||||||
|
assets,
|
||||||
|
errors: errorCodes,
|
||||||
|
} = await processImagePickerAssets(rawAssets, {
|
||||||
|
selectionCountRemaining,
|
||||||
|
allowedAssetTypes,
|
||||||
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Convert error codes to user-friendly messages.
|
||||||
|
*/
|
||||||
|
const errors = Array.from(errorCodes).map(error => {
|
||||||
|
return {
|
||||||
|
[SelectedAssetError.Unsupported]: _(
|
||||||
|
msg`One or more of your selected files are not supported.`,
|
||||||
|
),
|
||||||
|
[SelectedAssetError.MixedTypes]: _(
|
||||||
|
msg`Selecting multiple media types is not supported.`,
|
||||||
|
),
|
||||||
|
[SelectedAssetError.MaxImages]: _(
|
||||||
|
msg({
|
||||||
|
message: `You can select up to ${plural(MAX_IMAGES, {
|
||||||
|
other: '# images',
|
||||||
|
})} in total.`,
|
||||||
|
comment: `Error message for maximum number of images that can be selected to add to a post, currently 4 but may change.`,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
[SelectedAssetError.MaxVideos]: _(
|
||||||
|
msg`You can only select one video at a time.`,
|
||||||
|
),
|
||||||
|
[SelectedAssetError.VideoTooLong]: _(
|
||||||
|
msg`Videos must be less than 3 minutes long.`,
|
||||||
|
),
|
||||||
|
[SelectedAssetError.MaxGIFs]: _(
|
||||||
|
msg`You can only select one GIF at a time.`,
|
||||||
|
),
|
||||||
|
[SelectedAssetError.FileTooBig]: _(
|
||||||
|
msg`One or more of your selected files is too large. Maximum size is 100 MB.`,
|
||||||
|
),
|
||||||
|
}[error]
|
||||||
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Report the selected assets and any errors back to the
|
||||||
|
* composer.
|
||||||
|
*/
|
||||||
|
onSelectAssets({
|
||||||
|
type,
|
||||||
|
assets,
|
||||||
|
errors,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
[_, onSelectAssets, selectionCountRemaining, allowedAssetTypes],
|
||||||
|
)
|
||||||
|
|
||||||
|
const onPressSelectMedia = useCallback(async () => {
|
||||||
|
if (isNative) {
|
||||||
|
const [photoAccess, videoAccess] = await Promise.all([
|
||||||
|
requestPhotoAccessIfNeeded(),
|
||||||
|
requestVideoAccessIfNeeded(),
|
||||||
|
])
|
||||||
|
|
||||||
|
if (!photoAccess && !videoAccess) {
|
||||||
|
toast.show(_(msg`You need to allow access to your media library.`), {
|
||||||
|
type: 'error',
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNative && Keyboard.isVisible()) {
|
||||||
|
Keyboard.dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
const {assets, canceled} = await sheetWrapper(
|
||||||
|
launchImageLibraryAsync({
|
||||||
|
exif: false,
|
||||||
|
mediaTypes: ['images', 'videos'],
|
||||||
|
quality: 1,
|
||||||
|
allowsMultipleSelection: true,
|
||||||
|
legacy: true,
|
||||||
|
base64: isWeb,
|
||||||
|
selectionLimit: isIOS ? selectionCountRemaining : undefined,
|
||||||
|
preferredAssetRepresentationMode:
|
||||||
|
UIImagePickerPreferredAssetRepresentationMode.Current,
|
||||||
|
videoMaxDuration: VIDEO_MAX_DURATION_MS / 1000,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
if (canceled) return
|
||||||
|
|
||||||
|
await processSelectedAssets(assets)
|
||||||
|
}, [
|
||||||
|
_,
|
||||||
|
requestPhotoAccessIfNeeded,
|
||||||
|
requestVideoAccessIfNeeded,
|
||||||
|
sheetWrapper,
|
||||||
|
processSelectedAssets,
|
||||||
|
selectionCountRemaining,
|
||||||
|
])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
testID="openMediaBtn"
|
||||||
|
onPress={onPressSelectMedia}
|
||||||
|
label={_(
|
||||||
|
msg({
|
||||||
|
message: `Add media to post`,
|
||||||
|
comment: `Accessibility label for button in composer to add photos or a video to a post`,
|
||||||
|
}),
|
||||||
|
)}
|
||||||
|
accessibilityHint={
|
||||||
|
isNative
|
||||||
|
? _(
|
||||||
|
msg({
|
||||||
|
message: `Opens device gallery to select up to ${plural(
|
||||||
|
MAX_IMAGES,
|
||||||
|
{
|
||||||
|
other: '# images',
|
||||||
|
},
|
||||||
|
)}, or a single video.`,
|
||||||
|
comment: `Accessibility hint on native for button in composer to add images or a video to a post. Maximum number of images that can be selected is currently 4 but may change.`,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
: _(
|
||||||
|
msg({
|
||||||
|
message: `Opens device gallery to select up to ${plural(
|
||||||
|
MAX_IMAGES,
|
||||||
|
{
|
||||||
|
other: '# images',
|
||||||
|
},
|
||||||
|
)}, or a single video or GIF.`,
|
||||||
|
comment: `Accessibility hint on web for button in composer to add images, a video, or a GIF to a post. Maximum number of images that can be selected is currently 4 but may change.`,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
style={a.p_sm}
|
||||||
|
variant="ghost"
|
||||||
|
shape="round"
|
||||||
|
color="primary"
|
||||||
|
disabled={disabled}>
|
||||||
|
<ImageIcon
|
||||||
|
size="lg"
|
||||||
|
style={disabled && t.atoms.text_contrast_low}
|
||||||
|
accessibilityIgnoresInvertColors={true}
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -96,13 +96,12 @@ const ImageAltTextInner = ({
|
|||||||
<View style={[t.atoms.bg_contrast_50, a.rounded_sm, a.overflow_hidden]}>
|
<View style={[t.atoms.bg_contrast_50, a.rounded_sm, a.overflow_hidden]}>
|
||||||
<Image
|
<Image
|
||||||
style={imageStyle}
|
style={imageStyle}
|
||||||
source={{
|
source={{uri: (image.transformed ?? image.source).path}}
|
||||||
uri: (image.transformed ?? image.source).path,
|
|
||||||
}}
|
|
||||||
contentFit="contain"
|
contentFit="contain"
|
||||||
accessible={true}
|
accessible={true}
|
||||||
accessibilityIgnoresInvertColors
|
accessibilityIgnoresInvertColors
|
||||||
enableLiveTextInteraction
|
enableLiveTextInteraction
|
||||||
|
autoplay={false}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -1,60 +0,0 @@
|
|||||||
/* eslint-disable react-native-a11y/has-valid-accessibility-ignores-invert-colors */
|
|
||||||
import {useCallback} from 'react'
|
|
||||||
import {msg} from '@lingui/macro'
|
|
||||||
import {useLingui} from '@lingui/react'
|
|
||||||
|
|
||||||
import {usePhotoLibraryPermission} from '#/lib/hooks/usePermissions'
|
|
||||||
import {openPicker} from '#/lib/media/picker'
|
|
||||||
import {isNative} from '#/platform/detection'
|
|
||||||
import {ComposerImage, createComposerImage} from '#/state/gallery'
|
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
|
||||||
import {Button} from '#/components/Button'
|
|
||||||
import {useSheetWrapper} from '#/components/Dialog/sheet-wrapper'
|
|
||||||
import {Image_Stroke2_Corner0_Rounded as Image} from '#/components/icons/Image'
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
size: number
|
|
||||||
disabled?: boolean
|
|
||||||
onAdd: (next: ComposerImage[]) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export function SelectPhotoBtn({size, disabled, onAdd}: Props) {
|
|
||||||
const {_} = useLingui()
|
|
||||||
const {requestPhotoAccessIfNeeded} = usePhotoLibraryPermission()
|
|
||||||
const t = useTheme()
|
|
||||||
const sheetWrapper = useSheetWrapper()
|
|
||||||
|
|
||||||
const onPressSelectPhotos = useCallback(async () => {
|
|
||||||
if (isNative && !(await requestPhotoAccessIfNeeded())) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const images = await sheetWrapper(
|
|
||||||
openPicker({
|
|
||||||
selectionLimit: 4 - size,
|
|
||||||
allowsMultipleSelection: true,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
const results = await Promise.all(
|
|
||||||
images.map(img => createComposerImage(img)),
|
|
||||||
)
|
|
||||||
|
|
||||||
onAdd(results)
|
|
||||||
}, [requestPhotoAccessIfNeeded, size, onAdd, sheetWrapper])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
testID="openGalleryBtn"
|
|
||||||
onPress={onPressSelectPhotos}
|
|
||||||
label={_(msg`Gallery`)}
|
|
||||||
accessibilityHint={_(msg`Opens device photo gallery`)}
|
|
||||||
style={a.p_sm}
|
|
||||||
variant="ghost"
|
|
||||||
shape="round"
|
|
||||||
color="primary"
|
|
||||||
disabled={disabled}>
|
|
||||||
<Image size="lg" style={disabled && t.atoms.text_contrast_low} />
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
import {useCallback} from 'react'
|
|
||||||
import {type ImagePickerAsset} from 'expo-image-picker'
|
|
||||||
import {msg} from '@lingui/macro'
|
|
||||||
import {useLingui} from '@lingui/react'
|
|
||||||
|
|
||||||
import {
|
|
||||||
SUPPORTED_MIME_TYPES,
|
|
||||||
type SupportedMimeTypes,
|
|
||||||
VIDEO_MAX_DURATION_MS,
|
|
||||||
} from '#/lib/constants'
|
|
||||||
import {useVideoLibraryPermission} from '#/lib/hooks/usePermissions'
|
|
||||||
import {isWeb} from '#/platform/detection'
|
|
||||||
import {isNative} from '#/platform/detection'
|
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
|
||||||
import {Button} from '#/components/Button'
|
|
||||||
import {VideoClip_Stroke2_Corner0_Rounded as VideoClipIcon} from '#/components/icons/VideoClip'
|
|
||||||
import {pickVideo} from './pickVideo'
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
onSelectVideo: (video: ImagePickerAsset) => void
|
|
||||||
disabled?: boolean
|
|
||||||
setError: (error: string) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export function SelectVideoBtn({onSelectVideo, disabled, setError}: Props) {
|
|
||||||
const {_} = useLingui()
|
|
||||||
const t = useTheme()
|
|
||||||
const {requestVideoAccessIfNeeded} = useVideoLibraryPermission()
|
|
||||||
|
|
||||||
const onPressSelectVideo = useCallback(async () => {
|
|
||||||
if (isNative && !(await requestVideoAccessIfNeeded())) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await pickVideo()
|
|
||||||
if (response.assets && response.assets.length > 0) {
|
|
||||||
const asset = response.assets[0]
|
|
||||||
try {
|
|
||||||
if (isWeb) {
|
|
||||||
// asset.duration is null for gifs (see the TODO in pickVideo.web.ts)
|
|
||||||
if (asset.duration && asset.duration > VIDEO_MAX_DURATION_MS) {
|
|
||||||
throw Error(_(msg`Videos must be less than 3 minutes long`))
|
|
||||||
}
|
|
||||||
// compression step on native converts to mp4, so no need to check there
|
|
||||||
if (
|
|
||||||
!SUPPORTED_MIME_TYPES.includes(asset.mimeType as SupportedMimeTypes)
|
|
||||||
) {
|
|
||||||
throw Error(_(msg`Unsupported video type: ${asset.mimeType}`))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (typeof asset.duration !== 'number') {
|
|
||||||
throw Error('Asset is not a video')
|
|
||||||
}
|
|
||||||
if (asset.duration > VIDEO_MAX_DURATION_MS) {
|
|
||||||
throw Error(_(msg`Videos must be less than 3 minutes long`))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onSelectVideo(asset)
|
|
||||||
} catch (err) {
|
|
||||||
if (err instanceof Error) {
|
|
||||||
setError(err.message)
|
|
||||||
} else {
|
|
||||||
setError(_(msg`An error occurred while selecting the video`))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [requestVideoAccessIfNeeded, setError, _, onSelectVideo])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Button
|
|
||||||
testID="openGifBtn"
|
|
||||||
onPress={onPressSelectVideo}
|
|
||||||
label={_(msg`Select video`)}
|
|
||||||
accessibilityHint={_(msg`Opens video picker`)}
|
|
||||||
style={a.p_sm}
|
|
||||||
variant="ghost"
|
|
||||||
shape="round"
|
|
||||||
color="primary"
|
|
||||||
disabled={disabled}>
|
|
||||||
<VideoClipIcon
|
|
||||||
size="lg"
|
|
||||||
style={disabled && t.atoms.text_contrast_low}
|
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {View} from 'react-native'
|
import {View} from 'react-native'
|
||||||
import {ImagePickerAsset} from 'expo-image-picker'
|
import {Image} from 'expo-image'
|
||||||
|
import {type ImagePickerAsset} from 'expo-image-picker'
|
||||||
import {BlueskyVideoView} from '@haileyok/bluesky-video'
|
import {BlueskyVideoView} from '@haileyok/bluesky-video'
|
||||||
|
|
||||||
import {CompressedVideo} from '#/lib/media/video/types'
|
import {type CompressedVideo} from '#/lib/media/video/types'
|
||||||
import {clamp} from '#/lib/numbers'
|
import {clamp} from '#/lib/numbers'
|
||||||
import {useAutoplayDisabled} from '#/state/preferences'
|
import {useAutoplayDisabled} from '#/state/preferences'
|
||||||
import {ExternalEmbedRemoveBtn} from '#/view/com/composer/ExternalEmbedRemoveBtn'
|
import {ExternalEmbedRemoveBtn} from '#/view/com/composer/ExternalEmbedRemoveBtn'
|
||||||
@@ -48,6 +49,16 @@ export function VideoPreview({
|
|||||||
<VideoTranscodeBackdrop uri={asset.uri} />
|
<VideoTranscodeBackdrop uri={asset.uri} />
|
||||||
</View>
|
</View>
|
||||||
{isActivePost && (
|
{isActivePost && (
|
||||||
|
<>
|
||||||
|
{video.mimeType === 'image/gif' ? (
|
||||||
|
<Image
|
||||||
|
style={[a.flex_1]}
|
||||||
|
autoplay={!autoplayDisabled}
|
||||||
|
source={{uri: video.uri}}
|
||||||
|
accessibilityIgnoresInvertColors
|
||||||
|
cachePolicy="none"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
<BlueskyVideoView
|
<BlueskyVideoView
|
||||||
url={video.uri}
|
url={video.uri}
|
||||||
autoplay={!autoplayDisabled}
|
autoplay={!autoplayDisabled}
|
||||||
@@ -56,6 +67,8 @@ export function VideoPreview({
|
|||||||
ref={playerRef}
|
ref={playerRef}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<ExternalEmbedRemoveBtn onRemove={clear} />
|
<ExternalEmbedRemoveBtn onRemove={clear} />
|
||||||
{autoplayDisabled && (
|
{autoplayDisabled && (
|
||||||
<View style={[a.absolute, a.inset_0, a.justify_center, a.align_center]}>
|
<View style={[a.absolute, a.inset_0, a.justify_center, a.align_center]}>
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ function LightboxInner({
|
|||||||
const onKeyDown = useCallback(
|
const onKeyDown = useCallback(
|
||||||
(e: KeyboardEvent) => {
|
(e: KeyboardEvent) => {
|
||||||
if (e.key === 'Escape') {
|
if (e.key === 'Escape') {
|
||||||
|
e.preventDefault()
|
||||||
onClose()
|
onClose()
|
||||||
} else if (e.key === 'ArrowLeft') {
|
} else if (e.key === 'ArrowLeft') {
|
||||||
onPressLeft()
|
onPressLeft()
|
||||||
|
|||||||
@@ -11288,6 +11288,11 @@ expo-image-loader@~5.1.0:
|
|||||||
resolved "https://registry.yarnpkg.com/expo-image-loader/-/expo-image-loader-5.1.0.tgz#f7d65f9b9a9714eaaf5d50a406cb34cb25262153"
|
resolved "https://registry.yarnpkg.com/expo-image-loader/-/expo-image-loader-5.1.0.tgz#f7d65f9b9a9714eaaf5d50a406cb34cb25262153"
|
||||||
integrity sha512-sEBx3zDQIODWbB5JwzE7ZL5FJD+DK3LVLWBVJy6VzsqIA6nDEnSFnsnWyCfCTSvbGigMATs1lgkC2nz3Jpve1Q==
|
integrity sha512-sEBx3zDQIODWbB5JwzE7ZL5FJD+DK3LVLWBVJy6VzsqIA6nDEnSFnsnWyCfCTSvbGigMATs1lgkC2nz3Jpve1Q==
|
||||||
|
|
||||||
|
expo-image-loader@~6.0.0:
|
||||||
|
version "6.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/expo-image-loader/-/expo-image-loader-6.0.0.tgz#15230442cbb90e101c080a4c81e37d974e43e072"
|
||||||
|
integrity sha512-nKs/xnOGw6ACb4g26xceBD57FKLFkSwEUTDXEDF3Gtcu3MqF3ZIYd3YM+sSb1/z9AKV1dYT7rMSGVNgsveXLIQ==
|
||||||
|
|
||||||
expo-image-manipulator@~13.1.7:
|
expo-image-manipulator@~13.1.7:
|
||||||
version "13.1.7"
|
version "13.1.7"
|
||||||
resolved "https://registry.yarnpkg.com/expo-image-manipulator/-/expo-image-manipulator-13.1.7.tgz#e891ce9b49d75962eafdf5b7d670116583379e76"
|
resolved "https://registry.yarnpkg.com/expo-image-manipulator/-/expo-image-manipulator-13.1.7.tgz#e891ce9b49d75962eafdf5b7d670116583379e76"
|
||||||
@@ -11295,12 +11300,12 @@ expo-image-manipulator@~13.1.7:
|
|||||||
dependencies:
|
dependencies:
|
||||||
expo-image-loader "~5.1.0"
|
expo-image-loader "~5.1.0"
|
||||||
|
|
||||||
expo-image-picker@~16.1.4:
|
expo-image-picker@^17.0.2:
|
||||||
version "16.1.4"
|
version "17.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/expo-image-picker/-/expo-image-picker-16.1.4.tgz#d4ac2d1f64f6ec9347c3f64f8435b40e6e4dcc40"
|
resolved "https://registry.yarnpkg.com/expo-image-picker/-/expo-image-picker-17.0.2.tgz#79af7192b2947e54686d0ece6ccbb5f6a178a809"
|
||||||
integrity sha512-bTmmxtw1AohUT+HxEBn2vYwdeOrj1CLpMXKjvi9FKSoSbpcarT4xxI0z7YyGwDGHbrJqyyic3I9TTdP2J2b4YA==
|
integrity sha512-O74FIrc37KB4ZxC/BMUL3fEZwdmIB60As0q5XczRlzPvWismBl7GG3pPy+o5SGUI2jcepTvQAa2PcNcMbUZNYg==
|
||||||
dependencies:
|
dependencies:
|
||||||
expo-image-loader "~5.1.0"
|
expo-image-loader "~6.0.0"
|
||||||
|
|
||||||
expo-image@^2.4.0:
|
expo-image@^2.4.0:
|
||||||
version "2.4.0"
|
version "2.4.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user