mostly implement
This commit is contained in:
+1
-1
@@ -142,6 +142,7 @@
|
|||||||
"expo-font": "~13.3.0",
|
"expo-font": "~13.3.0",
|
||||||
"expo-haptics": "~14.1.4",
|
"expo-haptics": "~14.1.4",
|
||||||
"expo-image": "~2.1.6",
|
"expo-image": "~2.1.6",
|
||||||
|
"expo-image-crop-tool": "^0.1.3",
|
||||||
"expo-image-manipulator": "~13.1.5",
|
"expo-image-manipulator": "~13.1.5",
|
||||||
"expo-image-picker": "~16.1.4",
|
"expo-image-picker": "~16.1.4",
|
||||||
"expo-linear-gradient": "~14.1.4",
|
"expo-linear-gradient": "~14.1.4",
|
||||||
@@ -188,7 +189,6 @@
|
|||||||
"react-native-edge-to-edge": "^1.6.0",
|
"react-native-edge-to-edge": "^1.6.0",
|
||||||
"react-native-gesture-handler": "2.25.0",
|
"react-native-gesture-handler": "2.25.0",
|
||||||
"react-native-get-random-values": "~1.11.0",
|
"react-native-get-random-values": "~1.11.0",
|
||||||
"react-native-image-crop-picker": "^0.42.0",
|
|
||||||
"react-native-ios-context-menu": "^1.15.3",
|
"react-native-ios-context-menu": "^1.15.3",
|
||||||
"react-native-keyboard-controller": "^1.17.1",
|
"react-native-keyboard-controller": "^1.17.1",
|
||||||
"react-native-mmkv": "^2.12.2",
|
"react-native-mmkv": "^2.12.2",
|
||||||
|
|||||||
@@ -1,48 +0,0 @@
|
|||||||
diff --git a/node_modules/react-native-image-crop-picker/android/src/main/AndroidManifest.xml b/node_modules/react-native-image-crop-picker/android/src/main/AndroidManifest.xml
|
|
||||||
index a08629b..fab6299 100644
|
|
||||||
--- a/node_modules/react-native-image-crop-picker/android/src/main/AndroidManifest.xml
|
|
||||||
+++ b/node_modules/react-native-image-crop-picker/android/src/main/AndroidManifest.xml
|
|
||||||
@@ -24,7 +24,7 @@
|
|
||||||
|
|
||||||
<activity
|
|
||||||
android:name="com.yalantis.ucrop.UCropActivity"
|
|
||||||
- android:theme="@style/Theme.AppCompat.Light.NoActionBar" />
|
|
||||||
+ android:theme="@style/Theme.UCropNoEdgeToEdge" />
|
|
||||||
|
|
||||||
|
|
||||||
<!-- Prompt Google Play services to install the backported photo picker module -->
|
|
||||||
diff --git a/node_modules/react-native-image-crop-picker/android/src/main/res/values-v35/styles.xml b/node_modules/react-native-image-crop-picker/android/src/main/res/values-v35/styles.xml
|
|
||||||
new file mode 100644
|
|
||||||
index 0000000..5301f74
|
|
||||||
--- /dev/null
|
|
||||||
+++ b/node_modules/react-native-image-crop-picker/android/src/main/res/values-v35/styles.xml
|
|
||||||
@@ -0,0 +1,5 @@
|
|
||||||
+<resources>
|
|
||||||
+ <style name="Theme.UCropNoEdgeToEdge" parent="Theme.AppCompat.Light.NoActionBar">
|
|
||||||
+ <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
|
|
||||||
+ </style>
|
|
||||||
+</resources>
|
|
||||||
\ No newline at end of file
|
|
||||||
diff --git a/node_modules/react-native-image-crop-picker/android/src/main/res/values/styles.xml b/node_modules/react-native-image-crop-picker/android/src/main/res/values/styles.xml
|
|
||||||
new file mode 100644
|
|
||||||
index 0000000..55569aa
|
|
||||||
--- /dev/null
|
|
||||||
+++ b/node_modules/react-native-image-crop-picker/android/src/main/res/values/styles.xml
|
|
||||||
@@ -0,0 +1,3 @@
|
|
||||||
+<resources>
|
|
||||||
+ <style name="Theme.UCropNoEdgeToEdge" parent="Theme.AppCompat.Light.NoActionBar"/>
|
|
||||||
+</resources>
|
|
||||||
\ No newline at end of file
|
|
||||||
diff --git a/node_modules/react-native-image-crop-picker/ios/src/ImageCropPicker.m b/node_modules/react-native-image-crop-picker/ios/src/ImageCropPicker.m
|
|
||||||
index 9f20973..c414a7a 100644
|
|
||||||
--- a/node_modules/react-native-image-crop-picker/ios/src/ImageCropPicker.m
|
|
||||||
+++ b/node_modules/react-native-image-crop-picker/ios/src/ImageCropPicker.m
|
|
||||||
@@ -126,7 +126,7 @@ - (void) setConfiguration:(NSDictionary *)options
|
|
||||||
|
|
||||||
- (UIViewController*) getRootVC {
|
|
||||||
UIViewController *root = [[[[UIApplication sharedApplication] delegate] window] rootViewController];
|
|
||||||
- while (root.presentedViewController != nil) {
|
|
||||||
+ while (root.presentedViewController != nil && !root.presentedViewController.isBeingDismissed) {
|
|
||||||
root = root.presentedViewController;
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,7 +1,3 @@
|
|||||||
import {
|
|
||||||
Image as RNImage,
|
|
||||||
openCropper as openCropperFn,
|
|
||||||
} from 'react-native-image-crop-picker'
|
|
||||||
import {
|
import {
|
||||||
documentDirectory,
|
documentDirectory,
|
||||||
getInfoAsync,
|
getInfoAsync,
|
||||||
@@ -10,6 +6,8 @@ import {
|
|||||||
|
|
||||||
import {compressIfNeeded} from './manip'
|
import {compressIfNeeded} from './manip'
|
||||||
import {CropperOptions} from './types'
|
import {CropperOptions} from './types'
|
||||||
|
import {RNImage} from './picker.shared'
|
||||||
|
import ExpoImageCropTool from 'expo-image-crop-tool'
|
||||||
|
|
||||||
async function getFile() {
|
async function getFile() {
|
||||||
const imagesDir = documentDirectory!
|
const imagesDir = documentDirectory!
|
||||||
@@ -46,7 +44,7 @@ export async function openCamera(): Promise<RNImage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function openCropper(opts: CropperOptions) {
|
export async function openCropper(opts: CropperOptions) {
|
||||||
const item = await openCropperFn({
|
const item = await ExpoImageCropTool.openCropperAsync({
|
||||||
...opts,
|
...opts,
|
||||||
forceJpg: true, // ios only
|
forceJpg: true, // ios only
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -9,6 +9,14 @@ import {t} from '@lingui/macro'
|
|||||||
import * as Toast from '#/view/com/util/Toast'
|
import * as Toast from '#/view/com/util/Toast'
|
||||||
import {getDataUriSize} from './util'
|
import {getDataUriSize} from './util'
|
||||||
|
|
||||||
|
export type RNImage = {
|
||||||
|
mime: string
|
||||||
|
height: number
|
||||||
|
width: number
|
||||||
|
path: string
|
||||||
|
size: number
|
||||||
|
}
|
||||||
|
|
||||||
export async function openPicker(opts?: ImagePickerOptions) {
|
export async function openPicker(opts?: ImagePickerOptions) {
|
||||||
const response = await launchImageLibraryAsync({
|
const response = await launchImageLibraryAsync({
|
||||||
exif: false,
|
exif: false,
|
||||||
|
|||||||
+23
-25
@@ -1,36 +1,34 @@
|
|||||||
import {
|
import ExpoImageCropTool, {OpenCropperOptions} from 'expo-image-crop-tool'
|
||||||
Image as RNImage,
|
import {ImagePickerOptions, launchCameraAsync} from 'expo-image-picker'
|
||||||
openCamera as openCameraFn,
|
|
||||||
openCropper as openCropperFn,
|
|
||||||
} from 'react-native-image-crop-picker'
|
|
||||||
|
|
||||||
import {CameraOpts, CropperOptions} from './types'
|
export {openPicker, type RNImage} from './picker.shared'
|
||||||
export {openPicker} from './picker.shared'
|
|
||||||
|
|
||||||
export async function openCamera(opts: CameraOpts): Promise<RNImage> {
|
export async function openCamera(customOpts: ImagePickerOptions) {
|
||||||
const item = await openCameraFn({
|
const opts: ImagePickerOptions = {
|
||||||
width: opts.width,
|
mediaTypes: 'images',
|
||||||
height: opts.height,
|
...customOpts,
|
||||||
freeStyleCropEnabled: opts.freeStyleCropEnabled,
|
}
|
||||||
cropperCircleOverlay: opts.cropperCircleOverlay,
|
const res = await launchCameraAsync(opts)
|
||||||
cropping: false,
|
|
||||||
forceJpg: true, // ios only
|
if (!res || !res.assets) {
|
||||||
compressImageQuality: 0.8,
|
throw new Error('Camera was closed before taking a photo')
|
||||||
})
|
}
|
||||||
|
|
||||||
|
const asset = res?.assets[0]
|
||||||
|
|
||||||
return {
|
return {
|
||||||
path: item.path,
|
path: asset.uri,
|
||||||
mime: item.mime,
|
mime: asset.mimeType ?? 'image/jpeg',
|
||||||
size: item.size,
|
size: asset.fileSize ?? 0,
|
||||||
width: item.width,
|
width: asset.width,
|
||||||
height: item.height,
|
height: asset.height,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function openCropper(opts: CropperOptions) {
|
export async function openCropper(opts: OpenCropperOptions) {
|
||||||
const item = await openCropperFn({
|
const item = await ExpoImageCropTool.openCropperAsync({
|
||||||
...opts,
|
...opts,
|
||||||
forceJpg: true, // ios only
|
format: 'jpeg',
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
/// <reference lib="dom" />
|
/// <reference lib="dom" />
|
||||||
|
|
||||||
import {Image as RNImage} from 'react-native-image-crop-picker'
|
|
||||||
|
|
||||||
import {CameraOpts, CropperOptions} from './types'
|
import {CameraOpts, CropperOptions} from './types'
|
||||||
export {openPicker} from './picker.shared'
|
|
||||||
import {unstable__openModal} from '#/state/modals'
|
import {unstable__openModal} from '#/state/modals'
|
||||||
|
import {RNImage} from './picker.shared'
|
||||||
|
|
||||||
|
export {openPicker, type RNImage} from './picker.shared'
|
||||||
|
|
||||||
export async function openCamera(_opts: CameraOpts): Promise<RNImage> {
|
export async function openCamera(_opts: CameraOpts): Promise<RNImage> {
|
||||||
// const mediaType = opts.mediaType || 'photo' TODO
|
// const mediaType = opts.mediaType || 'photo' TODO
|
||||||
|
|||||||
@@ -182,11 +182,9 @@ export function StepProfile() {
|
|||||||
|
|
||||||
if (!isWeb) {
|
if (!isWeb) {
|
||||||
image = await openCropper({
|
image = await openCropper({
|
||||||
mediaType: 'photo',
|
imageUri: image.path,
|
||||||
cropperCircleOverlay: true,
|
shape: 'circle',
|
||||||
height: 1000,
|
aspectRatio: 1 / 1,
|
||||||
width: 1000,
|
|
||||||
path: image.path,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
image = await compressIfNeeded(image, 1000000)
|
image = await compressIfNeeded(image, 1000000)
|
||||||
|
|||||||
+1
-13
@@ -122,25 +122,13 @@ export async function cropImage(img: ComposerImage): Promise<ComposerImage> {
|
|||||||
return img
|
return img
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE
|
|
||||||
// on ios, react-native-image-crop-picker gives really bad quality
|
|
||||||
// without specifying width and height. on android, however, the
|
|
||||||
// crop stretches incorrectly if you do specify it. these are
|
|
||||||
// both separate bugs in the library. we deal with that by
|
|
||||||
// providing width & height for ios only
|
|
||||||
// -prf
|
|
||||||
|
|
||||||
const source = img.source
|
const source = img.source
|
||||||
const [w, h] = containImageRes(source.width, source.height, POST_IMG_MAX)
|
|
||||||
|
|
||||||
// @todo: we're always passing the original image here, does image-cropper
|
// @todo: we're always passing the original image here, does image-cropper
|
||||||
// allows for setting initial crop dimensions? -mary
|
// allows for setting initial crop dimensions? -mary
|
||||||
try {
|
try {
|
||||||
const cropped = await openCropper({
|
const cropped = await openCropper({
|
||||||
mediaType: 'photo',
|
imageUri: source.path,
|
||||||
path: source.path,
|
|
||||||
freeStyleCropEnabled: true,
|
|
||||||
...(isIOS ? {width: w, height: h} : {}),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -35,9 +35,7 @@ export function OpenCameraBtn({disabled, onAdd}: Props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const img = await openCamera({
|
const img = await openCamera({
|
||||||
width: POST_IMG_MAX.width,
|
aspect: [POST_IMG_MAX.width, POST_IMG_MAX.height],
|
||||||
height: POST_IMG_MAX.height,
|
|
||||||
freeStyleCropEnabled: true,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// If we don't have permissions it's fine, we just wont save it. The post itself will still have access to
|
// If we don't have permissions it's fine, we just wont save it. The post itself will still have access to
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import {
|
|||||||
View,
|
View,
|
||||||
ViewStyle,
|
ViewStyle,
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
import {Image as RNImage} from 'react-native-image-crop-picker'
|
|
||||||
import Svg, {Circle, Path, Rect} from 'react-native-svg'
|
import Svg, {Circle, Path, Rect} from 'react-native-svg'
|
||||||
import {ModerationUI} from '@atproto/api'
|
import {ModerationUI} from '@atproto/api'
|
||||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||||
@@ -23,7 +22,7 @@ import {
|
|||||||
import {makeProfileLink} from '#/lib/routes/links'
|
import {makeProfileLink} from '#/lib/routes/links'
|
||||||
import {colors} from '#/lib/styles'
|
import {colors} from '#/lib/styles'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {isAndroid, isNative, isWeb} from '#/platform/detection'
|
import {isAndroid, isIOS, isNative, isWeb} from '#/platform/detection'
|
||||||
import {precacheProfile} from '#/state/queries/profile'
|
import {precacheProfile} from '#/state/queries/profile'
|
||||||
import {HighPriorityImage} from '#/view/com/util/images/Image'
|
import {HighPriorityImage} from '#/view/com/util/images/Image'
|
||||||
import {tokens, useTheme} from '#/alf'
|
import {tokens, useTheme} from '#/alf'
|
||||||
@@ -39,7 +38,12 @@ import {MediaInsetBorder} from '#/components/MediaInsetBorder'
|
|||||||
import * as Menu from '#/components/Menu'
|
import * as Menu from '#/components/Menu'
|
||||||
import {ProfileHoverCard} from '#/components/ProfileHoverCard'
|
import {ProfileHoverCard} from '#/components/ProfileHoverCard'
|
||||||
import * as bsky from '#/types/bsky'
|
import * as bsky from '#/types/bsky'
|
||||||
import {openCamera, openCropper, openPicker} from '../../../lib/media/picker'
|
import {
|
||||||
|
openCamera,
|
||||||
|
openCropper,
|
||||||
|
openPicker,
|
||||||
|
RNImage,
|
||||||
|
} from '../../../lib/media/picker'
|
||||||
|
|
||||||
export type UserAvatarType = 'user' | 'algo' | 'list' | 'labeler'
|
export type UserAvatarType = 'user' | 'algo' | 'list' | 'labeler'
|
||||||
|
|
||||||
@@ -312,9 +316,7 @@ let EditableUserAvatar = ({
|
|||||||
|
|
||||||
onSelectNewAvatar(
|
onSelectNewAvatar(
|
||||||
await openCamera({
|
await openCamera({
|
||||||
width: 1000,
|
aspect: [1, 1],
|
||||||
height: 1000,
|
|
||||||
cropperCircleOverlay: true,
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}, [onSelectNewAvatar, requestCameraAccessIfNeeded])
|
}, [onSelectNewAvatar, requestCameraAccessIfNeeded])
|
||||||
@@ -335,17 +337,17 @@ let EditableUserAvatar = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
setTimeout(
|
||||||
|
async () => {
|
||||||
const croppedImage = await openCropper({
|
const croppedImage = await openCropper({
|
||||||
mediaType: 'photo',
|
imageUri: item.path,
|
||||||
cropperCircleOverlay: true,
|
shape: 'circle',
|
||||||
height: 1000,
|
aspectRatio: 1,
|
||||||
width: 1000,
|
|
||||||
path: item.path,
|
|
||||||
webAspectRatio: 1,
|
|
||||||
webCircularCrop: true,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
onSelectNewAvatar(croppedImage)
|
onSelectNewAvatar(croppedImage)
|
||||||
|
},
|
||||||
|
isIOS ? 500 : 0,
|
||||||
|
)
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
// Don't log errors for cancelling selection to sentry on ios or android
|
// Don't log errors for cancelling selection to sentry on ios or android
|
||||||
if (!String(e).toLowerCase().includes('cancel')) {
|
if (!String(e).toLowerCase().includes('cancel')) {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {Pressable, StyleSheet, View} from 'react-native'
|
import {Pressable, StyleSheet, View} from 'react-native'
|
||||||
import {Image as RNImage} from 'react-native-image-crop-picker'
|
|
||||||
import {Image} from 'expo-image'
|
import {Image} from 'expo-image'
|
||||||
import {ModerationUI} from '@atproto/api'
|
import {ModerationUI} from '@atproto/api'
|
||||||
import {msg, Trans} from '@lingui/macro'
|
import {msg, Trans} from '@lingui/macro'
|
||||||
@@ -25,7 +24,12 @@ import {
|
|||||||
import {StreamingLive_Stroke2_Corner0_Rounded as Library} from '#/components/icons/StreamingLive'
|
import {StreamingLive_Stroke2_Corner0_Rounded as Library} from '#/components/icons/StreamingLive'
|
||||||
import {Trash_Stroke2_Corner0_Rounded as Trash} from '#/components/icons/Trash'
|
import {Trash_Stroke2_Corner0_Rounded as Trash} from '#/components/icons/Trash'
|
||||||
import * as Menu from '#/components/Menu'
|
import * as Menu from '#/components/Menu'
|
||||||
import {openCamera, openCropper, openPicker} from '../../../lib/media/picker'
|
import {
|
||||||
|
openCamera,
|
||||||
|
openCropper,
|
||||||
|
openPicker,
|
||||||
|
RNImage,
|
||||||
|
} from '../../../lib/media/picker'
|
||||||
|
|
||||||
export function UserBanner({
|
export function UserBanner({
|
||||||
type,
|
type,
|
||||||
@@ -52,8 +56,7 @@ export function UserBanner({
|
|||||||
}
|
}
|
||||||
onSelectNewBanner?.(
|
onSelectNewBanner?.(
|
||||||
await openCamera({
|
await openCamera({
|
||||||
width: 3000,
|
aspect: [3, 1],
|
||||||
height: 1000,
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}, [onSelectNewBanner, requestCameraAccessIfNeeded])
|
}, [onSelectNewBanner, requestCameraAccessIfNeeded])
|
||||||
@@ -68,15 +71,14 @@ export function UserBanner({
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
setTimeout(async () => {
|
||||||
onSelectNewBanner?.(
|
onSelectNewBanner?.(
|
||||||
await openCropper({
|
await openCropper({
|
||||||
mediaType: 'photo',
|
imageUri: items[0].path,
|
||||||
path: items[0].path,
|
aspectRatio: 3 / 1,
|
||||||
width: 3000,
|
|
||||||
height: 1000,
|
|
||||||
webAspectRatio: 3,
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
}, 750)
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
if (!String(e).includes('Canceled')) {
|
if (!String(e).includes('Canceled')) {
|
||||||
logger.error('Failed to crop banner', {error: e})
|
logger.error('Failed to crop banner', {error: e})
|
||||||
|
|||||||
@@ -11247,6 +11247,11 @@ expo-haptics@~14.1.4:
|
|||||||
resolved "https://registry.yarnpkg.com/expo-haptics/-/expo-haptics-14.1.4.tgz#442f48b1bdf83484d4fcadc653445aaae6049b70"
|
resolved "https://registry.yarnpkg.com/expo-haptics/-/expo-haptics-14.1.4.tgz#442f48b1bdf83484d4fcadc653445aaae6049b70"
|
||||||
integrity sha512-QZdE3NMX74rTuIl82I+n12XGwpDWKb8zfs5EpwsnGi/D/n7O2Jd4tO5ivH+muEG/OCJOMq5aeaVDqqaQOhTkcA==
|
integrity sha512-QZdE3NMX74rTuIl82I+n12XGwpDWKb8zfs5EpwsnGi/D/n7O2Jd4tO5ivH+muEG/OCJOMq5aeaVDqqaQOhTkcA==
|
||||||
|
|
||||||
|
expo-image-crop-tool@^0.1.3:
|
||||||
|
version "0.1.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/expo-image-crop-tool/-/expo-image-crop-tool-0.1.3.tgz#057baf3200745f706e8ecc6cece835c4cd630013"
|
||||||
|
integrity sha512-C3hG9eYpbHRACeLKqmFK52s3aBtSQm6+vbhaabdmp202zILsGT95OWIBcjqHOd2fmaK0qq0+Yj3dz+1mSuQg+A==
|
||||||
|
|
||||||
expo-image-loader@~5.1.0:
|
expo-image-loader@~5.1.0:
|
||||||
version "5.1.0"
|
version "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"
|
||||||
@@ -16801,11 +16806,6 @@ react-native-get-random-values@~1.11.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
fast-base64-decode "^1.0.0"
|
fast-base64-decode "^1.0.0"
|
||||||
|
|
||||||
react-native-image-crop-picker@^0.42.0:
|
|
||||||
version "0.42.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/react-native-image-crop-picker/-/react-native-image-crop-picker-0.42.0.tgz#0672c080feb8ffefd65ba00a4e64bc8a1066136e"
|
|
||||||
integrity sha512-EOEkekPJ7g+CNf92HrWAGM4kcDJyVY02gQJUVH7MSNUOK11SHnurXVM0TnwIt410Y4T+lBkq3rfJEA1qDaDDwA==
|
|
||||||
|
|
||||||
react-native-ios-context-menu@^1.15.3:
|
react-native-ios-context-menu@^1.15.3:
|
||||||
version "1.15.3"
|
version "1.15.3"
|
||||||
resolved "https://registry.yarnpkg.com/react-native-ios-context-menu/-/react-native-ios-context-menu-1.15.3.tgz#c02e6a7af2df8c08d0b3e1c8f3395484b3c9c760"
|
resolved "https://registry.yarnpkg.com/react-native-ios-context-menu/-/react-native-ios-context-menu-1.15.3.tgz#c02e6a7af2df8c08d0b3e1c8f3395484b3c9c760"
|
||||||
|
|||||||
Reference in New Issue
Block a user