Test v4.1 (#320)

* New translations actions.json (German)

* New translations actions.json (Korean)

* New translations actions.json (Chinese Simplified)

* New translations actions.json (Chinese Traditional)

* New translations actions.json (Vietnamese)

* New translations actions.json (Italian)

* New translations actions.json (Portuguese, Brazilian)

* Bump packages

* New translations actions.json (Chinese Simplified)

* Fixed #108

* Fixed #117

* Fixed #137

* Fix badge not cleared on app launch

* Update Expo workflow

* Update build.yml

* New context menu largely working

* Fixed #158

* File format changes by `expo prebuild`

* Update .gitignore

* Try out notification sound

* Bump packages

* New Crowdin updates (#319)

* New translations actions.json (Portuguese, Brazilian)

* New translations timeline.json (Portuguese, Brazilian)

* New translations actions.json (Portuguese, Brazilian)

* New translations compose.json (Portuguese, Brazilian)

* New translations tabs.json (Portuguese, Brazilian)

* New translations actions.json (Vietnamese)

* New translations timeline.json (German)

* New translations mediaSelector.json (Italian)

* New translations contextMenu.json (Vietnamese)

* New translations contextMenu.json (Chinese Traditional)

* New translations contextMenu.json (Chinese Simplified)

* New translations contextMenu.json (Korean)

* New translations contextMenu.json (Italian)

* New translations contextMenu.json (German)

* New translations mediaSelector.json (Portuguese, Brazilian)

* New translations timeline.json (Portuguese, Brazilian)

* New translations timeline.json (Italian)

* New translations mediaSelector.json (German)

* New translations mediaSelector.json (Vietnamese)

* New translations mediaSelector.json (Chinese Traditional)

* New translations mediaSelector.json (Chinese Simplified)

* New translations mediaSelector.json (Korean)

* New translations timeline.json (Chinese Traditional)

* New translations timeline.json (Vietnamese)

* New translations timeline.json (Chinese Simplified)

* New translations timeline.json (Korean)

* New translations contextMenu.json (Portuguese, Brazilian)

* New translations mediaSelector.json (Vietnamese)

* New translations contextMenu.json (Vietnamese)

* New translations contextMenu.json (Vietnamese)

* New translations mediaSelector.json (Chinese Simplified)

* New translations contextMenu.json (German)

* New translations contextMenu.json (Italian)

* New translations contextMenu.json (Korean)

* New translations contextMenu.json (Chinese Simplified)

* New translations contextMenu.json (Portuguese, Brazilian)
This commit is contained in:
xmflsct 2022-06-07 22:27:24 +02:00 committed by GitHub
parent 6a1f8b3c73
commit 55053e73f8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
80 changed files with 2259 additions and 1923 deletions

View File

@ -25,10 +25,9 @@ jobs:
distribution: 'zulu'
java-version: '11'
- name: -- Step 4 -- Use Expo action
uses: expo/expo-github-action@v6
uses: expo/expo-github-action@v7
with:
expo-version: 5.x
username: ${{ secrets.EXPO_USERNAME }}
expo-version: latest
token: ${{ secrets.EXPO_TOKEN }}
- name: -- Step 5 -- Install node dependencies
run: yarn install

40
.gitignore vendored
View File

@ -5,13 +5,44 @@
coverage/
builds/
# @generated expo-cli sync-28e2ab0e9ece60556eaf932abe52d017ec33db50
# @generated expo-cli sync-e7dcf75f4e856f7b6f3239b3f3a7dd614ee755a8
# The following patterns were generated by expo-cli
# OSX
#
.DS_Store
# Xcode
#
build/
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata
*.xccheckout
*.moved-aside
DerivedData
*.hmap
*.ipa
*.xcuserstate
project.xcworkspace
# Android/IntelliJ
#
build/
.idea
.gradle
local.properties
*.iml
*.hprof
# node.js
#
node_modules/
npm-debug.log
yarn-error.log
@ -25,9 +56,12 @@ buck-out/
# Bundle artifacts
*.jsbundle
# CocoaPods
/ios/Pods/
# Expo
.expo/*
.expo-shared/*
.expo/
web-build/
dist/
# @end expo-cli

View File

@ -22,11 +22,3 @@
## OTA release channels
* `MAJOR.MINOR-environment`. Environments include `release`, `candidate` and `development`.
## Major versions mapping to native module versions
| Version | Native module version | Expo version |
| :------:| :-------------------: | :----------: |
| `0-` | `210201` | `40.0.0` |
| `1-` | `210317` | `40.0.0` |
| `2.2` | `210916` | `41.0.0` |

Binary file not shown.

View File

@ -1,4 +1,3 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<resources>
<string name="app_name">tooot</string>
<string name="expo_splash_screen_resize_mode">contain</string>

View File

@ -35,5 +35,13 @@ export default (): ExpoConfig => ({
googleServicesFile: './configs/google-services.json',
permissions: ['CAMERA', 'VIBRATE'],
blockedPermissions: ['USE_BIOMETRIC', 'USE_FINGERPRINT']
}
},
plugins: [
[
'expo-notifications',
{
sounds: ['./assets/sounds/boop.mp3']
}
]
]
})

BIN
assets/sounds/boop.mp3 Normal file

Binary file not shown.

View File

@ -4,8 +4,6 @@ PODS:
- DoubleConversion (1.1.6)
- EASClient (0.2.1):
- ExpoModulesCore
- EXApplication (4.1.0):
- ExpoModulesCore
- EXAV (11.2.3):
- ExpoModulesCore
- React-runtimeexecutor
@ -25,8 +23,6 @@ PODS:
- EXFirebaseCore (5.0.0):
- ExpoModulesCore
- Firebase/Core (= 8.14.0)
- EXFont (10.1.0):
- ExpoModulesCore
- EXImageLoader (3.2.0):
- ExpoModulesCore
- React-Core
@ -35,19 +31,14 @@ PODS:
- EXJSONUtils
- EXNotifications (0.15.2):
- ExpoModulesCore
- Expo (45.0.4):
- Expo (45.0.5):
- ExpoModulesCore
- ExpoCrypto (10.2.0):
- ExpoModulesCore
- ExpoHaptics (11.2.0):
- ExpoModulesCore
- ExpoImageManipulator (10.3.1):
- EXImageLoader
- ExpoModulesCore
- ExpoImagePicker (13.1.1):
- ExpoModulesCore
- ExpoKeepAwake (10.1.1):
- ExpoModulesCore
- ExpoLocalization (13.0.0):
- ExpoModulesCore
- ExpoModulesCore (0.9.2):
@ -55,7 +46,7 @@ PODS:
- ReactCommon/turbomodule/core
- ExpoRandom (12.2.0):
- ExpoModulesCore
- ExpoWebBrowser (10.2.0):
- ExpoWebBrowser (10.2.1):
- ExpoModulesCore
- EXScreenCapture (4.2.0):
- ExpoModulesCore
@ -67,7 +58,7 @@ PODS:
- EXStoreReview (5.2.0):
- ExpoModulesCore
- EXStructuredHeaders (2.2.1)
- EXUpdates (0.13.1):
- EXUpdates (0.13.2):
- ASN1Decoder (~> 1.8)
- EASClient
- EXManifests
@ -415,13 +406,16 @@ PODS:
- React-Core
- react-native-cameraroll (4.1.2):
- React-Core
- react-native-image-keyboard (2.2.0):
- react-native-context-menu-view (1.5.4):
- React
- react-native-netinfo (8.3.0):
- react-native-netinfo (9.0.0):
- React-Core
- react-native-pager-view (5.4.11):
- React-Core
- react-native-safe-area-context (4.2.5):
- react-native-paste-input (0.4.2):
- React-Core
- Swime (= 3.0.6)
- react-native-safe-area-context (4.3.1):
- RCT-Folly
- RCTRequired
- RCTTypeSafety
@ -523,7 +517,7 @@ PODS:
- React-logger (= 0.68.2)
- React-perflogger (= 0.68.2)
- ReactCommon/turbomodule/core (= 0.68.2)
- RNCAsyncStorage (1.17.4):
- RNCAsyncStorage (1.17.6):
- React-Core
- RNFastImage (8.5.11):
- React-Core
@ -531,6 +525,15 @@ PODS:
- SDWebImageWebPCoder (~> 0.8.4)
- RNGestureHandler (2.4.2):
- React-Core
- RNImageCropPicker (0.37.3):
- React-Core
- React-RCTImage
- RNImageCropPicker/QBImagePickerController (= 0.37.3)
- TOCropViewController
- RNImageCropPicker/QBImagePickerController (0.37.3):
- React-Core
- React-RCTImage
- TOCropViewController
- RNReanimated (2.8.0):
- DoubleConversion
- FBLazyVector
@ -561,7 +564,7 @@ PODS:
- RNScreens (3.13.1):
- React-Core
- React-RCTImage
- RNSentry (3.4.2):
- RNSentry (3.4.3):
- React-Core
- Sentry (= 7.11.0)
- RNShareMenu (5.0.5):
@ -577,13 +580,14 @@ PODS:
- Sentry (7.11.0):
- Sentry/Core (= 7.11.0)
- Sentry/Core (7.11.0)
- Swime (3.0.6)
- TOCropViewController (2.6.1)
- Yoga (1.14.0)
DEPENDENCIES:
- boost (from `../node_modules/react-native/third-party-podspecs/boost.podspec`)
- DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`)
- EASClient (from `../node_modules/expo-eas-client/ios`)
- EXApplication (from `../node_modules/expo-application/ios`)
- EXAV (from `../node_modules/expo-av/ios`)
- EXConstants (from `../node_modules/expo-constants/ios`)
- EXDevice (from `../node_modules/expo-device/ios`)
@ -591,7 +595,6 @@ DEPENDENCIES:
- EXFileSystem (from `../node_modules/expo-file-system/ios`)
- EXFirebaseAnalytics (from `../node_modules/expo-firebase-analytics/ios`)
- EXFirebaseCore (from `../node_modules/expo-firebase-core/ios`)
- EXFont (from `../node_modules/expo-font/ios`)
- EXImageLoader (from `../node_modules/expo-image-loader/ios`)
- EXJSONUtils (from `../node_modules/expo-json-utils/ios`)
- EXManifests (from `../node_modules/expo-manifests/ios`)
@ -599,9 +602,7 @@ DEPENDENCIES:
- Expo (from `../node_modules/expo/ios`)
- ExpoCrypto (from `../node_modules/expo-crypto/ios`)
- ExpoHaptics (from `../node_modules/expo-haptics/ios`)
- ExpoImageManipulator (from `../node_modules/expo-image-manipulator/ios`)
- ExpoImagePicker (from `../node_modules/expo-image-picker/ios`)
- ExpoKeepAwake (from `../node_modules/expo-keep-awake/ios`)
- ExpoLocalization (from `../node_modules/expo-localization/ios`)
- ExpoModulesCore (from `../node_modules/expo-modules-core/ios`)
- ExpoRandom (from `../node_modules/expo-random/ios`)
@ -638,9 +639,10 @@ DEPENDENCIES:
- "react-native-blur (from `../node_modules/@react-native-community/blur`)"
- react-native-blurhash (from `../node_modules/react-native-blurhash`)
- "react-native-cameraroll (from `../node_modules/@react-native-community/cameraroll`)"
- react-native-image-keyboard (from `../node_modules/react-native-image-keyboard`)
- react-native-context-menu-view (from `../node_modules/react-native-context-menu-view`)
- "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)"
- react-native-pager-view (from `../node_modules/react-native-pager-view`)
- "react-native-paste-input (from `../node_modules/@mattermost/react-native-paste-input`)"
- react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`)
- "react-native-segmented-control (from `../node_modules/@react-native-community/segmented-control`)"
- React-perflogger (from `../node_modules/react-native/ReactCommon/reactperflogger`)
@ -658,6 +660,7 @@ DEPENDENCIES:
- "RNCAsyncStorage (from `../node_modules/@react-native-async-storage/async-storage`)"
- RNFastImage (from `../node_modules/react-native-fast-image`)
- RNGestureHandler (from `../node_modules/react-native-gesture-handler`)
- RNImageCropPicker (from `../node_modules/react-native-image-crop-picker`)
- RNReanimated (from `../node_modules/react-native-reanimated`)
- RNScreens (from `../node_modules/react-native-screens`)
- "RNSentry (from `../node_modules/@sentry/react-native`)"
@ -685,6 +688,8 @@ SPEC REPOS:
- SDWebImage
- SDWebImageWebPCoder
- Sentry
- Swime
- TOCropViewController
EXTERNAL SOURCES:
boost:
@ -693,8 +698,6 @@ EXTERNAL SOURCES:
:podspec: "../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec"
EASClient:
:path: "../node_modules/expo-eas-client/ios"
EXApplication:
:path: "../node_modules/expo-application/ios"
EXAV:
:path: "../node_modules/expo-av/ios"
EXConstants:
@ -709,8 +712,6 @@ EXTERNAL SOURCES:
:path: "../node_modules/expo-firebase-analytics/ios"
EXFirebaseCore:
:path: "../node_modules/expo-firebase-core/ios"
EXFont:
:path: "../node_modules/expo-font/ios"
EXImageLoader:
:path: "../node_modules/expo-image-loader/ios"
EXJSONUtils:
@ -725,12 +726,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/expo-crypto/ios"
ExpoHaptics:
:path: "../node_modules/expo-haptics/ios"
ExpoImageManipulator:
:path: "../node_modules/expo-image-manipulator/ios"
ExpoImagePicker:
:path: "../node_modules/expo-image-picker/ios"
ExpoKeepAwake:
:path: "../node_modules/expo-keep-awake/ios"
ExpoLocalization:
:path: "../node_modules/expo-localization/ios"
ExpoModulesCore:
@ -795,12 +792,14 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-blurhash"
react-native-cameraroll:
:path: "../node_modules/@react-native-community/cameraroll"
react-native-image-keyboard:
:path: "../node_modules/react-native-image-keyboard"
react-native-context-menu-view:
:path: "../node_modules/react-native-context-menu-view"
react-native-netinfo:
:path: "../node_modules/@react-native-community/netinfo"
react-native-pager-view:
:path: "../node_modules/react-native-pager-view"
react-native-paste-input:
:path: "../node_modules/@mattermost/react-native-paste-input"
react-native-safe-area-context:
:path: "../node_modules/react-native-safe-area-context"
react-native-segmented-control:
@ -835,6 +834,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-fast-image"
RNGestureHandler:
:path: "../node_modules/react-native-gesture-handler"
RNImageCropPicker:
:path: "../node_modules/react-native-image-crop-picker"
RNReanimated:
:path: "../node_modules/react-native-reanimated"
RNScreens:
@ -853,7 +854,6 @@ SPEC CHECKSUMS:
boost: a7c83b31436843459a1961bfd74b96033dc77234
DoubleConversion: 831926d9b8bf8166fd87886c4abab286c2422662
EASClient: 93565f4d024559b75eac62bc7d50acaa354614f6
EXApplication: d6562af1204162e0ac46d341a7d4e5dc720b33de
EXAV: 88f61c5af8415715b7ee51f084c1020235b85c56
EXConstants: fdbe52259365b6a6faaa5e99a3b82cfa6bc2eb61
EXDevice: 0115b360059ccd32c1701744e374e3259ffbdd3c
@ -861,27 +861,24 @@ SPEC CHECKSUMS:
EXFileSystem: 2aa2d9289f84bca9532b9ccbd81504fa31eb1ded
EXFirebaseAnalytics: aeefc63f92277313c3ee86da6a7ecf892f345ed1
EXFirebaseCore: bdfa87df74fa1b74a6b38957561456aabad28a4f
EXFont: 04235cc22e6fef86028feb67db452978dc6f240f
EXImageLoader: b88e053d760f85a82405b1db2de4abf11978fc9f
EXJSONUtils: 2a74b8f40f1523cc3f92af99c91aa78201737a77
EXManifests: 0c6134b7b6f3236a93a778c3f44ba1cfb3f9fa3d
EXNotifications: ea9fc56d27d1fee229489c5d8f452c7f367c237e
Expo: 64d52669fa3b9342919b5b44b2b4f15f19b0cf76
Expo: b9fff0a1eac0f424fc68ea49b4347fb308e52e17
ExpoCrypto: d0d0f3e20875dc450b4ec88f0fb608da5c2c6c17
ExpoHaptics: ad58ec96a25e57579c14a47c7d71f0de0de8656a
ExpoImageManipulator: b55580bbc7b10099c7707949903e7176a8542ee8
ExpoImagePicker: d9d6b4f29db437fc7796f13cee5f133f5b4b5f7c
ExpoKeepAwake: c0c494b442ecd8122974c13b93ccfb57bd408e88
ExpoLocalization: 8f619bb6eec64575cd5220bfabbd7b4e2d6f33f8
ExpoModulesCore: e4278a668e8c13c0269ed8b8a4200989deea2973
ExpoRandom: 14df0976aa363a71a730ceb7655250f3047c0e42
ExpoWebBrowser: 818c519c3519cdd79780228039938fbd8236c885
ExpoWebBrowser: 4b5f9633e5f169dc948587cb6d26d2d1d1406187
EXScreenCapture: cbee2204f313038a1819d31ad99a31e15f8e0f59
EXSecureStore: aaae919d83aec2faf031e99398807edac0313285
EXSplashScreen: 34f460788db8d682883871708dddbfac72095bb7
EXStoreReview: e61fbd500624ee7363ab134ee247cff380a8b254
EXStructuredHeaders: 5d86829469399370a9fc7cb1e4391b09de87681d
EXUpdates: 08c3931d6a5f39686091130a10310266ae3ba5f9
EXUpdates: 08d69031f9ed1e918d50f041fa505fe67d6b4809
EXUpdatesInterface: 0b101ace1dbfa0f64260a5df31c71d03c66cca54
EXVideoThumbnails: 19e055dc3245b53c536da9e0ef9c618fd2118297
FBLazyVector: a7a655862f6b09625d11c772296b01cd5164b648
@ -918,10 +915,11 @@ SPEC CHECKSUMS:
react-native-blur: cad4d93b364f91e7b7931b3fa935455487e5c33c
react-native-blurhash: add4df9a937b4e021a24bc67a0714f13e0bd40b7
react-native-cameraroll: 2957f2bce63ae896a848fbe0d5352c1bd4d20866
react-native-image-keyboard: adbf5996b8592a7d8cb8d3e431a9607f9cf3b270
react-native-netinfo: 3671b091c4843fda5e153612866ef4024b8f5d62
react-native-context-menu-view: b0beca02aad4bd9f9d7d932bf437e0a03baa69ef
react-native-netinfo: 5b664b2945a8f02102b296f0f812bddd6827ed9c
react-native-pager-view: 7f00d63688f7df9fad86dfb0154814419cc5eb8d
react-native-safe-area-context: ebf8c413eb8b5f7c392a036a315eb7b46b96845f
react-native-paste-input: efbf0b08fa1673f0e3131da6ea01678c1bb8003e
react-native-safe-area-context: 6c12e3859b6f27b25de4fee8201cfb858432d8de
react-native-segmented-control: 65df6cd0619b780b3843d574a72d4c7cec396097
React-perflogger: a18b4f0bd933b8b24ecf9f3c54f9bf65180f3fe6
React-RCTActionSheet: 547fe42fdb4b6089598d79f8e1d855d7c23e2162
@ -935,17 +933,20 @@ SPEC CHECKSUMS:
React-RCTVibration: 79040b92bfa9c3c2d2cb4f57e981164ec7ab9374
React-runtimeexecutor: b960b687d2dfef0d3761fbb187e01812ebab8b23
ReactCommon: 095366164a276d91ea704ce53cb03825c487a3f2
RNCAsyncStorage: 9367a646dc24e3ab7b6874d79bc1bfd0832dce58
RNCAsyncStorage: 466b9df1a14bccda91da86e0b7d9a345d78e1673
RNFastImage: 945abf54742505d790d9024d230c69b1e866bc88
RNGestureHandler: 61628a2c859172551aa2100d3e73d1e57878392f
RNImageCropPicker: 44e2807bc410741f35d4c45b6586aedfe3da39d2
RNReanimated: 64573e25e078ae6bec03b891586d50b9ec284393
RNScreens: 40a2cb40a02a609938137a1e0acfbf8fc9eebf19
RNSentry: 2cd1daa124b0d9fd0dfc2cb6094fdd168cb579bc
RNSentry: 85f6525b5fe8d2ada065858026b338605b3c09da
RNShareMenu: c69282e50ac439737a86949a55c7b023b90027c8
RNSVG: 302bfc9905bd8122f08966dc2ce2d07b7b52b9f8
SDWebImage: 0905f1b7760fc8ac4198cae0036600d67478751e
SDWebImageWebPCoder: f93010f3f6c031e2f8fb3081ca4ee6966c539815
Sentry: 0c5cd63d714187b4a39c331c1f0eb04ba7868341
Swime: d7b2c277503b6cea317774aedc2dce05613f8b0b
TOCropViewController: edfd4f25713d56905ad1e0b9f5be3fbe0f59c863
Yoga: 99652481fcd320aefa4a7ef90095b95acd181952
PODFILE CHECKSUM: d6d20fa7c51228cebc309aed987ed7d8f4274844

View File

@ -1,3 +1,3 @@
{
"expo.jsEngine": "hermes"
}
}

View File

@ -6,6 +6,5 @@
*/
"NSCameraUsageDescription" = "Allow tooot to capture photo or video and attach it to your toot";
"NSPhotoLibraryAddUsageDescription" = "Allow tooot to save an image to your camera roll";
"NSPhotoLibraryUsageDescription" = "Allow tooot to access your camera roll to attach photos or videos to your toot";

View File

@ -12,6 +12,7 @@
13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
34A37A6C820725DC6DDAA0EE /* libPods-ShareExtension.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1B3640FCDF7C4396A68A74D1 /* libPods-ShareExtension.a */; };
3E461D99554A48A4959DE609 /* SplashScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */; };
4986628FD0DD4630BFE5F388 /* boop.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = DF8133F098604A10B0D94952 /* boop.mp3 */; };
5E36538325C9B8BD009F93EE /* RootViewColor.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5E36538225C9B8BD009F93EE /* RootViewColor.xcassets */; };
5EE088C926297820007E5FEC /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5EE088CB26297820007E5FEC /* InfoPlist.strings */; };
5EE44DD62600124E00A9BCED /* File.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EE44DD52600124E00A9BCED /* File.swift */; };
@ -71,6 +72,7 @@
AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = SplashScreen.storyboard; path = tooot/SplashScreen.storyboard; sourceTree = "<group>"; };
B96B72E5384D44A7B240B27E /* GoogleService-Info.plist */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "tooot/GoogleService-Info.plist"; sourceTree = "<group>"; };
BB2F792C24A3F905000567C9 /* Expo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Expo.plist; sourceTree = "<group>"; };
DF8133F098604A10B0D94952 /* boop.mp3 */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = undefined; includeInIndex = 0; lastKnownFileType = unknown; name = boop.mp3; path = tooot/boop.mp3; sourceTree = "<group>"; };
E613A80A28282A01003C97D6 /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AppDelegate.mm; path = tooot/AppDelegate.mm; sourceTree = "<group>"; };
E633A420281EAEAB000E540F /* ShareExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = ShareExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
E633A427281EAEAB000E540F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
@ -115,6 +117,7 @@
5E36538225C9B8BD009F93EE /* RootViewColor.xcassets */,
B96B72E5384D44A7B240B27E /* GoogleService-Info.plist */,
5EE088CB26297820007E5FEC /* InfoPlist.strings */,
DF8133F098604A10B0D94952 /* boop.mp3 */,
);
name = tooot;
sourceTree = "<group>";
@ -310,6 +313,7 @@
13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */,
3E461D99554A48A4959DE609 /* SplashScreen.storyboard in Resources */,
DA8B5B7F0DED488CAC0FF169 /* GoogleService-Info.plist in Resources */,
4986628FD0DD4630BFE5F388 /* boop.mp3 in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -386,13 +390,17 @@
"${PODS_ROOT}/Target Support Files/Pods-tooot/Pods-tooot-resources.sh",
"${PODS_CONFIGURATION_BUILD_DIR}/EXConstants/EXConstants.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/EXUpdates/EXUpdates.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/RNImageCropPicker/QBImagePicker.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/React-Core/AccessibilityResources.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/TOCropViewController/TOCropViewControllerBundle.bundle",
);
name = "[CP] Copy Pods Resources";
outputPaths = (
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXConstants.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXUpdates.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/QBImagePicker.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AccessibilityResources.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/TOCropViewControllerBundle.bundle",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;

View File

@ -1,92 +1,90 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleAllowMixedLocalizations</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
<string>tooot</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>0.2</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>com.xmflsct.app.tooot</string>
<key>CFBundleURLSchemes</key>
<array>
<string>tooot-share</string>
<string>tooot</string>
</array>
</dict>
</array>
<key>CFBundleVersion</key>
<string>2102022230</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSApplicationCategoryType</key>
<string>public.app-category.social-networking</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>localhost</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
</dict>
</dict>
<key>NSCameraUsageDescription</key>
<string>Allow $(PRODUCT_NAME) to capture photo or video and attach it to your toot</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string></string>
<key>NSMainNibFile</key>
<string>LaunchScreen</string>
<key>NSMicrophoneUsageDescription</key>
<string>$(PRODUCT_NAME) DOES NOT need microphone permission. Please reject this request.</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>Allow $(PRODUCT_NAME) to save an image to your camera roll</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Allow $(PRODUCT_NAME) to access your camera roll to attach photos or videos to your toot</string>
<key>UILaunchStoryboardName</key>
<string>SplashScreen</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UIRequiresFullScreen</key>
<false/>
<key>UIStatusBarHidden</key>
<true/>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIUserInterfaceStyle</key>
<string>Automatic</string>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
</dict>
<dict>
<key>CFBundleAllowMixedLocalizations</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
<string>tooot</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>0.2</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>com.xmflsct.app.tooot</string>
<key>CFBundleURLSchemes</key>
<array>
<string>tooot-share</string>
<string>tooot</string>
</array>
</dict>
</array>
<key>CFBundleVersion</key>
<string>2102022230</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSApplicationCategoryType</key>
<string>public.app-category.social-networking</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>localhost</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
</dict>
</dict>
<key>NSLocationWhenInUseUsageDescription</key>
<string></string>
<key>NSMainNibFile</key>
<string>LaunchScreen</string>
<key>NSMicrophoneUsageDescription</key>
<string>$(PRODUCT_NAME) DOES NOT need microphone permission. Please reject this request.</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>Allow $(PRODUCT_NAME) to save an image to your camera roll</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Allow $(PRODUCT_NAME) to access your camera roll to attach photos or videos to your toot</string>
<key>UILaunchStoryboardName</key>
<string>SplashScreen</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UIRequiresFullScreen</key>
<false/>
<key>UIStatusBarHidden</key>
<true/>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIUserInterfaceStyle</key>
<string>Automatic</string>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
</dict>
</plist>

View File

@ -1,18 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>EXUpdatesCheckOnLaunch</key>
<string>WIFI_ONLY</string>
<key>EXUpdatesEnabled</key>
<true/>
<key>EXUpdatesLaunchWaitMs</key>
<integer>0</integer>
<key>EXUpdatesReleaseChannel</key>
<string>0-development</string>
<key>EXUpdatesSDKVersion</key>
<string>0</string>
<key>EXUpdatesURL</key>
<string>https://exp.host/@xmflsct/tooot</string>
</dict>
</plist>
<dict>
<key>EXUpdatesCheckOnLaunch</key>
<string>WIFI_ONLY</string>
<key>EXUpdatesEnabled</key>
<true/>
<key>EXUpdatesLaunchWaitMs</key>
<integer>0</integer>
<key>EXUpdatesReleaseChannel</key>
<string>0-development</string>
<key>EXUpdatesSDKVersion</key>
<string>0</string>
<key>EXUpdatesURL</key>
<string>https://exp.host/@xmflsct/tooot</string>
</dict>
</plist>

BIN
ios/tooot/boop.mp3 Normal file

Binary file not shown.

View File

@ -1,24 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>aps-environment</key>
<string>development</string>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>group.com.xmflsct.app.tooot</string>
</array>
<key>com.apple.security.device.audio-input</key>
<true/>
<key>com.apple.security.device.camera</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.personal-information.location</key>
<true/>
<key>com.apple.security.personal-information.photos-library</key>
<true/>
</dict>
</plist>
<dict>
<key>aps-environment</key>
<string>development</string>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>group.com.xmflsct.app.tooot</string>
</array>
<key>com.apple.security.device.audio-input</key>
<true/>
<key>com.apple.security.device.camera</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.personal-information.location</key>
<true/>
<key>com.apple.security.personal-information.photos-library</key>
<true/>
</dict>
</plist>

View File

@ -6,6 +6,5 @@
*/
"NSCameraUsageDescription" = "允许tooot拍摄图片或视频以添加嘟文附件";
"NSPhotoLibraryAddUsageDescription" = "允许tooot保存图片至相册";
"NSPhotoLibraryUsageDescription" = "允许tooot读取相册图片或视频以添加嘟文附件";

View File

@ -1,10 +1,10 @@
{
"name": "tooot",
"versions": {
"native": "220508",
"native": "220603",
"major": 4,
"minor": 0,
"patch": 4,
"minor": 1,
"patch": 0,
"expo": "45.0.0"
},
"description": "tooot app for Mastodon",
@ -26,27 +26,28 @@
},
"dependencies": {
"@expo/react-native-action-sheet": "3.13.0",
"@formatjs/intl-datetimeformat": "^6.0.1",
"@formatjs/intl-getcanonicallocales": "^2.0.1",
"@formatjs/intl-locale": "^3.0.1",
"@formatjs/intl-numberformat": "^8.0.1",
"@formatjs/intl-pluralrules": "^5.0.1",
"@formatjs/intl-relativetimeformat": "^11.0.1",
"@formatjs/intl-datetimeformat": "^6.0.2",
"@formatjs/intl-getcanonicallocales": "^2.0.2",
"@formatjs/intl-locale": "^3.0.2",
"@formatjs/intl-numberformat": "^8.0.2",
"@formatjs/intl-pluralrules": "^5.0.2",
"@formatjs/intl-relativetimeformat": "^11.0.2",
"@mattermost/react-native-paste-input": "^0.4.2",
"@neverdull-agency/expo-unlimited-secure-store": "1.0.10",
"@react-native-async-storage/async-storage": "1.17.4",
"@react-native-async-storage/async-storage": "1.17.6",
"@react-native-community/blur": "3.6.0",
"@react-native-community/cameraroll": "4.1.2",
"@react-native-community/netinfo": "8.3.0",
"@react-native-community/netinfo": "9.0.0",
"@react-native-community/segmented-control": "2.2.2",
"@react-navigation/bottom-tabs": "6.3.1",
"@react-navigation/native": "6.0.10",
"@react-navigation/native-stack": "6.6.2",
"@react-navigation/stack": "6.2.1",
"@reduxjs/toolkit": "1.8.2",
"@sentry/react-native": "3.4.2",
"@sentry/react-native": "3.4.3",
"@sharcoux/slider": "6.0.3",
"axios": "0.27.2",
"expo": "45.0.4",
"expo": "45.0.5",
"expo-auth-session": "3.6.1",
"expo-av": "11.2.3",
"expo-constants": "^13.1.1",
@ -55,7 +56,6 @@
"expo-file-system": "14.0.0",
"expo-firebase-analytics": "7.0.0",
"expo-haptics": "11.2.0",
"expo-image-manipulator": "10.3.1",
"expo-image-picker": "13.1.1",
"expo-linking": "3.1.0",
"expo-localization": "13.0.0",
@ -65,29 +65,30 @@
"expo-secure-store": "11.2.0",
"expo-splash-screen": "0.15.1",
"expo-store-review": "5.2.0",
"expo-updates": "0.13.1",
"expo-updates": "0.13.2",
"expo-video-thumbnails": "6.3.0",
"expo-web-browser": "10.2.0",
"expo-web-browser": "10.2.1",
"i18next": "21.8.8",
"li": "1.3.0",
"lodash": "4.17.21",
"react": "17.0.2",
"react-dom": "17.0.2",
"react-i18next": "11.17.0",
"react-intl": "^6.0.3",
"react-intl": "^6.0.4",
"react-native": "0.68.2",
"react-native-animated-spinkit": "1.5.2",
"react-native-base64": "^0.2.1",
"react-native-blurhash": "1.1.10",
"react-native-context-menu-view": "xmflsct/react-native-context-menu-view",
"react-native-fast-image": "8.5.11",
"react-native-feather": "1.1.2",
"react-native-flash-message": "0.2.1",
"react-native-gesture-handler": "2.4.2",
"react-native-htmlview": "0.16.0",
"react-native-image-keyboard": "^2.2.0",
"react-native-image-crop-picker": "^0.37.3",
"react-native-pager-view": "5.4.11",
"react-native-reanimated": "2.8.0",
"react-native-safe-area-context": "4.2.5",
"react-native-safe-area-context": "4.3.1",
"react-native-screens": "3.13.1",
"react-native-share-menu": "^5.0.5",
"react-native-svg": "12.3.0",
@ -97,7 +98,7 @@
"react-redux": "8.0.2",
"redux-persist": "6.0.0",
"rn-placeholder": "3.0.3",
"sentry-expo": "4.1.1",
"sentry-expo": "4.2.0",
"tslib": "2.4.0",
"valid-url": "1.0.9"
},
@ -110,7 +111,7 @@
"@types/lodash": "4.14.182",
"@types/react": "17.0.43",
"@types/react-dom": "17.0.14",
"@types/react-native": "0.67.7",
"@types/react-native": "0.67.8",
"@types/react-native-base64": "^0.2.0",
"@types/react-native-share-menu": "^5.0.2",
"@types/react-timeago": "4.1.3",
@ -151,4 +152,4 @@
}
}
}
}
}

7
src/@types/app.d.ts vendored
View File

@ -13,11 +13,4 @@ declare namespace App {
| 'Conversations'
| 'Bookmarks'
| 'Favourites'
interface IImageInfo {
uri: string
width: number
height: number
type?: 'image' | 'video'
}
}

View File

@ -21,7 +21,6 @@ import * as SplashScreen from 'expo-splash-screen'
import React, { useCallback, useEffect, useState } from 'react'
import { LogBox, Platform } from 'react-native'
import { GestureHandlerRootView } from 'react-native-gesture-handler'
import 'react-native-image-keyboard'
import { enableFreeze } from 'react-native-screens'
import { QueryClientProvider } from 'react-query'
import { Provider } from 'react-redux'

View File

@ -178,8 +178,49 @@ const Screens: React.FC<Props> = ({ localCorrupt }) => {
}
let text: string | undefined = undefined
let images: { type: string; uri: string }[] = []
let video: { type: string; uri: string } | undefined = undefined
let media: { path: string; mime: string }[] = []
const typesImage = ['png', 'jpg', 'jpeg', 'gif']
const typesVideo = ['mp4', 'm4v', 'mov', 'webm', 'mpeg']
const filterMedia = ({ path, mime }: { path: string; mime: string }) => {
if (mime.startsWith('image/')) {
if (!typesImage.includes(mime.split('/')[1])) {
console.warn('Image type not supported:', mime.split('/')[1])
displayMessage({
message: t('shareError.imageNotSupported', {
type: mime.split('/')[1]
}),
type: 'error',
theme
})
return
}
media.push({ path, mime })
} else if (mime.startsWith('video/')) {
if (!typesVideo.includes(mime.split('/')[1])) {
console.warn('Video type not supported:', mime.split('/')[1])
displayMessage({
message: t('shareError.videoNotSupported', {
type: mime.split('/')[1]
}),
type: 'error',
theme
})
return
}
media.push({ path, mime })
} else {
if (typesImage.includes(path.split('.').pop() || '')) {
media.push({ path: path, mime: 'image/jpg' })
return
}
if (typesVideo.includes(path.split('.').pop() || '')) {
media.push({ path: path, mime: 'video/mp4' })
return
}
text = !text ? path : text.concat(text, `\n${path}`)
}
}
switch (Platform.OS) {
case 'ios':
@ -187,55 +228,11 @@ const Screens: React.FC<Props> = ({ localCorrupt }) => {
return
}
item.data.forEach(d => {
if (typeof d === 'string') return
const typesImage = ['png', 'jpg', 'jpeg', 'gif']
const typesVideo = ['mp4', 'm4v', 'mov', 'webm']
const { mimeType, data } = d
if (mimeType.startsWith('image/')) {
if (!typesImage.includes(mimeType.split('/')[1])) {
console.warn(
'Image type not supported:',
mimeType.split('/')[1]
)
displayMessage({
message: t('shareError.imageNotSupported', {
type: mimeType.split('/')[1]
}),
type: 'error',
theme
})
return
}
images.push({ type: mimeType.split('/')[1], uri: data })
} else if (mimeType.startsWith('video/')) {
if (!typesVideo.includes(mimeType.split('/')[1])) {
console.warn(
'Video type not supported:',
mimeType.split('/')[1]
)
displayMessage({
message: t('shareError.videoNotSupported', {
type: mimeType.split('/')[1]
}),
type: 'error',
theme
})
return
}
video = { type: mimeType.split('/')[1], uri: data }
} else {
if (typesImage.includes(data.split('.').pop() || '')) {
images.push({ type: data.split('.').pop()!, uri: data })
return
}
if (typesVideo.includes(data.split('.').pop() || '')) {
video = { type: data.split('.').pop()!, uri: data }
return
}
text = !text ? data : text.concat(text, `\n${data}`)
for (const d of item.data) {
if (typeof d !== 'string') {
filterMedia({ path: d.data, mime: d.mimeType })
}
})
}
break
case 'android':
if (!item.mimeType) {
@ -247,65 +244,16 @@ const Screens: React.FC<Props> = ({ localCorrupt }) => {
} else {
tempData = item.data
}
tempData.forEach(d => {
const typesImage = ['png', 'jpg', 'jpeg', 'gif']
const typesVideo = ['mp4', 'm4v', 'mov', 'webm', 'mpeg']
if (item.mimeType!.startsWith('image/')) {
if (!typesImage.includes(item.mimeType.split('/')[1])) {
console.warn(
'Image type not supported:',
item.mimeType.split('/')[1]
)
displayMessage({
message: t('shareError.imageNotSupported', {
type: item.mimeType.split('/')[1]
}),
type: 'error',
theme
})
return
}
images.push({ type: item.mimeType.split('/')[1], uri: d })
} else if (item.mimeType.startsWith('video/')) {
if (!typesVideo.includes(item.mimeType.split('/')[1])) {
console.warn(
'Video type not supported:',
item.mimeType.split('/')[1]
)
displayMessage({
message: t('shareError.videoNotSupported', {
type: item.mimeType.split('/')[1]
}),
type: 'error',
theme
})
return
}
video = { type: item.mimeType.split('/')[1], uri: d }
} else {
if (typesImage.includes(d.split('.').pop() || '')) {
images.push({ type: d.split('.').pop()!, uri: d })
return
}
if (typesVideo.includes(d.split('.').pop() || '')) {
video = { type: d.split('.').pop()!, uri: d }
return
}
text = !text ? d : text.concat(text, `\n${d}`)
}
})
for (const d of item.data) {
filterMedia({ path: d, mime: item.mimeType })
}
break
}
if (!text && (!images || !images.length) && !video) {
if (!text && !media.length) {
return
} else {
navigationRef.navigate('Screen-Compose', {
type: 'share',
text,
images,
video
})
navigationRef.navigate('Screen-Compose', { type: 'share', text, media })
}
},
[instanceActive]

View File

@ -0,0 +1,164 @@
import analytics from '@components/analytics'
import { displayMessage } from '@components/Message'
import {
MutationVarsTimelineUpdateAccountProperty,
QueryKeyTimeline,
useTimelineMutation
} from '@utils/queryHooks/timeline'
import { getInstanceAccount } from '@utils/slices/instancesSlice'
import { useTheme } from '@utils/styles/ThemeManager'
import { useTranslation } from 'react-i18next'
import { Platform } from 'react-native'
import { ContextMenuAction } from 'react-native-context-menu-view'
import { useQueryClient } from 'react-query'
import { useSelector } from 'react-redux'
export interface Props {
actions: ContextMenuAction[]
queryKey?: QueryKeyTimeline
rootQueryKey?: QueryKeyTimeline
id: Mastodon.Account['id']
}
const contextMenuAccount = ({
actions,
queryKey,
rootQueryKey,
id: accountId
}: Props) => {
const { theme } = useTheme()
const { t } = useTranslation('componentContextMenu')
const queryClient = useQueryClient()
const mutateion = useTimelineMutation({
onSuccess: (_, params) => {
const theParams = params as MutationVarsTimelineUpdateAccountProperty
displayMessage({
theme,
type: 'success',
message: t('common:message.success.message', {
function: t(`account.${theParams.payload.property}.action`)
})
})
},
onError: (err: any, params) => {
const theParams = params as MutationVarsTimelineUpdateAccountProperty
displayMessage({
theme,
type: 'error',
message: t('common:message.error.message', {
function: t(`account.${theParams.payload.property}.action`)
}),
...(err.status &&
typeof err.status === 'number' &&
err.data &&
err.data.error &&
typeof err.data.error === 'string' && {
description: err.data.error
})
})
},
onSettled: () => {
queryKey && queryClient.invalidateQueries(queryKey)
rootQueryKey && queryClient.invalidateQueries(rootQueryKey)
}
})
const instanceAccount = useSelector(
getInstanceAccount,
(prev, next) => prev.id === next.id
)
const ownAccount = instanceAccount?.id === accountId
if (!ownAccount) {
switch (Platform.OS) {
case 'ios':
actions.push({
id: 'account',
title: t('account.title'),
inlineChildren: true,
actions: [
{
id: 'account-mute',
title: t('account.mute.action'),
systemIcon: 'eye.slash'
},
{
id: 'account-block',
title: t('account.block.action'),
systemIcon: 'xmark.circle',
destructive: true
},
{
id: 'account-reports',
title: t('account.reports.action'),
systemIcon: 'flag',
destructive: true
}
]
})
break
default:
actions.push(
{
id: 'account-mute',
title: t('account.mute.action'),
systemIcon: 'eye.slash'
},
{
id: 'account-block',
title: t('account.block.action'),
systemIcon: 'xmark.circle',
destructive: true
},
{
id: 'account-reports',
title: t('account.reports.action'),
systemIcon: 'flag',
destructive: true
}
)
break
}
}
return (id: string) => {
switch (id) {
case 'account-mute':
analytics('timeline_shared_headeractions_account_mute_press', {
page: queryKey && queryKey[1].page
})
mutateion.mutate({
type: 'updateAccountProperty',
queryKey,
id: accountId,
payload: { property: 'mute' }
})
break
case 'account-block':
analytics('timeline_shared_headeractions_account_block_press', {
page: queryKey && queryKey[1].page
})
mutateion.mutate({
type: 'updateAccountProperty',
queryKey,
id: accountId,
payload: { property: 'block' }
})
break
case 'account-report':
analytics('timeline_shared_headeractions_account_reports_press', {
page: queryKey && queryKey[1].page
})
mutateion.mutate({
type: 'updateAccountProperty',
queryKey,
id: accountId,
payload: { property: 'reports' }
})
break
}
}
}
export default contextMenuAccount

View File

@ -0,0 +1,108 @@
import analytics from '@components/analytics'
import { displayMessage } from '@components/Message'
import {
QueryKeyTimeline,
useTimelineMutation
} from '@utils/queryHooks/timeline'
import { getInstanceUrl } from '@utils/slices/instancesSlice'
import { useTheme } from '@utils/styles/ThemeManager'
import { useTranslation } from 'react-i18next'
import { Alert, Platform } from 'react-native'
import { ContextMenuAction } from 'react-native-context-menu-view'
import { useQueryClient } from 'react-query'
import { useSelector } from 'react-redux'
export interface Props {
actions: ContextMenuAction[]
status: Mastodon.Status
queryKey: QueryKeyTimeline
rootQueryKey?: QueryKeyTimeline
}
const contextMenuInstance = ({
actions,
status,
queryKey,
rootQueryKey
}: Props) => {
const { t } = useTranslation('componentContextMenu')
const { theme } = useTheme()
const currentInstance = useSelector(getInstanceUrl)
const instance = status.uri && status.uri.split(new RegExp(/\/\/(.*?)\//))[1]
const queryClient = useQueryClient()
const mutation = useTimelineMutation({
onSettled: () => {
displayMessage({
theme,
type: 'success',
message: t('common:message.success.message', {
function: t(`instance.block.action`, { instance })
})
})
queryClient.invalidateQueries(queryKey)
rootQueryKey && queryClient.invalidateQueries(rootQueryKey)
}
})
if (currentInstance !== instance && instance) {
switch (Platform.OS) {
case 'ios':
actions.push({
id: 'instance',
title: t('instance.title'),
actions: [
{
id: 'instance-block',
title: t('instance.block.action', { instance }),
destructive: true
}
]
})
break
default:
actions.push({
id: 'instance-block',
title: t('instance.block.action', { instance }),
destructive: true
})
break
}
}
return (id: string) => {
switch (id) {
case 'instance-block':
analytics('timeline_shared_headeractions_domain_block_press', {
page: queryKey[1].page
})
Alert.alert(
t('instance.block.alert.title', { instance }),
t('instance.block.alert.message'),
[
{
text: t('instance.block.alert.buttons.confirm'),
style: 'destructive',
onPress: () => {
analytics(
'timeline_shared_headeractions_domain_block_confirm',
{ page: queryKey && queryKey[1].page }
)
mutation.mutate({
type: 'domainBlock',
queryKey,
domain: instance
})
}
},
{
text: t('common:buttons.cancel')
}
]
)
}
}
}
export default contextMenuInstance

View File

@ -0,0 +1,38 @@
import analytics from '@components/analytics'
import { useTranslation } from 'react-i18next'
import { Platform, Share } from 'react-native'
import { ContextMenuAction } from 'react-native-context-menu-view'
export interface Props {
actions: ContextMenuAction[]
type: 'status' | 'account'
url: string
}
const contextMenuShare = ({ actions, type, url }: Props) => {
const { t } = useTranslation('componentContextMenu')
actions.push({
id: 'share',
title: t(`share.${type}.action`),
systemIcon: 'square.and.arrow.up'
})
return (id: string) => {
switch (id) {
case 'share':
analytics('timeline_shared_headeractions_share_press')
switch (Platform.OS) {
case 'ios':
Share.share({ url })
break
case 'android':
Share.share({ message: url })
break
}
break
}
}
}
export default contextMenuShare

View File

@ -0,0 +1,286 @@
import apiInstance from '@api/instance'
import analytics from '@components/analytics'
import { displayMessage } from '@components/Message'
import { useNavigation } from '@react-navigation/native'
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
import { RootStackParamList } from '@utils/navigation/navigators'
import {
MutationVarsTimelineUpdateStatusProperty,
QueryKeyTimeline,
useTimelineMutation
} from '@utils/queryHooks/timeline'
import {
checkInstanceFeature,
getInstanceAccount
} from '@utils/slices/instancesSlice'
import { useTheme } from '@utils/styles/ThemeManager'
import { useTranslation } from 'react-i18next'
import { Alert, Platform } from 'react-native'
import { ContextMenuAction } from 'react-native-context-menu-view'
import { useQueryClient } from 'react-query'
import { useSelector } from 'react-redux'
export interface Props {
actions: ContextMenuAction[]
status: Mastodon.Status
queryKey: QueryKeyTimeline
rootQueryKey?: QueryKeyTimeline
}
const contextMenuStatus = ({
actions,
status,
queryKey,
rootQueryKey
}: Props) => {
const navigation =
useNavigation<
NativeStackNavigationProp<RootStackParamList, 'Screen-Tabs'>
>()
const { theme } = useTheme()
const { t } = useTranslation('componentContextMenu')
const queryClient = useQueryClient()
const mutation = useTimelineMutation({
onMutate: true,
onError: (err: any, params, oldData) => {
const theFunction = (params as MutationVarsTimelineUpdateStatusProperty)
.payload
? (params as MutationVarsTimelineUpdateStatusProperty).payload.property
: 'delete'
displayMessage({
theme,
type: 'error',
message: t('common:message.error.message', {
function: t(`status.${theFunction}.action`)
}),
...(err.status &&
typeof err.status === 'number' &&
err.data &&
err.data.error &&
typeof err.data.error === 'string' && {
description: err.data.error
})
})
queryClient.setQueryData(queryKey, oldData)
}
})
const instanceAccount = useSelector(
getInstanceAccount,
(prev, next) => prev.id === next.id
)
const ownAccount = instanceAccount?.id === status.account.id
if (ownAccount) {
const accountMenuItems: ContextMenuAction[] = [
{
id: 'status-delete',
title: t('status.delete.action'),
systemIcon: 'trash',
destructive: true
},
{
id: 'status-delete-edit',
title: t('status.deleteEdit.action'),
systemIcon: 'pencil.and.outline',
destructive: true
},
{
id: 'status-mute',
title: t('status.mute.action-muted', {
context: status.muted.toString()
}),
systemIcon: status.muted ? 'speaker' : 'speaker.slash'
}
]
const canEditPost = useSelector(checkInstanceFeature('edit_post'))
if (canEditPost) {
accountMenuItems.unshift({
id: 'status-edit',
title: t('status.edit.action'),
systemIcon: 'square.and.pencil'
})
}
if (status.visibility === 'public' || status.visibility === 'unlisted') {
accountMenuItems.push({
id: 'status-pin',
title: t('status.pin.action-pinned', {
context: status.pinned.toString()
}),
systemIcon: status.pinned ? 'pin.slash' : 'pin'
})
}
switch (Platform.OS) {
case 'ios':
actions.push({
id: 'status',
title: t('status.title'),
inlineChildren: true,
actions: accountMenuItems
})
break
default:
actions.push(...accountMenuItems)
break
}
}
return async (id: string) => {
switch (id) {
case 'status-delete':
analytics('timeline_shared_headeractions_status_delete_press', {
page: queryKey && queryKey[1].page
})
Alert.alert(
t('status.delete.alert.title'),
t('status.delete.alert.message'),
[
{
text: t('status.delete.alert.buttons.confirm'),
style: 'destructive',
onPress: async () => {
analytics(
'timeline_shared_headeractions_status_delete_confirm',
{
page: queryKey && queryKey[1].page
}
)
mutation.mutate({
type: 'deleteItem',
source: 'statuses',
queryKey,
rootQueryKey,
id: status.id
})
}
},
{
text: t('common:buttons.cancel')
}
]
)
break
case 'status-delete-edit':
analytics('timeline_shared_headeractions_status_deleteedit_press', {
page: queryKey && queryKey[1].page
})
Alert.alert(
t('status.deleteEdit.alert.title'),
t('status.deleteEdit.alert.message'),
[
{
text: t('status.deleteEdit.alert.buttons.confirm'),
style: 'destructive',
onPress: async () => {
analytics(
'timeline_shared_headeractions_status_deleteedit_confirm',
{
page: queryKey && queryKey[1].page
}
)
let replyToStatus: Mastodon.Status | undefined = undefined
if (status.in_reply_to_id) {
replyToStatus = await apiInstance<Mastodon.Status>({
method: 'get',
url: `statuses/${status.in_reply_to_id}`
}).then(res => res.body)
}
mutation
.mutateAsync({
type: 'deleteItem',
source: 'statuses',
queryKey,
id: status.id
})
.then(res => {
navigation.navigate('Screen-Compose', {
type: 'deleteEdit',
incomingStatus: res.body as Mastodon.Status,
...(replyToStatus && { replyToStatus }),
queryKey
})
})
}
},
{
text: t('common:buttons.cancel')
}
]
)
break
case 'status-mute':
analytics('timeline_shared_headeractions_status_mute_press', {
page: queryKey && queryKey[1].page
})
mutation.mutate({
type: 'updateStatusProperty',
queryKey,
rootQueryKey,
id: status.id,
payload: {
property: 'muted',
currentValue: status.muted,
propertyCount: undefined,
countValue: undefined
}
})
break
case 'status-edit':
analytics('timeline_shared_headeractions_status_edit_press', {
page: queryKey && queryKey[1].page
})
let replyToStatus: Mastodon.Status | undefined = undefined
if (status.in_reply_to_id) {
replyToStatus = await apiInstance<Mastodon.Status>({
method: 'get',
url: `statuses/${status.in_reply_to_id}`
}).then(res => res.body)
}
apiInstance<{
id: Mastodon.Status['id']
text: NonNullable<Mastodon.Status['text']>
spoiler_text: Mastodon.Status['spoiler_text']
}>({
method: 'get',
url: `statuses/${status.id}/source`
}).then(res => {
navigation.navigate('Screen-Compose', {
type: 'edit',
incomingStatus: {
...status,
text: res.body.text,
spoiler_text: res.body.spoiler_text
},
...(replyToStatus && { replyToStatus }),
queryKey,
rootQueryKey
})
})
break
case 'status-pin':
// Also note that reblogs cannot be pinned.
analytics('timeline_shared_headeractions_status_pin_press', {
page: queryKey && queryKey[1].page
})
mutation.mutate({
type: 'updateStatusProperty',
queryKey,
rootQueryKey,
id: status.id,
payload: {
property: 'pinned',
currentValue: status.pinned,
propertyCount: undefined,
countValue: undefined
}
})
break
}
}
}
export default contextMenuStatus

View File

@ -18,6 +18,7 @@ import { uniqBy } from 'lodash'
import React, { useCallback } from 'react'
import { Pressable, View } from 'react-native'
import { useSelector } from 'react-redux'
import TimelineContextMenu from './Shared/ContextMenu'
import TimelineFeedback from './Shared/Feedback'
import TimelineFiltered, { shouldFilter } from './Shared/Filtered'
import TimelineFullConversation from './Shared/FullConversation'
@ -73,96 +74,108 @@ const TimelineDefault: React.FC<Props> = ({
}, [])
return (
<Pressable
accessible={highlighted ? false : true}
style={{
padding: StyleConstants.Spacing.Global.PagePadding,
backgroundColor: colors.backgroundDefault,
paddingBottom:
disableDetails && disableOnPress
? StyleConstants.Spacing.Global.PagePadding
: 0
}}
onPress={onPress}
<TimelineContextMenu
status={actualStatus}
queryKey={queryKey}
rootQueryKey={rootQueryKey}
>
{item.reblog ? (
<TimelineActioned action='reblog' account={item.account} />
) : item._pinned ? (
<TimelineActioned action='pinned' account={item.account} />
) : null}
<View style={{ flex: 1, width: '100%', flexDirection: 'row' }}>
<TimelineAvatar
queryKey={disableOnPress ? undefined : queryKey}
account={actualStatus.account}
highlighted={highlighted}
/>
<TimelineHeaderDefault
queryKey={disableOnPress ? undefined : queryKey}
rootQueryKey={disableOnPress ? undefined : rootQueryKey}
status={actualStatus}
highlighted={highlighted}
/>
</View>
<View
<Pressable
accessible={highlighted ? false : true}
style={{
paddingTop: highlighted ? StyleConstants.Spacing.S : 0,
paddingLeft: highlighted
? 0
: StyleConstants.Avatar.M + StyleConstants.Spacing.S
padding: StyleConstants.Spacing.Global.PagePadding,
backgroundColor: colors.backgroundDefault,
paddingBottom:
disableDetails && disableOnPress
? StyleConstants.Spacing.Global.PagePadding
: 0
}}
onPress={onPress}
onLongPress={() => {}}
>
{typeof actualStatus.content === 'string' &&
actualStatus.content.length > 0 ? (
<TimelineContent
{item.reblog ? (
<TimelineActioned action='reblog' account={item.account} />
) : item._pinned ? (
<TimelineActioned action='pinned' account={item.account} />
) : null}
<View style={{ flex: 1, width: '100%', flexDirection: 'row' }}>
<TimelineAvatar
queryKey={disableOnPress ? undefined : queryKey}
account={actualStatus.account}
highlighted={highlighted}
/>
<TimelineHeaderDefault
queryKey={disableOnPress ? undefined : queryKey}
status={actualStatus}
highlighted={highlighted}
disableDetails={disableDetails}
/>
) : null}
{queryKey && actualStatus.poll ? (
<TimelinePoll
</View>
<View
style={{
paddingTop: highlighted ? StyleConstants.Spacing.S : 0,
paddingLeft: highlighted
? 0
: StyleConstants.Avatar.M + StyleConstants.Spacing.S
}}
>
{typeof actualStatus.content === 'string' &&
actualStatus.content.length > 0 ? (
<TimelineContent
status={actualStatus}
highlighted={highlighted}
disableDetails={disableDetails}
/>
) : null}
{queryKey && actualStatus.poll ? (
<TimelinePoll
queryKey={queryKey}
rootQueryKey={rootQueryKey}
statusId={actualStatus.id}
poll={actualStatus.poll}
reblog={item.reblog ? true : false}
sameAccount={ownAccount}
/>
) : null}
{!disableDetails &&
Array.isArray(actualStatus.media_attachments) &&
actualStatus.media_attachments.length ? (
<TimelineAttachment status={actualStatus} />
) : null}
{!disableDetails && actualStatus.card ? (
<TimelineCard card={actualStatus.card} />
) : null}
{!disableDetails ? (
<TimelineFullConversation
queryKey={queryKey}
status={actualStatus}
/>
) : null}
<TimelineTranslate status={actualStatus} highlighted={highlighted} />
<TimelineFeedback status={actualStatus} highlighted={highlighted} />
</View>
{queryKey && !disableDetails ? (
<TimelineActions
queryKey={queryKey}
rootQueryKey={rootQueryKey}
statusId={actualStatus.id}
poll={actualStatus.poll}
highlighted={highlighted}
status={actualStatus}
ownAccount={ownAccount}
accts={uniqBy(
(
[actualStatus.account] as Mastodon.Account[] &
Mastodon.Mention[]
)
.concat(actualStatus.mentions)
.filter(d => d?.id !== instanceAccount?.id),
d => d?.id
).map(d => d?.acct)}
reblog={item.reblog ? true : false}
sameAccount={ownAccount}
/>
) : null}
{!disableDetails &&
Array.isArray(actualStatus.media_attachments) &&
actualStatus.media_attachments.length ? (
<TimelineAttachment status={actualStatus} />
) : null}
{!disableDetails && actualStatus.card ? (
<TimelineCard card={actualStatus.card} />
) : null}
{!disableDetails ? (
<TimelineFullConversation queryKey={queryKey} status={actualStatus} />
) : null}
<TimelineTranslate status={actualStatus} highlighted={highlighted} />
<TimelineFeedback status={actualStatus} highlighted={highlighted} />
</View>
{queryKey && !disableDetails ? (
<TimelineActions
queryKey={queryKey}
rootQueryKey={rootQueryKey}
highlighted={highlighted}
status={actualStatus}
ownAccount={ownAccount}
accts={uniqBy(
([actualStatus.account] as Mastodon.Account[] & Mastodon.Mention[])
.concat(actualStatus.mentions)
.filter(d => d?.id !== instanceAccount?.id),
d => d?.id
).map(d => d?.acct)}
reblog={item.reblog ? true : false}
/>
) : null}
</Pressable>
</Pressable>
</TimelineContextMenu>
)
}

View File

@ -18,6 +18,7 @@ import { isEqual, uniqBy } from 'lodash'
import React, { useCallback } from 'react'
import { Pressable, View } from 'react-native'
import { useSelector } from 'react-redux'
import TimelineContextMenu from './Shared/ContextMenu'
import TimelineFiltered, { shouldFilter } from './Shared/Filtered'
import TimelineFullConversation from './Shared/FullConversation'
@ -58,103 +59,105 @@ const TimelineNotifications = React.memo(
}, [])
return (
<Pressable
style={{
padding: StyleConstants.Spacing.Global.PagePadding,
backgroundColor: colors.backgroundDefault,
paddingBottom: notification.status
? 0
: StyleConstants.Spacing.Global.PagePadding
}}
onPress={onPress}
>
{notification.type !== 'mention' ? (
<TimelineActioned
action={notification.type}
account={notification.account}
notification
/>
) : null}
<View
<TimelineContextMenu status={notification.status} queryKey={queryKey}>
<Pressable
style={{
opacity:
notification.type === 'follow' ||
notification.type === 'follow_request' ||
notification.type === 'mention' ||
notification.type === 'status'
? 1
: 0.5
padding: StyleConstants.Spacing.Global.PagePadding,
backgroundColor: colors.backgroundDefault,
paddingBottom: notification.status
? 0
: StyleConstants.Spacing.Global.PagePadding
}}
onPress={onPress}
onLongPress={() => {}}
>
<View style={{ flex: 1, width: '100%', flexDirection: 'row' }}>
<TimelineAvatar
queryKey={queryKey}
account={actualAccount}
highlighted={highlighted}
/>
<TimelineHeaderNotification
queryKey={queryKey}
notification={notification}
{notification.type !== 'mention' ? (
<TimelineActioned
action={notification.type}
account={notification.account}
notification
/>
) : null}
<View
style={{
opacity:
notification.type === 'follow' ||
notification.type === 'follow_request' ||
notification.type === 'mention' ||
notification.type === 'status'
? 1
: 0.5
}}
>
<View style={{ flex: 1, width: '100%', flexDirection: 'row' }}>
<TimelineAvatar
queryKey={queryKey}
account={actualAccount}
highlighted={highlighted}
/>
<TimelineHeaderNotification notification={notification} />
</View>
{notification.status ? (
<View
style={{
paddingTop: highlighted ? StyleConstants.Spacing.S : 0,
paddingLeft: highlighted
? 0
: StyleConstants.Avatar.M + StyleConstants.Spacing.S
}}
>
{notification.status.content.length > 0 ? (
<TimelineContent
status={notification.status}
highlighted={highlighted}
/>
) : null}
{notification.status.poll ? (
<TimelinePoll
queryKey={queryKey}
statusId={notification.status.id}
poll={notification.status.poll}
reblog={false}
sameAccount={
notification.account.id === instanceAccount?.id
}
/>
) : null}
{notification.status.media_attachments.length > 0 ? (
<TimelineAttachment status={notification.status} />
) : null}
{notification.status.card ? (
<TimelineCard card={notification.status.card} />
) : null}
<TimelineFullConversation
queryKey={queryKey}
status={notification.status}
/>
</View>
) : null}
</View>
{notification.status ? (
<View
style={{
paddingTop: highlighted ? StyleConstants.Spacing.S : 0,
paddingLeft: highlighted
? 0
: StyleConstants.Avatar.M + StyleConstants.Spacing.S
}}
>
{notification.status.content.length > 0 ? (
<TimelineContent
status={notification.status}
highlighted={highlighted}
/>
) : null}
{notification.status.poll ? (
<TimelinePoll
queryKey={queryKey}
statusId={notification.status.id}
poll={notification.status.poll}
reblog={false}
sameAccount={notification.account.id === instanceAccount?.id}
/>
) : null}
{notification.status.media_attachments.length > 0 ? (
<TimelineAttachment status={notification.status} />
) : null}
{notification.status.card ? (
<TimelineCard card={notification.status.card} />
) : null}
<TimelineFullConversation
queryKey={queryKey}
status={notification.status}
/>
</View>
<TimelineActions
queryKey={queryKey}
status={notification.status}
highlighted={highlighted}
accts={uniqBy(
(
[notification.status.account] as Mastodon.Account[] &
Mastodon.Mention[]
)
.concat(notification.status.mentions)
.filter(d => d?.id !== instanceAccount?.id),
d => d?.id
).map(d => d?.acct)}
reblog={false}
/>
) : null}
</View>
{notification.status ? (
<TimelineActions
queryKey={queryKey}
status={notification.status}
highlighted={highlighted}
accts={uniqBy(
(
[notification.status.account] as Mastodon.Account[] &
Mastodon.Mention[]
)
.concat(notification.status.mentions)
.filter(d => d?.id !== instanceAccount?.id),
d => d?.id
).map(d => d?.acct)}
reblog={false}
/>
) : null}
</Pressable>
</Pressable>
</TimelineContextMenu>
)
},
(prev, next) => isEqual(prev.notification, next.notification)

View File

@ -0,0 +1,82 @@
import contextMenuAccount from '@components/ContextMenu/account'
import contextMenuInstance from '@components/ContextMenu/instance'
import contextMenuShare from '@components/ContextMenu/share'
import contextMenuStatus from '@components/ContextMenu/status'
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
import React from 'react'
import { createContext } from 'react'
import ContextMenu, {
ContextMenuAction,
ContextMenuProps
} from 'react-native-context-menu-view'
export interface Props {
status?: Mastodon.Status
queryKey?: QueryKeyTimeline
rootQueryKey?: QueryKeyTimeline
}
export const ContextMenuContext = createContext<ContextMenuAction[]>([])
const TimelineContextMenu: React.FC<Props & ContextMenuProps> = ({
children,
status,
queryKey,
rootQueryKey,
...props
}) => {
if (!status || !queryKey) {
return <>{children}</>
}
const actions: ContextMenuAction[] = []
const shareOnPress =
status.visibility !== 'direct'
? contextMenuShare({
actions,
type: 'status',
url: status.url || status.uri
})
: null
const statusOnPress = contextMenuStatus({
actions,
status,
queryKey,
rootQueryKey
})
const accountOnPress = contextMenuAccount({
actions,
queryKey,
rootQueryKey,
id: status.account.id
})
const instanceOnPress = contextMenuInstance({
actions,
status,
queryKey,
rootQueryKey
})
return (
<ContextMenuContext.Provider value={actions}>
<ContextMenu
actions={actions}
onPress={({ nativeEvent: { id } }) => {
for (const on of [
shareOnPress,
statusOnPress,
accountOnPress,
instanceOnPress
]) {
on && on(id)
}
}}
children={children}
{...props}
/>
</ContextMenuContext.Provider>
)
}
export default TimelineContextMenu

View File

@ -1,13 +1,12 @@
import Icon from '@components/Icon'
import { useNavigation } from '@react-navigation/native'
import { StackNavigationProp } from '@react-navigation/stack'
import { RootStackParamList } from '@utils/navigation/navigators'
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import React from 'react'
import React, { useContext } from 'react'
import { useTranslation } from 'react-i18next'
import { Pressable, View } from 'react-native'
import ContextMenu from 'react-native-context-menu-view'
import { ContextMenuContext } from './ContextMenu'
import HeaderSharedAccount from './HeaderShared/Account'
import HeaderSharedApplication from './HeaderShared/Application'
import HeaderSharedCreated from './HeaderShared/Created'
@ -16,24 +15,19 @@ import HeaderSharedVisibility from './HeaderShared/Visibility'
export interface Props {
queryKey?: QueryKeyTimeline
rootQueryKey?: QueryKeyTimeline
status: Mastodon.Status
highlighted: boolean
}
const TimelineHeaderDefault = ({
queryKey,
rootQueryKey,
status,
highlighted
}: Props) => {
const { t } = useTranslation('componentTimeline')
const navigation = useNavigation<StackNavigationProp<RootStackParamList>>()
const TimelineHeaderDefault = ({ queryKey, status, highlighted }: Props) => {
const { t } = useTranslation('componentContextMenu')
const { colors } = useTheme()
const contextMenuContext = useContext(ContextMenuContext)
return (
<View style={{ flex: 1, flexDirection: 'row' }}>
<View style={{ flex: 5 }}>
<View style={{ flex: 7 }}>
<HeaderSharedAccount account={status.account} />
<View
style={{
@ -56,29 +50,27 @@ const TimelineHeaderDefault = ({
{queryKey ? (
<Pressable
accessibilityHint={t('shared.header.actions.accessibilityHint')}
accessibilityHint={t('accessibilityHint')}
style={{
flex: 1,
flexDirection: 'row',
justifyContent: 'center',
paddingBottom: StyleConstants.Spacing.S
marginBottom: StyleConstants.Spacing.L
}}
onPress={() =>
navigation.navigate('Screen-Actions', {
queryKey,
rootQueryKey,
status,
type: 'status'
})
}
children={
<Icon
name='MoreHorizontal'
color={colors.secondary}
size={StyleConstants.Font.Size.L}
/>
}
/>
>
<ContextMenu
dropdownMenuMode
actions={contextMenuContext}
onPress={() => {}}
children={
<Icon
name='MoreHorizontal'
color={colors.secondary}
size={StyleConstants.Font.Size.L}
/>
}
/>
</Pressable>
) : null}
</View>
)

View File

@ -3,14 +3,12 @@ import {
RelationshipIncoming,
RelationshipOutgoing
} from '@components/Relationship'
import { useNavigation } from '@react-navigation/native'
import { StackNavigationProp } from '@react-navigation/stack'
import { RootStackParamList } from '@utils/navigation/navigators'
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import React, { useMemo } from 'react'
import React, { useContext, useMemo } from 'react'
import { Pressable, View } from 'react-native'
import ContextMenu from 'react-native-context-menu-view'
import { ContextMenuContext } from './ContextMenu'
import HeaderSharedAccount from './HeaderShared/Account'
import HeaderSharedApplication from './HeaderShared/Application'
import HeaderSharedCreated from './HeaderShared/Created'
@ -18,14 +16,14 @@ import HeaderSharedMuted from './HeaderShared/Muted'
import HeaderSharedVisibility from './HeaderShared/Visibility'
export interface Props {
queryKey: QueryKeyTimeline
notification: Mastodon.Notification
}
const TimelineHeaderNotification = ({ queryKey, notification }: Props) => {
const navigation = useNavigation<StackNavigationProp<RootStackParamList>>()
const TimelineHeaderNotification = ({ notification }: Props) => {
const { colors } = useTheme()
const contextMenuContext = useContext(ContextMenuContext)
const actions = useMemo(() => {
switch (notification.type) {
case 'follow':
@ -42,18 +40,18 @@ const TimelineHeaderNotification = ({ queryKey, notification }: Props) => {
justifyContent: 'center',
paddingBottom: StyleConstants.Spacing.S
}}
onPress={() =>
navigation.navigate('Screen-Actions', {
queryKey,
status: notification.status!,
type: 'status'
})
}
children={
<Icon
name='MoreHorizontal'
color={colors.secondary}
size={StyleConstants.Font.Size.L}
<ContextMenu
dropdownMenuMode
actions={contextMenuContext}
onPress={() => {}}
children={
<Icon
name='MoreHorizontal'
color={colors.secondary}
size={StyleConstants.Font.Size.L}
/>
}
/>
}
/>

View File

@ -1,17 +1,20 @@
import analytics from '@components/analytics'
import { ActionSheetOptions } from '@expo/react-native-action-sheet'
import * as ImageManipulator from 'expo-image-manipulator'
import * as ImagePicker from 'expo-image-picker'
import {
ImageInfo,
UIImagePickerPresentationStyle
} from 'expo-image-picker/build/ImagePicker.types'
import { store } from '@root/store'
import { getInstanceConfigurationStatusMaxAttachments } from '@utils/slices/instancesSlice'
import * as ExpoImagePicker from 'expo-image-picker'
import i18next from 'i18next'
import { Alert, Linking, Platform } from 'react-native'
import { Alert, Linking } from 'react-native'
import ImagePicker, {
Image,
ImageOrVideo
} from 'react-native-image-crop-picker'
export interface Props {
mediaTypes?: ImagePicker.MediaTypeOptions
resize?: { width?: number; height?: number } // Resize mode contain
mediaType?: 'photo' | 'video'
resize?: { width?: number; height?: number }
maximum?: number
indicateMaximum?: boolean
showActionSheetWithOptions: (
options: ActionSheetOptions,
callback: (i?: number | undefined) => void | Promise<void>
@ -19,134 +22,164 @@ export interface Props {
}
const mediaSelector = async ({
mediaTypes = ImagePicker.MediaTypeOptions.All,
mediaType,
resize,
maximum,
indicateMaximum = false,
showActionSheetWithOptions
}: Props): Promise<ImageInfo> => {
return new Promise((resolve, reject) => {
const resolveResult = async (result: ImageInfo) => {
if (resize && result.type === 'image') {
let newResult: ImageManipulator.ImageResult
if (resize.width && resize.height) {
if (resize.width / resize.height > result.width / result.height) {
newResult = await ImageManipulator.manipulateAsync(result.uri, [
{ resize: { width: resize.width } }
])
} else {
newResult = await ImageManipulator.manipulateAsync(result.uri, [
{ resize: { height: resize.height } }
])
}: Props): Promise<ImageOrVideo[]> => {
const checkLibraryPermission = async (): Promise<boolean> => {
const { status } =
await ExpoImagePicker.requestMediaLibraryPermissionsAsync()
if (status !== 'granted') {
Alert.alert(
i18next.t('componentMediaSelector:library.alert.title'),
i18next.t('componentMediaSelector:library.alert.message'),
[
{
text: i18next.t('common:buttons.cancel'),
style: 'cancel',
onPress: () =>
analytics('mediaSelector_nopermission', {
action: 'cancel'
})
},
{
text: i18next.t(
'componentMediaSelector:library.alert.buttons.settings'
),
style: 'default',
onPress: () => {
analytics('mediaSelector_nopermission', {
action: 'settings'
})
Linking.openURL('app-settings:')
}
}
} else {
newResult = await ImageManipulator.manipulateAsync(result.uri, [
{ resize }
])
}
resolve({ ...newResult, cancelled: false })
]
)
return false
} else {
return true
}
}
const _maximum =
maximum ||
getInstanceConfigurationStatusMaxAttachments(store.getState()) ||
4
const options = () => {
switch (mediaType) {
case 'photo':
return [
i18next.t(
'componentMediaSelector:options.image',
indicateMaximum ? { context: 'max', max: _maximum } : undefined
),
i18next.t('common:buttons.cancel')
]
case 'video':
return [
i18next.t(
'componentMediaSelector:options.video',
indicateMaximum ? { context: 'max', max: 1 } : undefined
),
i18next.t('common:buttons.cancel')
]
default:
return [
i18next.t(
'componentMediaSelector:options.image',
indicateMaximum ? { context: 'max', max: _maximum } : undefined
),
i18next.t(
'componentMediaSelector:options.video',
indicateMaximum ? { context: 'max', max: 1 } : undefined
),
i18next.t('common:buttons.cancel')
]
}
}
return new Promise((resolve, reject) => {
const selectImage = async () => {
const images = await ImagePicker.openPicker({
mediaType: 'photo',
includeExif: false,
multiple: true,
minFiles: 1,
maxFiles: _maximum,
loadingLabelText: '',
compressImageMaxWidth: 4096,
compressImageMaxHeight: 4096
}).catch(() => {})
if (!images) {
return reject()
}
if (!resize) {
return resolve(images)
} else {
resolve(result)
const croppedImages: Image[] = []
for (const image of images) {
const croppedImage = await ImagePicker.openCropper({
mediaType: 'photo',
path: image.path,
width: resize.width,
height: resize.height,
cropperChooseText: i18next.t('common:buttons.apply'),
cropperCancelText: i18next.t('common:buttons.cancel'),
hideBottomControls: true
}).catch(() => {})
croppedImage && croppedImages.push(croppedImage)
}
return resolve(croppedImages)
}
}
const selectVideo = async () => {
const video = await ImagePicker.openPicker({
mediaType: 'video',
includeExif: false,
loadingLabelText: ''
}).catch(() => {})
if (video) {
return resolve([video])
} else {
return reject()
}
}
showActionSheetWithOptions(
{
title: i18next.t('componentMediaSelector:title'),
options: [
i18next.t('componentMediaSelector:options.library'),
i18next.t('componentMediaSelector:options.photo'),
i18next.t('componentMediaSelector:options.cancel')
],
cancelButtonIndex: 2
options: options(),
cancelButtonIndex: mediaType ? 1 : 2
},
async buttonIndex => {
if (buttonIndex === 0) {
const { status } =
await ImagePicker.requestMediaLibraryPermissionsAsync()
if (status !== 'granted') {
Alert.alert(
i18next.t('componentMediaSelector:library.alert.title'),
i18next.t('componentMediaSelector:library.alert.message'),
[
{
text: i18next.t(
'componentMediaSelector:library.alert.buttons.cancel'
),
style: 'cancel',
onPress: () =>
analytics('mediaSelector_nopermission', {
action: 'cancel'
})
},
{
text: i18next.t(
'componentMediaSelector:library.alert.buttons.settings'
),
style: 'default',
onPress: () => {
analytics('mediaSelector_nopermission', {
action: 'settings'
})
Linking.openURL('app-settings:')
}
}
]
)
} else {
const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes,
exif: false,
presentationStyle:
Platform.OS === 'ios' && parseInt(Platform.Version) < 13
? UIImagePickerPresentationStyle.FULL_SCREEN
: UIImagePickerPresentationStyle.AUTOMATIC
})
if (!(await checkLibraryPermission())) {
return reject()
}
if (!result.cancelled) {
await resolveResult(result)
switch (mediaType) {
case 'photo':
if (buttonIndex === 0) {
await selectImage()
}
}
} else if (buttonIndex === 1) {
const { status } = await ImagePicker.requestCameraPermissionsAsync()
if (status !== 'granted') {
Alert.alert(
i18next.t('componentMediaSelector:photo.alert.title'),
i18next.t('componentMediaSelector:photo.alert.message'),
[
{
text: i18next.t(
'componentMediaSelector:photo.alert.buttons.cancel'
),
style: 'cancel',
onPress: () => {
analytics('compose_addattachment_camera_nopermission', {
action: 'cancel'
})
}
},
{
text: i18next.t(
'componentMediaSelector:photo.alert.buttons.settings'
),
style: 'default',
onPress: () => {
analytics('compose_addattachment_camera_nopermission', {
action: 'settings'
})
Linking.openURL('app-settings:')
}
}
]
)
} else {
const result = await ImagePicker.launchCameraAsync({
mediaTypes,
exif: false
})
if (!result.cancelled) {
await resolveResult(result)
break
case 'video':
if (buttonIndex === 0) {
await selectVideo()
}
}
break
default:
if (buttonIndex === 0) {
await selectImage()
} else if (buttonIndex === 1) {
await selectVideo()
}
break
}
}
)

View File

@ -0,0 +1,70 @@
{
"accessibilityHint": "Funktionen für diesen Tröt - wie z. B. Autor und Originaltröt",
"account": {
"title": "",
"mute": {
"action": "Profil stummschalten"
},
"block": {
"action": "Nutzer blockieren"
},
"reports": {
"action": "User melden"
}
},
"instance": {
"title": "",
"block": {
"action": "Instanz {{instance}} blockieren",
"alert": {
"title": "",
"message": "Üblicherweise kannst du einen User stummschalten oder blockieren.\nBlockierst du hingegegen eine Instanz, wird deren gesamter Inhalt samt Usern, die dir von dieser Instanz folgen, entfernt!",
"buttons": {
"confirm": "Bestätigen"
}
}
}
},
"share": {
"status": {
"action": "Tröt teilen"
},
"account": {
"action": "User verlinken"
}
},
"status": {
"title": "",
"edit": {
"action": "Tröt bearbeiten"
},
"delete": {
"action": "Tröt löschen",
"alert": {
"title": "Löschen bestätigen?",
"message": "",
"buttons": {
"confirm": "Bestätigen"
}
}
},
"deleteEdit": {
"action": "",
"alert": {
"title": "",
"message": "",
"buttons": {
"confirm": "Bestätigen"
}
}
},
"mute": {
"action-muted_false": "",
"action-muted_true": ""
},
"pin": {
"action-pinned_false": "",
"action-pinned_true": ""
}
}
}

View File

@ -1,27 +1,17 @@
{
"title": "Datenquelle auswählen",
"options": {
"library": "Hochladen",
"photo": "Bild aufnehmen",
"cancel": "$t(common:buttons.cancel)"
"image": "",
"image_max": "",
"video": "",
"video_max": ""
},
"library": {
"alert": {
"title": "Kein Zugriff",
"message": "Für den Upload ist eine Zugriffsgenehmigung erforderlich",
"buttons": {
"settings": "Einstellungen bestätigen",
"cancel": "$t(common:buttons.cancel)"
}
}
},
"photo": {
"alert": {
"title": "Zugriff verweigert",
"message": "Zugriff auf die Kamera erforderlich",
"buttons": {
"settings": "Einstellungen übernehmen",
"cancel": "$t(common:buttons.cancel)"
"settings": "Einstellungen bestätigen"
}
}
}

View File

@ -123,94 +123,6 @@
"delete": {
"function": "Nachricht löschen"
}
},
"actions": {
"accessibilityHint": "Funktionen für diesen Tröt - wie z. B. Autor und Originaltröt",
"account": {
"heading": "Über die Nutzerin/den Nutzer",
"mute": {
"function": "Profil stummschalten",
"button": "@{{acct}} stummschalten"
},
"block": {
"function": "Nutzer blockieren",
"button": "@{{acct}} blockieren"
},
"reports": {
"function": "User melden",
"button": "@{{acct}} melden"
}
},
"domain": {
"heading": "Über diese Instanz",
"block": {
"function": "Instanz blockieren",
"button": "Instanz {{domain}} blockieren"
},
"alert": {
"title": "{{domain}} wirklich blockieren?",
"message": "Üblicherweise kannst du einen User stummschalten oder blockieren.\nBlockierst du hingegegen eine Instanz, wird deren gesamter Inhalt samt Usern, die dir von dieser Instanz folgen, entfernt!",
"buttons": {
"confirm": "Blockierung bestätigen",
"cancel": "$t(common:buttons.cancel)"
}
}
},
"share": {
"status": {
"heading": "Tröt teilen",
"button": "Link zu diesem Tröt teilen"
},
"account": {
"heading": "User verlinken",
"button": "Link zu diesem Benutzer teilen"
}
},
"status": {
"heading": "Über Toot",
"edit": {
"function": "Tröt bearbeiten",
"button": "Diesen Tröt bearbeiten"
},
"delete": {
"function": "Tröt löschen",
"button": "Diesen Tröt löschen",
"alert": {
"title": "Tröt sicher löschen?",
"message": "Bist du wirklich sicher, diesen Tröt löschen zu wollen? Sämtliche Boosts und Sterne werden samt der Antworten entfernt.",
"buttons": {
"confirm": "Löschen bestätigen",
"cancel": "$t(common:buttons.cancel)"
}
}
},
"deleteEdit": {
"function": "Tröt löschen",
"button": "Diesen Tröt neu entwerfen",
"alert": {
"title": "Tröt sicher löschen?",
"message": "Bist du wirklich sicher, diesen Tröt neu zu entwerfen? Alle Boosts und Sterne werden entfernt - samt der Antworten.",
"buttons": {
"confirm": "Löschen bestätigen",
"cancel": "$t(common:buttons.cancel)"
}
}
},
"mute": {
"function": "Tröt stummschalten",
"button": {
"positive": "Diesen Tröt sowie die Antworten stummschalten",
"negative": "Diesen Tröt sowie die Antworten nicht mehr stummschalten"
}
},
"pin": {
"function": "Anheften",
"button": {
"positive": "Diesen Tröt anheften",
"negative": "Diesen Tröt nicht mehr anheften"
}
}
}
}
},
"poll": {

View File

@ -1,8 +1,7 @@
{
"content": {
"button": {
"apply": "$t(common:buttons.apply)",
"cancel": "$t(common:buttons.cancel)"
"altText": {
"heading": ""
},
"notificationsFilter": {
"heading": "Benachrichtigungsart anzeigen",

View File

@ -8,6 +8,7 @@ export default {
screenImageViewer: require('./screens/imageViewer'),
screenTabs: require('./screens/tabs'),
componentContextMenu: require('./components/contextMenu'),
componentEmojis: require('./components/emojis'),
componentInstance: require('./components/instance'),
componentMediaSelector: require('./components/mediaSelector'),

View File

@ -0,0 +1,70 @@
{
"accessibilityHint": "Actions for this toot, such as its posted user, toot itself",
"account": {
"title": "User actions",
"mute": {
"action": "Mute user"
},
"block": {
"action": "Block user"
},
"reports": {
"action": "Report user"
}
},
"instance": {
"title": "Instance action",
"block": {
"action": "Block instance {{instance}}",
"alert": {
"title": "Confirm blocking instance {{instance}} ?",
"message": "Mostly you can mute or block certain user.\n\nAfter blocking instance, all its content including followers from this instance will be removed!",
"buttons": {
"confirm": "Confirm"
}
}
}
},
"share": {
"status": {
"action": "Share toot"
},
"account": {
"action": "Share user"
}
},
"status": {
"title": "Toot actions",
"edit": {
"action": "Edit toot"
},
"delete": {
"action": "Delete toot",
"alert": {
"title": "Confirm deleting?",
"message": "All boosts and favourites will be cleared, including all replies.",
"buttons": {
"confirm": "Confirm"
}
}
},
"deleteEdit": {
"action": "Delete toot and repost",
"alert": {
"title": "Confirm deleting and repost?",
"message": "All boosts and favourites will be cleared, including all replies.",
"buttons": {
"confirm": "Confirm"
}
}
},
"mute": {
"action-muted_false": "Mute toot and replies",
"action-muted_true": "Unmute toot and replies"
},
"pin": {
"action-pinned_false": "Pin toot",
"action-pinned_true": "Unpin toot"
}
}
}

View File

@ -1,27 +1,17 @@
{
"title": "Select media source",
"options": {
"library": "Upload from library",
"photo": "Take a photo",
"cancel": "$t(common:buttons.cancel)"
"image": "Upload photos",
"image_max": "Upload photos (max {{max}})",
"video": "Upload video",
"video_max": "Upload video (max {{max}})"
},
"library": {
"alert": {
"title": "No permission",
"message": "Require photo library read permission to upload",
"buttons": {
"settings": "Update setting",
"cancel": "$t(common:buttons.cancel)"
}
}
},
"photo": {
"alert": {
"title": "No permission",
"message": "Require camera usage permission to upload",
"buttons": {
"settings": "Update setting",
"cancel": "$t(common:buttons.cancel)"
"settings": "Update setting"
}
}
}

View File

@ -123,94 +123,6 @@
"delete": {
"function": "Delete direct message"
}
},
"actions": {
"accessibilityHint": "Actions for this toot, such as its posted user, toot itself",
"account": {
"heading": "About user",
"mute": {
"function": "Mute user",
"button": "Mute @{{acct}}"
},
"block": {
"function": "Block user",
"button": "Block @{{acct}}"
},
"reports": {
"function": "Report user",
"button": "Report @{{acct}}"
}
},
"domain": {
"heading": "About instance",
"block": {
"function": "Block instance",
"button": "Block instance {{domain}}"
},
"alert": {
"title": "Confirm blocking {{domain}} ?",
"message": "Mostly you can mute or block certain user.\n\nAfter blocking instance, all its content including followers from this instance will be removed!",
"buttons": {
"confirm": "Confirm blocking",
"cancel": "$t(common:buttons.cancel)"
}
}
},
"share": {
"status": {
"heading": "Share toot",
"button": "Share link to this toot"
},
"account": {
"heading": "Share user",
"button": "Share link to this user"
}
},
"status": {
"heading": "About toot",
"edit": {
"function": "Edit toot",
"button": "Edit this toot"
},
"delete": {
"function": "Delete toot",
"button": "Delete this toot",
"alert": {
"title": "Confirm deleting toot?",
"message": "Are you sure to delete this toot? All boosts and favourites will be cleared, including all replies.",
"buttons": {
"confirm": "Confirm deleting",
"cancel": "$t(common:buttons.cancel)"
}
}
},
"deleteEdit": {
"function": "Delete toot",
"button": "Delete and re-draft",
"alert": {
"title": "Confirm deleting toot?",
"message": "Are you sure to delete and re-draft this toot? All boosts and favourites will be cleared, including all replies.",
"buttons": {
"confirm": "Confirm deleting",
"cancel": "$t(common:buttons.cancel)"
}
}
},
"mute": {
"function": "Mute toot",
"button": {
"positive": "Mute this toot and replies",
"negative": "Unmute this toot and replies"
}
},
"pin": {
"function": "Pin",
"button": {
"positive": "Pin this toot",
"negative": "Unpin this toot"
}
}
}
}
},
"poll": {

View File

@ -60,8 +60,8 @@ i18n.use(initReactI18next).init({
returnEmptyString: false,
saveMissing: true,
missingKeyHandler: (ns, key) => {
console.log('i18n missing: ' + ns + ' : ' + key)
missingKeyHandler: (_, ns, key) => {
console.log('i18n missing', ns, key)
},
interpolation: {

View File

@ -0,0 +1,70 @@
{
"accessibilityHint": "Azioni per questo toot, per l'utente che l'ha pubblicato o per il toot stesso",
"account": {
"title": "",
"mute": {
"action": "Muta utente"
},
"block": {
"action": "Blocca utente"
},
"reports": {
"action": "Segnala utente"
}
},
"instance": {
"title": "",
"block": {
"action": "Blocca istanza {{instance}}",
"alert": {
"title": "",
"message": "Sarebbe meglio mutare o bloccare singoli utenti.\n\nSe blocchi un'istanza, tutti i suoi contenuti a te relativi, inclusi tutti i tuoi seguaci da questa, saranno rimossi.",
"buttons": {
"confirm": "Ho capito"
}
}
}
},
"share": {
"status": {
"action": "Condividi toot"
},
"account": {
"action": "Condividi utente"
}
},
"status": {
"title": "",
"edit": {
"action": "Modifica toot"
},
"delete": {
"action": "Cancella toot",
"alert": {
"title": "Conferma?",
"message": "",
"buttons": {
"confirm": "Ho capito"
}
}
},
"deleteEdit": {
"action": "",
"alert": {
"title": "",
"message": "",
"buttons": {
"confirm": "Ho capito"
}
}
},
"mute": {
"action-muted_false": "",
"action-muted_true": ""
},
"pin": {
"action-pinned_false": "",
"action-pinned_true": ""
}
}
}

View File

@ -1,27 +1,17 @@
{
"title": "Seleziona origine media",
"options": {
"library": "Carica da libreria locale",
"photo": "Scatta una foto",
"cancel": "$t(common:buttons.cancel)"
"image": "",
"image_max": "",
"video": "",
"video_max": ""
},
"library": {
"alert": {
"title": "Permesso non concesso",
"message": "È richiesto l'accesso ai file del dispositivo per il caricamento dalla libreria",
"buttons": {
"settings": "Correggi impostazioni",
"cancel": "$t(common:buttons.cancel)"
}
}
},
"photo": {
"alert": {
"title": "Permesso non concesso",
"message": "È richiesto l'accesso alla fotocamera per scattare foto",
"buttons": {
"settings": "Correggi impostazioni",
"cancel": "$t(common:buttons.cancel)"
"settings": "Correggi impostazioni"
}
}
}

View File

@ -123,94 +123,6 @@
"delete": {
"function": "Cancella messaggio"
}
},
"actions": {
"accessibilityHint": "Azioni per questo toot, per l'utente che l'ha pubblicato o per il toot stesso",
"account": {
"heading": "Riguardo quest'utente",
"mute": {
"function": "Muta utente",
"button": "Muta @{{acct}}"
},
"block": {
"function": "Blocca utente",
"button": "Blocca @{{acct}}"
},
"reports": {
"function": "Segnala utente",
"button": "Segnala @{{acct}}"
}
},
"domain": {
"heading": "Riguardo questa istanza",
"block": {
"function": "Blocca istanza",
"button": "Blocca istanza {{domain}}"
},
"alert": {
"title": "Conferma blocco di {{domain}} ?",
"message": "Sarebbe meglio mutare o bloccare singoli utenti.\n\nSe blocchi un'istanza, tutti i suoi contenuti a te relativi, inclusi tutti i tuoi seguaci da questa, saranno rimossi.",
"buttons": {
"confirm": "Conferma blocco",
"cancel": "$t(common:buttons.cancel)"
}
}
},
"share": {
"status": {
"heading": "Condividi toot",
"button": "Condividi il link a questo toot"
},
"account": {
"heading": "Condividi utente",
"button": "Share il link a questo utente"
}
},
"status": {
"heading": "Riguardo questo toot",
"edit": {
"function": "Modifica toot",
"button": "Modifica questo toot"
},
"delete": {
"function": "Cancella toot",
"button": "Cancella toot",
"alert": {
"title": "Cancellare il toot?",
"message": "Vuoi davvero cancellare questo toot? Tutti gli apprezzamenti, le ricondivisioni, e le risposte verranno persi.",
"buttons": {
"confirm": "Conferma",
"cancel": "$t(common:buttons.cancel)"
}
}
},
"deleteEdit": {
"function": "Cancella toot",
"button": "Cancella e riscrivi toot",
"alert": {
"title": "Cancellare e riscrivere il toot?",
"message": "Vuoi davvero cancellare e riscrivere questo toot? Tutti gli apprezzamenti, le ricondivisioni, e le risposte verranno persi.",
"buttons": {
"confirm": "Conferma",
"cancel": "$t(common:buttons.cancel)"
}
}
},
"mute": {
"function": "Muta toot",
"button": {
"positive": "Muta questo toot e le sue risposte",
"negative": "Smuta questo toot e le sue risposte"
}
},
"pin": {
"function": "Fissa in cima",
"button": {
"positive": "Fissa questo toot in cima al profilo",
"negative": "Togli questo toot dalla cima del profilo"
}
}
}
}
},
"poll": {

View File

@ -1,8 +1,7 @@
{
"content": {
"button": {
"apply": "$t(common:buttons.apply)",
"cancel": "$t(common:buttons.cancel)"
"altText": {
"heading": ""
},
"notificationsFilter": {
"heading": "Filtra notifiche per tipo",

View File

@ -0,0 +1,70 @@
{
"accessibilityHint": "이 툿에 할 동작, 툿 자체나 포스트한 사용자",
"account": {
"title": "",
"mute": {
"action": "사용자 음소거"
},
"block": {
"action": "사용자 차단"
},
"reports": {
"action": "사용자 신고"
}
},
"instance": {
"title": "",
"block": {
"action": "인스턴스 {{instance}} 차단",
"alert": {
"title": "",
"message": "보통은 유저 음소거나 차단으로 충분해요.\n\n인스턴스를 차단하면, 팔로워를 포함하는 인스턴스의 모든 콘텐츠가 삭제됩니다!",
"buttons": {
"confirm": "확인"
}
}
}
},
"share": {
"status": {
"action": "툿 공유"
},
"account": {
"action": "사용자 공유"
}
},
"status": {
"title": "",
"edit": {
"action": ""
},
"delete": {
"action": "툿 삭제",
"alert": {
"title": "삭제 확인?",
"message": "",
"buttons": {
"confirm": "확인"
}
}
},
"deleteEdit": {
"action": "",
"alert": {
"title": "",
"message": "",
"buttons": {
"confirm": "확인"
}
}
},
"mute": {
"action-muted_false": "",
"action-muted_true": ""
},
"pin": {
"action-pinned_false": "",
"action-pinned_true": ""
}
}
}

View File

@ -1,27 +1,17 @@
{
"title": "미디어 소스 선택",
"options": {
"library": "라이브러리에서 업로드",
"photo": "사진 촬영",
"cancel": "$t(common:buttons.cancel)"
"image": "",
"image_max": "",
"video": "",
"video_max": ""
},
"library": {
"alert": {
"title": "권한 없음",
"message": "업로드를 위해 사진 라이브러리 권한이 필요해요",
"buttons": {
"settings": "설정 업데이트",
"cancel": "$t(common:buttons.cancel)"
}
}
},
"photo": {
"alert": {
"title": "권한 없음",
"message": "업로드를 위해 카메라 사용 권한이 필요해요",
"buttons": {
"settings": "설정 업데이트",
"cancel": "$t(common:buttons.cancel)"
"settings": "설정 업데이트"
}
}
}

View File

@ -123,94 +123,6 @@
"delete": {
"function": "개인 메시지 삭제"
}
},
"actions": {
"accessibilityHint": "이 툿에 할 동작, 툿 자체나 포스트한 사용자",
"account": {
"heading": "사용자 정보",
"mute": {
"function": "사용자 음소거",
"button": "@{{acct}} 음소거"
},
"block": {
"function": "사용자 차단",
"button": "@{{acct}} 차단"
},
"reports": {
"function": "사용자 신고",
"button": "@{{acct}} 신고"
}
},
"domain": {
"heading": "인스턴스 정보",
"block": {
"function": "인스턴스 차단",
"button": "인스턴스 {{domain}} 차단"
},
"alert": {
"title": "{{domain}}을 정말 차단할까요?",
"message": "보통은 유저 음소거나 차단으로 충분해요.\n\n인스턴스를 차단하면, 팔로워를 포함하는 인스턴스의 모든 콘텐츠가 삭제됩니다!",
"buttons": {
"confirm": "차단 확인",
"cancel": "$t(common:buttons.cancel)"
}
}
},
"share": {
"status": {
"heading": "툿 공유",
"button": "이 툿의 링크 공유"
},
"account": {
"heading": "사용자 공유",
"button": "이 사용자에게 링크 공유"
}
},
"status": {
"heading": "툿 정보",
"edit": {
"function": "",
"button": ""
},
"delete": {
"function": "툿 삭제",
"button": "이 툿 삭제",
"alert": {
"title": "툿을 정말 삭제할까요?",
"message": "",
"buttons": {
"confirm": "삭제 확인",
"cancel": "$t(common:buttons.cancel)"
}
}
},
"deleteEdit": {
"function": "툿 삭제",
"button": "삭제하고 다시 쓰기",
"alert": {
"title": "툿을 정말 삭제할까요?",
"message": "이 툿을 삭제하고 다시 초안을 작성하시겠어요? 모든 답장, 부스트와 즐겨찾기가 지워져요.",
"buttons": {
"confirm": "삭제 확인",
"cancel": "$t(common:buttons.cancel)"
}
}
},
"mute": {
"function": "툿 음소거",
"button": {
"positive": "이 툿과 답장 음소거",
"negative": "이 툿과 답장 음소거 해제"
}
},
"pin": {
"function": "고정",
"button": {
"positive": "이 툿 고정",
"negative": "이 툿 고정 해제"
}
}
}
}
},
"poll": {

View File

@ -1,8 +1,7 @@
{
"content": {
"button": {
"apply": "$t(common:buttons.apply)",
"cancel": "$t(common:buttons.cancel)"
"altText": {
"heading": ""
},
"notificationsFilter": {
"heading": "알림 종류 표시",

View File

@ -0,0 +1,70 @@
{
"accessibilityHint": "Ações para este toot, como o seu usuário publicado",
"account": {
"title": "",
"mute": {
"action": "Silenciar usuário"
},
"block": {
"action": "Bloquear usuário"
},
"reports": {
"action": "Denunciar usuário"
}
},
"instance": {
"title": "",
"block": {
"action": "Bloquear a instância {{instance}}",
"alert": {
"title": "",
"message": "Na maioria das vezes, você pode silenciar ou bloquear determinado usuário.\n\nDepois de bloquear a instância, todo seu conteúdo, incluindo seguidores, será removido!",
"buttons": {
"confirm": "Confirmar"
}
}
}
},
"share": {
"status": {
"action": "Compartilhar toot"
},
"account": {
"action": "Compartilhar Usuário"
}
},
"status": {
"title": "",
"edit": {
"action": "Editar toot"
},
"delete": {
"action": "Remover toot",
"alert": {
"title": "Confirme a exclusão?",
"message": "",
"buttons": {
"confirm": "Confirmar"
}
}
},
"deleteEdit": {
"action": "",
"alert": {
"title": "",
"message": "",
"buttons": {
"confirm": "Confirmar"
}
}
},
"mute": {
"action-muted_false": "",
"action-muted_true": ""
},
"pin": {
"action-pinned_false": "",
"action-pinned_true": ""
}
}
}

View File

@ -1,27 +1,17 @@
{
"title": "Selecionar fonte de mídia",
"options": {
"library": "Carregar da biblioteca",
"photo": "Tirar foto",
"cancel": "$t(common:buttons.cancel)"
"image": "",
"image_max": "",
"video": "",
"video_max": ""
},
"library": {
"alert": {
"title": "Sem permissão",
"message": "Exigir permissão de leitura da biblioteca de fotos para fazer upload",
"buttons": {
"settings": "Atualizar configurações",
"cancel": "$t(common:buttons.cancel)"
}
}
},
"photo": {
"alert": {
"title": "Sem permissão",
"message": "Requer permissão de uso da câmera para fazer upload",
"buttons": {
"settings": "Atualizar configurações",
"cancel": "$t(common:buttons.cancel)"
"settings": "Atualizar configurações"
}
}
}

View File

@ -46,7 +46,7 @@
},
"bookmarked": {
"accessibilityLabel": "Adicionar este toot aos favoritos",
"function": "Favoritos"
"function": "Salvos"
}
},
"actionsUsers": {
@ -123,94 +123,6 @@
"delete": {
"function": "Excluir mensagem direta"
}
},
"actions": {
"accessibilityHint": "Ações para este toot, como o seu usuário publicado",
"account": {
"heading": "Sobre o usuário",
"mute": {
"function": "Silenciar usuário",
"button": "Silenciar @{{acct}}"
},
"block": {
"function": "Bloquear usuário",
"button": "Bloquear @{{acct}}"
},
"reports": {
"function": "Denunciar usuário",
"button": "Reportar @{{acct}}"
}
},
"domain": {
"heading": "Sobre a instância",
"block": {
"function": "Bloquear instância",
"button": "Bloquear a instância {{domain}}"
},
"alert": {
"title": "Confirma bloquear {{domain}}?",
"message": "Na maioria das vezes, você pode silenciar ou bloquear determinado usuário.\n\nDepois de bloquear a instância, todo seu conteúdo, incluindo seguidores, será removido!",
"buttons": {
"confirm": "Confirmar bloqueio",
"cancel": "$t(common:buttons.cancel)"
}
}
},
"share": {
"status": {
"heading": "Compartilhar toot",
"button": "Compartilhar o link para este mundo"
},
"account": {
"heading": "Compartilhar Usuário",
"button": "Compartilhar link para este usuário"
}
},
"status": {
"heading": "Sobre o toot",
"edit": {
"function": "Editar toot",
"button": "Editar este toot"
},
"delete": {
"function": "Remover toot",
"button": "Deletar este toot",
"alert": {
"title": "Confirmar exclusão?",
"message": "Tem certeza que deseja excluir este toot? Todos os boosts e favoritos serão apagados, incluindo todas as respostas.",
"buttons": {
"confirm": "Confirme a exclusão",
"cancel": "$t(common:buttons.cancel)"
}
}
},
"deleteEdit": {
"function": "Remover toot",
"button": "Excluir e rascunhar",
"alert": {
"title": "Confirmar exclusão?",
"message": "Tem certeza que deseja excluir este toot? Todos os boosts e favoritos serão apagados, incluindo todas as respostas.",
"buttons": {
"confirm": "Confirme a exclusão",
"cancel": "$t(common:buttons.cancel)"
}
}
},
"mute": {
"function": "Silenciar toot",
"button": {
"positive": "Silenciar este toot e respostas",
"negative": "Desbloquear este toot e respostas"
}
},
"pin": {
"function": "Fixar",
"button": {
"positive": "Fixar este toot",
"negative": "Desafixar este toot"
}
}
}
}
},
"poll": {

View File

@ -1,8 +1,7 @@
{
"content": {
"button": {
"apply": "$t(common:buttons.apply)",
"cancel": "$t(common:buttons.cancel)"
"altText": {
"heading": "Texto Alternativo"
},
"notificationsFilter": {
"heading": "Exibir notificações",
@ -13,8 +12,8 @@
"reblog": "$t(screenTabs:me.push.reblog.heading)",
"mention": "$t(screenTabs:me.push.mention.heading)",
"poll": "$t(screenTabs:me.push.poll.heading)",
"status": "",
"update": ""
"status": "Toot de usuários inscritos",
"update": "Toot foi editado"
}
}
}

View File

@ -168,7 +168,7 @@
"header": {
"title": "Rascunho"
},
"warning": "",
"warning": "Os rascunhos são armazenados localmente e podem ser perdidos em eventos infortúnios. Aconselhamos não usar rascunhos para armazenamento de longo prazo.",
"content": {
"accessibilityHint": "Toque para editar este rascunho",
"textEmpty": "O conteúdo está vazio"

View File

@ -170,7 +170,7 @@
"heading": "Novo seguidor"
},
"follow_request": {
"heading": ""
"heading": "Solicitações de seguidores pendentes"
},
"favourite": {
"heading": "Favoritos"
@ -185,7 +185,7 @@
"heading": "Pesquisa atualizada"
},
"status": {
"heading": ""
"heading": "Toot de usuários inscritos"
},
"howitworks": "Saiba como funciona o roteamento"
},

View File

@ -0,0 +1,70 @@
{
"accessibilityHint": "Hành động với tút này, bao gồm đăng thủ công hay đăng tự động",
"account": {
"title": "Hành động người dùng",
"mute": {
"action": "Ẩn người này"
},
"block": {
"action": "Chặn người này"
},
"reports": {
"action": "Báo cáo"
}
},
"instance": {
"title": "Hành động máy chủ",
"block": {
"action": "Chặn {{instance}}",
"alert": {
"title": "Xác nhận chặn {{instance}} ?",
"message": "Bạn có thể ẩn hoặc chặn bất kỳ người nào.\n\nĐối với máy chủ, toàn bộ nội dung bao gồm người theo dõi bạn từ máy chủ đó cũng sẽ bị chặn!",
"buttons": {
"confirm": "Xác nhận"
}
}
}
},
"share": {
"status": {
"action": "Đăng lại"
},
"account": {
"action": "Chia sẻ"
}
},
"status": {
"title": "Hành động tút",
"edit": {
"action": "Sửa tút"
},
"delete": {
"action": "Xóa tút",
"alert": {
"title": "Tiếp tục xóa?",
"message": "Tất cả lượt thích và đăng lại sẽ bị mất, bao gồm cả những trả lời.",
"buttons": {
"confirm": "Xác nhận"
}
}
},
"deleteEdit": {
"action": "Xóa và đăng lại",
"alert": {
"title": "Tiếp tục xóa và đăng lại?",
"message": "Tất cả lượt thích và đăng lại sẽ bị mất, bao gồm cả những trả lời.",
"buttons": {
"confirm": "Xác nhận"
}
}
},
"mute": {
"action-muted_false": "Ẩn tút này",
"action-muted_true": "Bỏ ẩn tút này"
},
"pin": {
"action-pinned_false": "Tút ghim",
"action-pinned_true": "Bỏ ghim tút"
}
}
}

View File

@ -1,27 +1,17 @@
{
"title": "Chọn nguồn",
"options": {
"library": "Từ thiết bị",
"photo": "Chụp ảnh",
"cancel": "$t(common:buttons.cancel)"
"image": "Tải ảnh lên",
"image_max": "Tải ảnh lên (tối đa {{max}})",
"video": "Tải video lên",
"video_max": "Tải video lên (tối đa {{max}})"
},
"library": {
"alert": {
"title": "Chưa được cấp quyền",
"message": "Bạn cần cấp quyền đọc thư viện ảnh trước",
"buttons": {
"settings": "Cài đặt cập nhật",
"cancel": "$t(common:buttons.cancel)"
}
}
},
"photo": {
"alert": {
"title": "Chưa cấp quyền",
"message": "Cần cấp quyền sử dụng camera trước",
"buttons": {
"settings": "Cài đặt cập nhật",
"cancel": "$t(common:buttons.cancel)"
"settings": "Cài đặt cập nhật"
}
}
}

View File

@ -123,94 +123,6 @@
"delete": {
"function": "Xóa nhắn riêng"
}
},
"actions": {
"accessibilityHint": "Hành động cho tút này, bao gồm đăng thủ công hay đăng tự động",
"account": {
"heading": "Đối với người dùng",
"mute": {
"function": "Ẩn người dùng",
"button": "Ẩn @{{acct}}"
},
"block": {
"function": "Chặn người dùng",
"button": "Chặn @{{acct}}"
},
"reports": {
"function": "Báo cáo người dùng",
"button": "Báo cáo @{{acct}}"
}
},
"domain": {
"heading": "Đối với máy chủ",
"block": {
"function": "Chặn máy chủ",
"button": "Chặn {{domain}}"
},
"alert": {
"title": "Bạn có chắc muốn chặn {{domain}}?",
"message": "Bạn có thể ẩn hoặc chặn bất kỳ người nào.\n\nĐối với máy chủ, toàn bộ nội dung bao gồm người theo dõi bạn từ máy chủ đó cũng sẽ bị chặn!",
"buttons": {
"confirm": "Tiếp tục chặn",
"cancel": "$t(common:buttons.cancel)"
}
}
},
"share": {
"status": {
"heading": "Đăng lại",
"button": "Đăng lại URL tút"
},
"account": {
"heading": "Chia sẻ",
"button": "Chia sẻ URL người dùng này"
}
},
"status": {
"heading": "Đối với tút",
"edit": {
"function": "Sửa tút",
"button": "Sửa tút này"
},
"delete": {
"function": "Xóa tút",
"button": "Xóa tút này",
"alert": {
"title": "Vẫn xóa tút?",
"message": "Bạn có chắc muốn xóa tút này? Toàn bộ lượt thích, đăng lại và trả lời tút cũng sẽ bị xóa theo.",
"buttons": {
"confirm": "Xác nhận xóa",
"cancel": "$t(common:buttons.cancel)"
}
}
},
"deleteEdit": {
"function": "Xóa tút",
"button": "Xóa & viết lại",
"alert": {
"title": "Xác nhận xóa tút?",
"message": "Bạn có chắc muốn xóa và viết lại tút này? Toàn bộ lượt thích, đăng lại và trả lời tút cũng sẽ bị xóa theo.",
"buttons": {
"confirm": "Xác nhận xóa",
"cancel": "$t(common:buttons.cancel)"
}
}
},
"mute": {
"function": "Ẩn tút",
"button": {
"positive": "Ẩn tút này",
"negative": "Bỏ ẩn tút này"
}
},
"pin": {
"function": "Ghim",
"button": {
"positive": "Ghim tút này",
"negative": "Bỏ ghim tút này"
}
}
}
}
},
"poll": {

View File

@ -1,8 +1,7 @@
{
"content": {
"button": {
"apply": "$t(common:buttons.apply)",
"cancel": "$t(common:buttons.cancel)"
"altText": {
"heading": "Văn bản thay thế"
},
"notificationsFilter": {
"heading": "Những kiểu thông báo cho phép",

View File

@ -0,0 +1,70 @@
{
"accessibilityHint": "更多关于此条嘟文,例如发布者等",
"account": {
"title": "用户操作",
"mute": {
"action": "静音用户"
},
"block": {
"action": "屏蔽用户"
},
"reports": {
"action": "举报用户"
}
},
"instance": {
"title": "实例操作",
"block": {
"action": "屏蔽实例 {{instance}}",
"alert": {
"title": "确认屏蔽实例 {{instance}}",
"message": "多数情况下,隐藏或屏蔽特定用户即可。\n\n屏蔽之后来自此实例的所有内容将被移除。",
"buttons": {
"confirm": "确认"
}
}
}
},
"share": {
"status": {
"action": "分享嘟文"
},
"account": {
"action": "分享用户"
}
},
"status": {
"title": "嘟文操作",
"edit": {
"action": "编辑嘟文"
},
"delete": {
"action": "删除嘟文",
"alert": {
"title": "确认删除?",
"message": "所以转发及收藏将被清除,包括所有回复。",
"buttons": {
"confirm": "确认"
}
}
},
"deleteEdit": {
"action": "删除嘟文并重新发布",
"alert": {
"title": "确认删除并重新发布?",
"message": "所以转发及收藏将被清除,包括所有回复。",
"buttons": {
"confirm": "确认"
}
}
},
"mute": {
"action-muted_false": "静音嘟文及回复",
"action-muted_true": "取消静音嘟文及回复"
},
"pin": {
"action-pinned_false": "置顶嘟文",
"action-pinned_true": "取消置顶嘟文"
}
}
}

View File

@ -1,27 +1,17 @@
{
"title": "选择媒体",
"options": {
"library": "从相册上传",
"photo": "拍摄照片",
"cancel": "$t(common:buttons.cancel)"
"image": "上传图片",
"image_max": "上传照片(上限 {{max}}",
"video": "上传视频",
"video_max": "上传视频(上限 {{max}}"
},
"library": {
"alert": {
"title": "无权限",
"message": "需要读取相册权限才能上传附件",
"buttons": {
"settings": "去更新设置",
"cancel": "$t(common:buttons.cancel)"
}
}
},
"photo": {
"alert": {
"title": "无权限",
"message": "需要使用相机权限才能上传附件",
"buttons": {
"settings": "去更新设置",
"cancel": "$t(common:buttons.cancel)"
"settings": "去更新设置"
}
}
}

View File

@ -123,94 +123,6 @@
"delete": {
"function": "删除私信"
}
},
"actions": {
"accessibilityHint": "更多关于此条嘟文,例如发布者等",
"account": {
"heading": "关于用户",
"mute": {
"function": "隐藏 @{{acct}} 的嘟文",
"button": "隐藏 @{{acct}} 的嘟文"
},
"block": {
"function": "屏蔽 @{{acct}}",
"button": "屏蔽 @{{acct}}"
},
"reports": {
"function": "举报 @{{acct}}",
"button": "举报 @{{acct}}"
}
},
"domain": {
"heading": "关于社区",
"block": {
"function": "屏蔽社区",
"button": "屏蔽社区 {{domain}}"
},
"alert": {
"title": "确定要屏蔽 {{domain}} 吗?",
"message": "多数情况下,隐藏或屏蔽特定用户即可。\n\n屏蔽之后来自此社区的所有内容将不再出现在你的时间轴里。同时来自该社区的关注者将被移除。请谨慎使用。",
"buttons": {
"confirm": "确定屏蔽整个社区",
"cancel": "$t(common:buttons.cancel)"
}
}
},
"share": {
"status": {
"heading": "分享嘟文",
"button": "分享此条嘟文的链接"
},
"account": {
"heading": "分享用户",
"button": "分享此用户的链接"
}
},
"status": {
"heading": "关于嘟文",
"edit": {
"function": "编辑嘟文",
"button": "编辑此条嘟文"
},
"delete": {
"function": "删除",
"button": "删除此条嘟文",
"alert": {
"title": "确认删除嘟文?",
"message": "确定要删除这条嘟文吗?所有相关的转嘟和喜欢都会被清除,回复将会失去关联。",
"buttons": {
"confirm": "确认删除",
"cancel": "$t(common:buttons.cancel)"
}
}
},
"deleteEdit": {
"function": "删除并重新编辑",
"button": "删除并重新编辑此条嘟文",
"alert": {
"title": "确认删除嘟文?",
"message": "确定要删除这条嘟文并重新编辑它吗?所有相关的转嘟和喜欢都会被清除,回复将会失去关联。",
"buttons": {
"confirm": "删除并重新编辑",
"cancel": "$t(common:buttons.cancel)"
}
}
},
"mute": {
"function": "静音",
"button": {
"positive": "静音此条嘟文及对话",
"negative": "取消静音此条嘟文及对话"
}
},
"pin": {
"function": "置顶",
"button": {
"positive": "置顶此条嘟文",
"negative": "取消置顶此条嘟文"
}
}
}
}
},
"poll": {

View File

@ -1,8 +1,7 @@
{
"content": {
"button": {
"apply": "$t(common:buttons.apply)",
"cancel": "$t(common:buttons.cancel)"
"altText": {
"heading": "替代文本"
},
"notificationsFilter": {
"heading": "显示通知",

View File

@ -0,0 +1,70 @@
{
"accessibilityHint": "",
"account": {
"title": "",
"mute": {
"action": ""
},
"block": {
"action": ""
},
"reports": {
"action": ""
}
},
"instance": {
"title": "",
"block": {
"action": "",
"alert": {
"title": "",
"message": "",
"buttons": {
"confirm": ""
}
}
}
},
"share": {
"status": {
"action": ""
},
"account": {
"action": ""
}
},
"status": {
"title": "",
"edit": {
"action": ""
},
"delete": {
"action": "",
"alert": {
"title": "",
"message": "",
"buttons": {
"confirm": ""
}
}
},
"deleteEdit": {
"action": "",
"alert": {
"title": "",
"message": "",
"buttons": {
"confirm": ""
}
}
},
"mute": {
"action-muted_false": "",
"action-muted_true": ""
},
"pin": {
"action-pinned_false": "",
"action-pinned_true": ""
}
}
}

View File

@ -1,27 +1,17 @@
{
"title": "選擇媒體來源",
"options": {
"library": "上傳",
"photo": "拍照",
"cancel": "$t(common:buttons.cancel)"
"image": "",
"image_max": "",
"video": "",
"video_max": ""
},
"library": {
"alert": {
"title": "權限不足",
"message": "上傳照片需要讀取的權限",
"buttons": {
"settings": "更新設定",
"cancel": "$t(common:buttons.cancel)"
}
}
},
"photo": {
"alert": {
"title": "權限不足",
"message": "需要使用相機的權限來上傳",
"buttons": {
"settings": "更新設定",
"cancel": "$t(common:buttons.cancel)"
"settings": "更新設定"
}
}
}

View File

@ -123,94 +123,6 @@
"delete": {
"function": ""
}
},
"actions": {
"accessibilityHint": "",
"account": {
"heading": "",
"mute": {
"function": "",
"button": ""
},
"block": {
"function": "",
"button": ""
},
"reports": {
"function": "",
"button": ""
}
},
"domain": {
"heading": "",
"block": {
"function": "",
"button": ""
},
"alert": {
"title": "",
"message": "",
"buttons": {
"confirm": "",
"cancel": ""
}
}
},
"share": {
"status": {
"heading": "",
"button": ""
},
"account": {
"heading": "",
"button": ""
}
},
"status": {
"heading": "",
"edit": {
"function": "",
"button": ""
},
"delete": {
"function": "",
"button": "",
"alert": {
"title": "",
"message": "",
"buttons": {
"confirm": "",
"cancel": ""
}
}
},
"deleteEdit": {
"function": "",
"button": "",
"alert": {
"title": "",
"message": "",
"buttons": {
"confirm": "",
"cancel": ""
}
}
},
"mute": {
"function": "",
"button": {
"positive": "",
"negative": ""
}
},
"pin": {
"function": "",
"button": {
"positive": "",
"negative": ""
}
}
}
}
},
"poll": {

View File

@ -1,8 +1,7 @@
{
"content": {
"button": {
"apply": "",
"cancel": ""
"altText": {
"heading": ""
},
"notificationsFilter": {
"heading": "",

View File

@ -1,14 +1,7 @@
import analytics from '@components/analytics'
import Button from '@components/Button'
import { RootStackScreenProps } from '@utils/navigation/navigators'
import {
getInstanceAccount,
getInstanceUrl
} from '@utils/slices/instancesSlice'
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import React, { useCallback, useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import { Dimensions, StyleSheet, View } from 'react-native'
import {
PanGestureHandler,
@ -28,47 +21,13 @@ import {
SafeAreaProvider,
useSafeAreaInsets
} from 'react-native-safe-area-context'
import { useSelector } from 'react-redux'
import ActionsAccount from './Actions/Account'
import ActionsAltText from './Actions/AltText'
import ActionsDomain from './Actions/Domain'
import ActionsNotificationsFilter from './Actions/NotificationsFilter'
import ActionsShare from './Actions/Share'
import ActionsStatus from './Actions/Status'
const ScreenActions = ({
route: { params },
navigation
}: RootStackScreenProps<'Screen-Actions'>) => {
const { t } = useTranslation()
const instanceAccount = useSelector(
getInstanceAccount,
(prev, next) => prev?.id === next?.id
)
let sameAccount = false
switch (params.type) {
case 'status':
console.log('media length', params.status.media_attachments.length)
sameAccount = instanceAccount?.id === params.status.account.id
break
case 'account':
sameAccount = instanceAccount?.id === params.account.id
break
}
const instanceDomain = useSelector(getInstanceUrl)
let sameDomain = true
let statusDomain: string
switch (params.type) {
case 'status':
statusDomain = params.status.uri
? params.status.uri.split(new RegExp(/\/\/(.*?)\//))[1]
: ''
sameDomain = instanceDomain === statusDomain
break
}
const { colors } = useTheme()
const insets = useSafeAreaInsets()
@ -106,72 +65,6 @@ const ScreenActions = ({
const actions = () => {
switch (params.type) {
case 'status':
return (
<>
{!sameAccount ? (
<ActionsAccount
queryKey={params.queryKey}
rootQueryKey={params.rootQueryKey}
account={params.status.account}
dismiss={dismiss}
/>
) : null}
{sameAccount && params.status ? (
<ActionsStatus
navigation={navigation}
queryKey={params.queryKey}
rootQueryKey={params.rootQueryKey}
status={params.status}
dismiss={dismiss}
/>
) : null}
{!sameDomain && statusDomain ? (
<ActionsDomain
queryKey={params.queryKey}
rootQueryKey={params.rootQueryKey}
domain={statusDomain}
dismiss={dismiss}
/>
) : null}
{params.status.visibility !== 'direct' ? (
<ActionsShare
url={params.status.url || params.status.uri}
type={params.type}
dismiss={dismiss}
/>
) : null}
<Button
type='text'
content={t('common:buttons.cancel')}
onPress={() => {
analytics('bottomsheet_acknowledge')
}}
style={styles.button}
/>
</>
)
case 'account':
return (
<>
{!sameAccount ? (
<ActionsAccount account={params.account} dismiss={dismiss} />
) : null}
<ActionsShare
url={params.account.url}
type={params.type}
dismiss={dismiss}
/>
<Button
type='text'
content={t('common:buttons.cancel')}
onPress={() => {
analytics('bottomsheet_acknowledge')
}}
style={styles.button}
/>
</>
)
case 'notifications_filter':
return <ActionsNotificationsFilter />
case 'alt_text':

View File

@ -136,18 +136,6 @@ const ScreenCompose: React.FC<RootStackScreenProps<'Screen-Compose'>> = ({
])
useEffect(() => {
const uploadImage = async ({
type,
uri
}: {
type: 'image' | 'video'
uri: string
}) => {
await uploadAttachment({
composeDispatch,
imageInfo: { type, uri, width: 100, height: 100 }
})
}
switch (params?.type) {
case 'share':
if (params.text) {
@ -158,12 +146,13 @@ const ScreenCompose: React.FC<RootStackScreenProps<'Screen-Compose'>> = ({
disableDebounce: true
})
}
if (params.images?.length) {
params.images.forEach(image => {
uploadImage({ type: 'image', uri: image.uri })
})
} else if (params.video) {
uploadImage({ type: 'video', uri: params.video.uri })
if (params.media?.length) {
for (const m of params.media) {
uploadAttachment({
composeDispatch,
media: { ...m, width: 100, height: 100 }
})
}
}
break
case 'edit':

View File

@ -128,7 +128,9 @@ const ComposeEditAttachmentImage: React.FC<Props> = ({ index }) => {
height: imageDimensionis.height
}}
source={{
uri: theAttachmentLocal?.uri || theAttachmentRemote?.preview_url
uri: theAttachmentLocal?.path
? `file://${theAttachmentLocal?.path}`
: theAttachmentRemote?.preview_url
}}
/>
<PanGestureHandler onGestureEvent={onGestureEvent}>

View File

@ -34,7 +34,7 @@ const ComposeEditAttachmentRoot: React.FC<Props> = ({ index }) => {
video={
video.local
? ({
url: video.local.uri,
url: `file://${video.local.path}`,
preview_url: video.local.local_thumbnail,
blurhash: video.remote?.blurhash
} as Mastodon.AttachmentVideo)

View File

@ -1,5 +1,4 @@
import * as Crypto from 'expo-crypto'
import { ImageInfo } from 'expo-image-picker/build/ImagePicker.types'
import * as VideoThumbnails from 'expo-video-thumbnails'
import { Dispatch } from 'react'
import { Alert } from 'react-native'
@ -8,6 +7,7 @@ import { ActionSheetOptions } from '@expo/react-native-action-sheet'
import i18next from 'i18next'
import apiInstance from '@api/instance'
import mediaSelector from '@components/mediaSelector'
import { ImageOrVideo } from 'react-native-image-crop-picker'
export interface Props {
composeDispatch: Dispatch<ComposeAction>
@ -19,39 +19,38 @@ export interface Props {
export const uploadAttachment = async ({
composeDispatch,
imageInfo
media
}: {
composeDispatch: Dispatch<ComposeAction>
imageInfo: Pick<ImageInfo, 'type' | 'uri' | 'width' | 'height'>
media: Pick<ImageOrVideo, 'path' | 'mime' | 'width' | 'height'>
}) => {
const hash = await Crypto.digestStringAsync(
Crypto.CryptoDigestAlgorithm.SHA256,
imageInfo.uri + Math.random()
media.path + Math.random()
)
let attachmentType: string
switch (imageInfo.type) {
switch (media.mime.split('/')[0]) {
case 'image':
console.log('uri', imageInfo.uri)
attachmentType = `image/${imageInfo.uri.split('.')[1]}`
attachmentType = `image/${media.path.split('.')[1]}`
composeDispatch({
type: 'attachment/upload/start',
payload: {
local: { ...imageInfo, local_thumbnail: imageInfo.uri, hash },
local: { ...media, type: 'image', local_thumbnail: media.path, hash },
uploading: true
}
})
break
case 'video':
attachmentType = `video/${imageInfo.uri.split('.')[1]}`
VideoThumbnails.getThumbnailAsync(imageInfo.uri)
attachmentType = `video/${media.path.split('.')[1]}`
VideoThumbnails.getThumbnailAsync(media.path)
.then(({ uri, width, height }) =>
composeDispatch({
type: 'attachment/upload/start',
payload: {
local: {
...imageInfo,
...media,
type: 'video',
local_thumbnail: uri,
hash,
width,
@ -65,7 +64,7 @@ export const uploadAttachment = async ({
composeDispatch({
type: 'attachment/upload/start',
payload: {
local: { ...imageInfo, hash },
local: { ...media, type: 'video', hash },
uploading: true
}
})
@ -76,7 +75,7 @@ export const uploadAttachment = async ({
composeDispatch({
type: 'attachment/upload/start',
payload: {
local: { ...imageInfo, hash },
local: { ...media, type: 'unknown', hash },
uploading: true
}
})
@ -106,14 +105,14 @@ export const uploadAttachment = async ({
const formData = new FormData()
formData.append('file', {
// @ts-ignore
uri: imageInfo.uri,
uri: `file://${media.path}`,
name: attachmentType,
type: attachmentType
})
} as any)
return apiInstance<Mastodon.Attachment>({
method: 'post',
version: 'v2',
url: 'media',
body: formData
})
@ -121,7 +120,7 @@ export const uploadAttachment = async ({
if (res.body.id) {
composeDispatch({
type: 'attachment/upload/end',
payload: { remote: res.body, local: imageInfo }
payload: { remote: res.body, local: media }
})
} else {
uploadFailed()
@ -136,8 +135,13 @@ const chooseAndUploadAttachment = async ({
composeDispatch,
showActionSheetWithOptions
}: Props): Promise<any> => {
const result = await mediaSelector({ showActionSheetWithOptions })
await uploadAttachment({ composeDispatch, imageInfo: result })
const result = await mediaSelector({
indicateMaximum: true,
showActionSheetWithOptions
})
for (const media of result) {
uploadAttachment({ composeDispatch, media })
}
}
export default chooseAndUploadAttachment

View File

@ -1,10 +1,11 @@
import CustomText from '@components/Text'
import PasteInput, { PastedFile } from '@mattermost/react-native-paste-input'
import { getInstanceConfigurationStatusMaxAttachments } from '@utils/slices/instancesSlice'
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import React, { useContext } from 'react'
import { useTranslation } from 'react-i18next'
import { Alert, TextInput } from 'react-native'
import { Alert } from 'react-native'
import { useSelector } from 'react-redux'
import formatText from '../../formatText'
import ComposeContext from '../../utils/createContext'
@ -21,7 +22,7 @@ const ComposeTextInput: React.FC = () => {
)
return (
<TextInput
<PasteInput
keyboardAppearance={mode}
style={{
...StyleConstants.FontStyle.M,
@ -62,8 +63,12 @@ const ComposeTextInput: React.FC = () => {
}}
ref={composeState.textInputFocus.refs.text}
scrollEnabled={false}
onImageChange={({ nativeEvent }) => {
if (composeState.attachments.uploads.length >= maxAttachments) {
disableCopyPaste={false}
onPaste={(error: string | null | undefined, files: PastedFile[]) => {
if (
composeState.attachments.uploads.length + files.length >
maxAttachments
) {
Alert.alert(
t(
'content.root.header.textInput.keyboardImage.exceedMaximum.title'
@ -80,12 +85,13 @@ const ComposeTextInput: React.FC = () => {
)
return
}
if (nativeEvent.linkUri) {
for (const file of files) {
uploadAttachment({
composeDispatch,
imageInfo: {
uri: nativeEvent.linkUri,
type: 'image',
media: {
path: file.uri,
mime: file.type,
width: 100,
height: 100
}
@ -94,7 +100,7 @@ const ComposeTextInput: React.FC = () => {
}}
>
<CustomText>{composeState.text.formatted}</CustomText>
</TextInput>
</PasteInput>
)
}

View File

@ -58,7 +58,7 @@ const composeReducer = (
attachments: {
...state.attachments,
uploads: state.attachments.uploads.map(upload =>
upload.local?.uri === action.payload.local?.uri
upload.local?.path === action.payload.local?.path
? { ...upload, remote: action.payload.remote, uploading: false }
: upload
)

View File

@ -1,6 +1,12 @@
import { ImageOrVideo } from 'react-native-image-crop-picker'
export type ExtendedAttachment = {
remote?: Mastodon.Attachment
local?: App.IImageInfo & { local_thumbnail?: string; hash?: string }
local?: Pick<ImageOrVideo, 'path' | 'width' | 'height' | 'mime'> & {
type: 'image' | 'video' | 'unknown'
local_thumbnail?: string
hash?: string
}
uploading?: boolean
}
@ -115,7 +121,10 @@ export type ComposeAction =
}
| {
type: 'attachment/upload/end'
payload: { remote: Mastodon.Attachment; local: ImageInfo }
payload: {
remote: Mastodon.Attachment
local: Pick<ImageOrVideo, 'path' | 'width' | 'height' | 'mime'>
}
}
| {
type: 'attachment/upload/fail'

View File

@ -3,7 +3,6 @@ import { MenuRow } from '@components/Menu'
import { useActionSheet } from '@expo/react-native-action-sheet'
import { useProfileMutation, useProfileQuery } from '@utils/queryHooks/profile'
import { useTheme } from '@utils/styles/ThemeManager'
import * as ImagePicker from 'expo-image-picker'
import React, { RefObject } from 'react'
import { useTranslation } from 'react-i18next'
import FlashMessage from 'react-native-flash-message'
@ -30,9 +29,13 @@ const ProfileAvatarHeader: React.FC<Props> = ({ type, messageRef }) => {
iconBack='ChevronRight'
onPress={async () => {
const image = await mediaSelector({
mediaType: 'photo',
maximum: 1,
showActionSheetWithOptions,
mediaTypes: ImagePicker.MediaTypeOptions.Images,
resize: { width: 400, height: 400 }
resize:
type === 'avatar'
? { width: 400, height: 400 }
: { width: 1500, height: 500 }
})
mutation.mutate({
theme,
@ -43,7 +46,7 @@ const ProfileAvatarHeader: React.FC<Props> = ({ type, messageRef }) => {
failed: true
},
type,
data: image.uri
data: image[0].path
})
}}
/>

View File

@ -1,5 +1,3 @@
import analytics from '@components/analytics'
import { HeaderRight } from '@components/Header'
import Timeline from '@components/Timeline'
import TimelineDefault from '@components/Timeline/Default'
import SegmentedControl from '@react-native-community/segmented-control'
@ -23,8 +21,7 @@ const TabSharedAccount: React.FC<
> = ({
route: {
params: { account }
},
navigation
}
}) => {
const { t, i18n } = useTranslation('screenTabs')
const { colors, mode } = useTheme()
@ -33,33 +30,6 @@ const TabSharedAccount: React.FC<
const scrollY = useSharedValue(0)
useEffect(() => {
const updateHeaderRight = () =>
navigation.setOptions({
headerRight: () => (
<HeaderRight
accessibilityLabel={t('shared.account.actions.accessibilityLabel', {
user: data?.acct
})}
accessibilityHint={t('shared.account.actions.accessibilityHint')}
content='MoreHorizontal'
onPress={() => {
analytics('bottomsheet_open_press', {
page: 'account'
})
// @ts-ignore
navigation.navigate('Screen-Actions', {
type: 'account',
account
})
}}
background
/>
)
})
return updateHeaderRight()
}, [i18n.language])
const onScroll = useCallback(({ nativeEvent }) => {
scrollY.value = nativeEvent.contentOffset.y
}, [])

View File

@ -1,4 +1,6 @@
import { HeaderCenter, HeaderLeft } from '@components/Header'
import contextMenuAccount from '@components/ContextMenu/account'
import contextMenuShare from '@components/ContextMenu/share'
import { HeaderCenter, HeaderLeft, HeaderRight } from '@components/Header'
import { ParseEmojis } from '@components/Parse'
import CustomText from '@components/Text'
import { createNativeStackNavigator } from '@react-navigation/native-stack'
@ -16,6 +18,7 @@ import { debounce } from 'lodash'
import React from 'react'
import { Trans, useTranslation } from 'react-i18next'
import { Platform, TextInput, View } from 'react-native'
import ContextMenu, { ContextMenuAction } from 'react-native-context-menu-view'
const TabSharedRoot = ({
Stack
@ -36,7 +39,10 @@ const TabSharedRoot = ({
name='Tab-Shared-Account'
component={TabSharedAccount}
options={({
navigation
navigation,
route: {
params: { account }
}
}: TabSharedStackScreenProps<'Tab-Shared-Account'>) => {
return {
headerTransparent: true,
@ -46,7 +52,44 @@ const TabSharedRoot = ({
title: '',
headerLeft: () => (
<HeaderLeft onPress={() => navigation.goBack()} background />
)
),
headerRight: () => {
const actions: ContextMenuAction[] = []
const shareOnPress = contextMenuShare({
actions,
type: 'account',
url: account.url
})
const accountOnPress = contextMenuAccount({
actions,
id: account.id
})
return (
<ContextMenu
actions={actions}
onPress={({ nativeEvent: { id } }) => {
shareOnPress(id)
accountOnPress(id)
}}
dropdownMenuMode
>
<HeaderRight
accessibilityLabel={t(
'shared.account.actions.accessibilityLabel',
{ user: account.acct }
)}
accessibilityHint={t(
'shared.account.actions.accessibilityHint'
)}
content='MoreHorizontal'
onPress={() => {}}
background
/>
</ContextMenu>
)
}
}
}}
/>

View File

@ -7,16 +7,6 @@ import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
export type RootStackParamList = {
'Screen-Tabs': NavigatorScreenParams<ScreenTabsStackParamList>
'Screen-Actions':
| {
type: 'status'
queryKey: QueryKeyTimeline
rootQueryKey?: QueryKeyTimeline
status: Mastodon.Status
}
| {
type: 'account'
account: Mastodon.Account
}
| {
type: 'notifications_filter'
}
@ -52,8 +42,7 @@ export type RootStackParamList = {
| {
type: 'share'
text?: string
images?: { type: string; uri: string }[]
video?: { type: string; uri: string }
media?: { path: string; mime: string }[]
}
| undefined
'Screen-ImagesViewer': {

View File

@ -78,7 +78,6 @@ const pushUseConnect = ({ t, instances }: Params) => {
useEffect(() => {
const appStateListener = AppState.addEventListener('change', state => {
console.log('changing state to', state)
if (expoToken && pushEnabled.length && state === 'active') {
Notifications.getBadgeCountAsync().then(count => {
if (count > 0) {
@ -96,6 +95,7 @@ const pushUseConnect = ({ t, instances }: Params) => {
return useEffect(() => {
if (expoToken && pushEnabled.length) {
Notifications.setBadgeCountAsync(0)
connect()
}
}, [expoToken, pushEnabled.length])

View File

@ -71,11 +71,10 @@ const mutationFunction = async ({ type, data }: MutationVarsProfile) => {
})
} else if (type === 'avatar' || type === 'header') {
formData.append(type, {
// @ts-ignore
uri: data,
uri: `file://${data}`,
name: 'image/jpeg',
type: 'image/jpeg'
})
} as any)
} else {
// @ts-ignore
formData.append(type, data)

356
yarn.lock
View File

@ -1410,10 +1410,10 @@
mv "~2"
safe-json-stringify "~1"
"@expo/cli@0.1.4":
version "0.1.4"
resolved "https://registry.yarnpkg.com/@expo/cli/-/cli-0.1.4.tgz#1eebf652eabf0eca4b567d9f8bb6ab38d11a3051"
integrity sha512-A9yq0+3ntqc7eNot4QfhAtAqx8bT50uleeQfTwhCiBPlArQ+zL2sHJVR5Vy79o80PJLQ0KoP3sxsCs1nkZ6rWw==
"@expo/cli@0.1.5":
version "0.1.5"
resolved "https://registry.yarnpkg.com/@expo/cli/-/cli-0.1.5.tgz#2427e3c3b6be1936b2e6ffb595fc9c83e37e4be1"
integrity sha512-27LNT3b9MtBHEosmvJiC9Ug9aJpQAK9T3cC8ekaB9cHnVcJw+mJs2kdVBYpV1aBjKkH7T57aiWWimZp0O7m1wQ==
dependencies:
"@babel/runtime" "^7.14.0"
"@expo/code-signing-certificates" "^0.0.2"
@ -1532,24 +1532,6 @@
xcode "^3.0.1"
xml2js "0.4.23"
"@expo/config-plugins@^2.0.2":
version "2.0.4"
resolved "https://registry.yarnpkg.com/@expo/config-plugins/-/config-plugins-2.0.4.tgz#955fd70a2aeefbe99ec71cecb1d7ea7b626dc79e"
integrity sha512-JGt/X2tFr7H8KBQrKfbGo9hmCubQraMxq5sj3bqDdKmDOLcE1a/EDCP9g0U4GHsa425J8VDIkQUHYz3h3ndEXQ==
dependencies:
"@expo/config-types" "^41.0.0"
"@expo/json-file" "8.2.30"
"@expo/plist" "0.0.13"
debug "^4.3.1"
find-up "~5.0.0"
fs-extra "9.0.0"
getenv "^1.0.0"
glob "7.1.6"
resolve-from "^5.0.0"
slash "^3.0.0"
xcode "^3.0.1"
xml2js "^0.4.23"
"@expo/config-plugins@^4.0.14":
version "4.1.0"
resolved "https://registry.yarnpkg.com/@expo/config-plugins/-/config-plugins-4.1.0.tgz#0365e2e51e2e3e3b4e7db1fbbada5be661798be6"
@ -1572,11 +1554,6 @@
xcode "^3.0.1"
xml2js "0.4.23"
"@expo/config-types@^41.0.0":
version "41.0.0"
resolved "https://registry.yarnpkg.com/@expo/config-types/-/config-types-41.0.0.tgz#ffe1444c6c26e0e3a8f7149b4afe486e357536d1"
integrity sha512-Ax0pHuY5OQaSrzplOkT9DdpdmNzaVDnq9VySb4Ujq7UJ4U4jriLy8u93W98zunOXpcu0iiKubPsqD6lCiq0pig==
"@expo/config-types@^44.0.0":
version "44.0.0"
resolved "https://registry.yarnpkg.com/@expo/config-types/-/config-types-44.0.0.tgz#d3480fe2c99f9e895dae4ebba58b74ed72d03e26"
@ -1690,16 +1667,6 @@
semver "7.3.2"
tempy "0.3.0"
"@expo/json-file@8.2.30":
version "8.2.30"
resolved "https://registry.yarnpkg.com/@expo/json-file/-/json-file-8.2.30.tgz#bd855b6416b5c3af7e55b43f6761c1e7d2b755b0"
integrity sha512-vrgGyPEXBoFI5NY70IegusCSoSVIFV3T3ry4tjJg1MFQKTUlR7E0r+8g8XR6qC705rc2PawaZQjqXMAVtV6s2A==
dependencies:
"@babel/code-frame" "~7.10.4"
fs-extra "9.0.0"
json5 "^1.0.1"
write-file-atomic "^2.3.0"
"@expo/json-file@8.2.34":
version "8.2.34"
resolved "https://registry.yarnpkg.com/@expo/json-file/-/json-file-8.2.34.tgz#2f24e90a677195f7a81e167115460eb2902c3130"
@ -1756,15 +1723,6 @@
split "^1.0.1"
sudo-prompt "9.1.1"
"@expo/plist@0.0.13":
version "0.0.13"
resolved "https://registry.yarnpkg.com/@expo/plist/-/plist-0.0.13.tgz#700a48d9927aa2b0257c613e13454164e7371a96"
integrity sha512-zGPSq9OrCn7lWvwLLHLpHUUq2E40KptUFXn53xyZXPViI0k9lbApcR9KlonQZ95C+ELsf0BQ3gRficwK92Ivcw==
dependencies:
base64-js "^1.2.3"
xmlbuilder "^14.0.0"
xmldom "~0.5.0"
"@expo/plist@0.0.17":
version "0.0.17"
resolved "https://registry.yarnpkg.com/@expo/plist/-/plist-0.0.17.tgz#0f6c594e116f45a28f5ed20998dadb5f3429f88b"
@ -1855,126 +1813,126 @@
find-up "^5.0.0"
js-yaml "^4.1.0"
"@formatjs/ecma402-abstract@1.11.6":
version "1.11.6"
resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-1.11.6.tgz#0e828ddfed6fb3413ae379e48fb7170fb0795db5"
integrity sha512-6TcI+IroIK+GTWXBJ643LBJklmCBsqLt1sUTGWfzdBcI5Y6b1L1iamrJB1B5OAQLnhzWveLbmzPYHYsFEZfeig==
"@formatjs/ecma402-abstract@1.11.7":
version "1.11.7"
resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-1.11.7.tgz#47f1a854f679f813d9baa1ee55adae94880ec706"
integrity sha512-uNaok4XWMJBtPZk/veTDamFCm5HeWJUk2jwoVfH5/+wenQ60QHjH6T3UQ0GOOCz9jpKmed7vqOri7xSf//Dt7g==
dependencies:
"@formatjs/intl-localematcher" "0.2.27"
"@formatjs/intl-localematcher" "0.2.28"
tslib "2.4.0"
"@formatjs/fast-memoize@1.2.3":
version "1.2.3"
resolved "https://registry.yarnpkg.com/@formatjs/fast-memoize/-/fast-memoize-1.2.3.tgz#5c950bd64c4959e30bbd16b22a17040fbeb9c4d2"
integrity sha512-RVI3e4M7mIxAhKbbyS78H8++fsoiSRZgxh0zReHfvV6p1cpfgG2/k2qJYhJq0RXh6orVtUEsQ3xK9i4tDfsOSg==
"@formatjs/fast-memoize@1.2.4":
version "1.2.4"
resolved "https://registry.yarnpkg.com/@formatjs/fast-memoize/-/fast-memoize-1.2.4.tgz#4b5ddce9eb7803ff0bd4052387672151a8b7f8a0"
integrity sha512-9ARYoLR8AEzXvj2nYrOVHY/h1dDMDWGTnKDLXSISF1uoPakSmfcZuSqjiqZX2wRkEUimPxdwTu/agyozBtZRHA==
dependencies:
tslib "2.4.0"
"@formatjs/icu-messageformat-parser@2.1.2":
version "2.1.2"
resolved "https://registry.yarnpkg.com/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.1.2.tgz#9ff4dfc4f1ed613cca2c188b29f299854b86b7f8"
integrity sha512-FYQ2pkgbDJxJlst/U5MU2H7+bR9HrZ4x8J4c0etrya24pJzQxYguVlAhc2S6NoEImlQ2LmIIGsURaBQu9bCtew==
"@formatjs/icu-messageformat-parser@2.1.3":
version "2.1.3"
resolved "https://registry.yarnpkg.com/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.1.3.tgz#d228ac26f22630689a1263e83192227f1d085bd3"
integrity sha512-hsdAn1dXcujW/G8DHw0iiIy7357pw10yOulCrL6xrQOKJAxT7m7EgpG0Hm1OW9xqaLEzqWyE/jA2AGVnOCaCQw==
dependencies:
"@formatjs/ecma402-abstract" "1.11.6"
"@formatjs/icu-skeleton-parser" "1.3.8"
"@formatjs/ecma402-abstract" "1.11.7"
"@formatjs/icu-skeleton-parser" "1.3.9"
tslib "2.4.0"
"@formatjs/icu-skeleton-parser@1.3.8":
version "1.3.8"
resolved "https://registry.yarnpkg.com/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.3.8.tgz#3d150fcb45b4867c1db84237ca1f1f701d598918"
integrity sha512-CVdsPMs/KvrIDKhMDw8bSq/Zst2bhdn/bTUfVCHi/c/bj462lChIJmW/JP/FaGKgZzdG8slGyVIFLonpG4uqFA==
"@formatjs/icu-skeleton-parser@1.3.9":
version "1.3.9"
resolved "https://registry.yarnpkg.com/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.3.9.tgz#149badc16ffd15dd928f8047ae21aa9136e0ea73"
integrity sha512-s9THwwhiiSzbGSk73FP6Ur2MBwEj1vfgYDHKa5FiXGQMfYzdRdRvyH1dgqNgSFJPB6PM3DKtkloJLjpqpSDNUg==
dependencies:
"@formatjs/ecma402-abstract" "1.11.6"
"@formatjs/ecma402-abstract" "1.11.7"
tslib "2.4.0"
"@formatjs/intl-datetimeformat@^6.0.1":
version "6.0.1"
resolved "https://registry.yarnpkg.com/@formatjs/intl-datetimeformat/-/intl-datetimeformat-6.0.1.tgz#cd107a2d3f85dd8e8103fc967e40618f65c7c8ad"
integrity sha512-j/cDALY35GENMBEvcTUaRO97VCZn8CaocbNl7WLIl2HC9jdcdnMeVTY38dYLM6lfSoarIiNAWRE3oTQKOh086g==
"@formatjs/intl-datetimeformat@^6.0.2":
version "6.0.2"
resolved "https://registry.yarnpkg.com/@formatjs/intl-datetimeformat/-/intl-datetimeformat-6.0.2.tgz#a1d7da8b660af48b2fd152b0d745bec14a0f15ad"
integrity sha512-nKJmSmyTYAN5NaHRocebuoyPpd+m2HWiXgwY0CvMfFjv0QIeDVNAtECBzaDPf57bTKFjaDt1t1/5Z0D0r7+r4Q==
dependencies:
"@formatjs/ecma402-abstract" "1.11.6"
"@formatjs/intl-localematcher" "0.2.27"
"@formatjs/ecma402-abstract" "1.11.7"
"@formatjs/intl-localematcher" "0.2.28"
tslib "2.4.0"
"@formatjs/intl-displaynames@6.0.1":
version "6.0.1"
resolved "https://registry.yarnpkg.com/@formatjs/intl-displaynames/-/intl-displaynames-6.0.1.tgz#b4bf890d440a19da03203a5e7f1bff1460b2b859"
integrity sha512-KPfB+mOIzcptAzpNIciDc+rK4kRCg5aTCXPr5feIWNxvd/H1Wr3cVVDV2YGdPn+Woo9b1K4cnUi3b1IvBFQ/5g==
"@formatjs/intl-displaynames@6.0.2":
version "6.0.2"
resolved "https://registry.yarnpkg.com/@formatjs/intl-displaynames/-/intl-displaynames-6.0.2.tgz#f6349b5c75fd9ecc7c77c7e73101daee5dc69e3a"
integrity sha512-h9Id/6vbSHpARHKMVsjWag3KMZJpop9s67CZTd+AMxhjHb5xDG2b5rlSRJKx/UdIDQ+GzESK7a4fv32yylG3cw==
dependencies:
"@formatjs/ecma402-abstract" "1.11.6"
"@formatjs/intl-localematcher" "0.2.27"
"@formatjs/ecma402-abstract" "1.11.7"
"@formatjs/intl-localematcher" "0.2.28"
tslib "2.4.0"
"@formatjs/intl-getcanonicallocales@2.0.1", "@formatjs/intl-getcanonicallocales@^2.0.1":
version "2.0.1"
resolved "https://registry.yarnpkg.com/@formatjs/intl-getcanonicallocales/-/intl-getcanonicallocales-2.0.1.tgz#da2b7749a99d4347e2dacef103e3044eed3ef915"
integrity sha512-MDbYGYdBdhxNBNm5trIhYZWRu2yC6Le+2IQ0bzus7sY7AEHswz0TcyekfYywiV3QF4E7x0YtJDIKTi6oH14NRg==
"@formatjs/intl-getcanonicallocales@2.0.2", "@formatjs/intl-getcanonicallocales@^2.0.2":
version "2.0.2"
resolved "https://registry.yarnpkg.com/@formatjs/intl-getcanonicallocales/-/intl-getcanonicallocales-2.0.2.tgz#e9fd5d2215ce0dba4d8611b37ac87c5fae60a1a7"
integrity sha512-nMkPblAjgK49ORueVhyuKJDXOCq8at2h9Xf0lqabBZylaX3xLEeuAW2eMXuwJ/ascYYQnwuxeukd/qlzDkuJzQ==
dependencies:
tslib "2.4.0"
"@formatjs/intl-listformat@7.0.1":
version "7.0.1"
resolved "https://registry.yarnpkg.com/@formatjs/intl-listformat/-/intl-listformat-7.0.1.tgz#9b7f0d46a6eb04138dde5d57c898222315f87334"
integrity sha512-sgE4B9+mu3ZF77vhZB0tR8O3evvcPA//WbA/8UJ21XOrSzfY6RXhSbvDfSd7Y5iEeBu+2C+5YxDuAwLnvq2SnQ==
"@formatjs/intl-listformat@7.0.2":
version "7.0.2"
resolved "https://registry.yarnpkg.com/@formatjs/intl-listformat/-/intl-listformat-7.0.2.tgz#c07d370c9171dfc86c163addbfcb08f67ae26215"
integrity sha512-K+HXrYIvEcAH/dS8XXnSHQYC/z4w0eHjPlDx43HOoDY87/xV7rpHxFVXWXTgwLYC6iAPUO72Y/AaT9iq873juw==
dependencies:
"@formatjs/ecma402-abstract" "1.11.6"
"@formatjs/intl-localematcher" "0.2.27"
"@formatjs/ecma402-abstract" "1.11.7"
"@formatjs/intl-localematcher" "0.2.28"
tslib "2.4.0"
"@formatjs/intl-locale@^3.0.1":
version "3.0.1"
resolved "https://registry.yarnpkg.com/@formatjs/intl-locale/-/intl-locale-3.0.1.tgz#ec6eb1d989b4ccb4f3669a03ac84c087dc686398"
integrity sha512-z3WXS9SxHUBi6JqvWgEPCvlSZF2Hw6jj272J/YqIVqBahViReUHjj3/2kVsTBndTe2HVkkaxURJNE+yd1MFqFw==
"@formatjs/intl-locale@^3.0.2":
version "3.0.2"
resolved "https://registry.yarnpkg.com/@formatjs/intl-locale/-/intl-locale-3.0.2.tgz#d0055c8672e6f27fc09dff15f0f7def32ce1bcf1"
integrity sha512-ZnBrrm0nDup6AzRGGBp7Gt5Ow4lLwtKdb3i4kCdjUr8drZin1WJUP63sxtotEGHene9c34mA5kifBeq7hHWS9g==
dependencies:
"@formatjs/ecma402-abstract" "1.11.6"
"@formatjs/intl-getcanonicallocales" "2.0.1"
"@formatjs/ecma402-abstract" "1.11.7"
"@formatjs/intl-getcanonicallocales" "2.0.2"
tslib "2.4.0"
"@formatjs/intl-localematcher@0.2.27":
version "0.2.27"
resolved "https://registry.yarnpkg.com/@formatjs/intl-localematcher/-/intl-localematcher-0.2.27.tgz#8a837ddca17a55d86e4ab68bcbb25b15f547d61d"
integrity sha512-XHYcVas2ebDTh3VtfdluvbTjqyMUHqFHARnuJo5KYF/0MKOTmozVSK7PJGnu1IEHdmRdTWuG6TB+2RnkasaxVw==
"@formatjs/intl-localematcher@0.2.28":
version "0.2.28"
resolved "https://registry.yarnpkg.com/@formatjs/intl-localematcher/-/intl-localematcher-0.2.28.tgz#412ea7fefbfc7ed33cd6b43aa304fc14d816e564"
integrity sha512-FLsc6Gifs1np/8HnCn/7Q+lHMmenrD5fuDhRT82yj0gi9O19kfaFwjQUw1gZsyILuRyT93GuzdifHj7TKRhBcw==
dependencies:
tslib "2.4.0"
"@formatjs/intl-numberformat@^8.0.1":
version "8.0.1"
resolved "https://registry.yarnpkg.com/@formatjs/intl-numberformat/-/intl-numberformat-8.0.1.tgz#f5a9269480c869c3ec35c4689074c32ae6240a10"
integrity sha512-g0ce2KBbYFhbArsduHBB/QsGw4iVLEhbIYOT6Ra44/iEMGQ1aFLvSKiGYZatCZF7CgK0DTwmZ1RFy7vrs7w24w==
"@formatjs/intl-numberformat@^8.0.2":
version "8.0.2"
resolved "https://registry.yarnpkg.com/@formatjs/intl-numberformat/-/intl-numberformat-8.0.2.tgz#35bacb5d55441b1d3f541c796057f87c165b63ef"
integrity sha512-Fw7Y8Iqm/d5aF+3HkTCbtOxTHIh1DonohPGUEJYHqKVftx12ybtxMLpEGbArRnWdDC6+lVTmPTlHIHcf/vyLTA==
dependencies:
"@formatjs/ecma402-abstract" "1.11.6"
"@formatjs/intl-localematcher" "0.2.27"
"@formatjs/ecma402-abstract" "1.11.7"
"@formatjs/intl-localematcher" "0.2.28"
tslib "2.4.0"
"@formatjs/intl-pluralrules@^5.0.1":
version "5.0.1"
resolved "https://registry.yarnpkg.com/@formatjs/intl-pluralrules/-/intl-pluralrules-5.0.1.tgz#23bce3c680791e25dec066c358d7aad81376cff7"
integrity sha512-YOgsZ4t2rrlfT53BC4AtxOrmG5i4yp75L/9HJOcH7leheqAT2AWc8GzimwGCXXzaHqrne5p4uhx4ikfzTyLbpw==
"@formatjs/intl-pluralrules@^5.0.2":
version "5.0.2"
resolved "https://registry.yarnpkg.com/@formatjs/intl-pluralrules/-/intl-pluralrules-5.0.2.tgz#406a60d8b8296fc0a1c2d59801e6decc236eed89"
integrity sha512-wXIW26RMLOSKpK5jqX0fZYHqPb1mhR41+FSL9x/6B/qe3zb+xkq5FuAoKLb4j/+rNehy7uVOl963JXsbSexy0A==
dependencies:
"@formatjs/ecma402-abstract" "1.11.6"
"@formatjs/intl-localematcher" "0.2.27"
"@formatjs/ecma402-abstract" "1.11.7"
"@formatjs/intl-localematcher" "0.2.28"
tslib "2.4.0"
"@formatjs/intl-relativetimeformat@^11.0.1":
version "11.0.1"
resolved "https://registry.yarnpkg.com/@formatjs/intl-relativetimeformat/-/intl-relativetimeformat-11.0.1.tgz#4257938077881d069d134410971596ddb0045b27"
integrity sha512-+R7CReF9mgoCblWLsO6RPRCN47VLhi8TaK6yNL5bH7aQXcNk8m/4UZjz7taUVXBoJmqfxqzIh5pa8n9V+JtNgg==
"@formatjs/intl-relativetimeformat@^11.0.2":
version "11.0.2"
resolved "https://registry.yarnpkg.com/@formatjs/intl-relativetimeformat/-/intl-relativetimeformat-11.0.2.tgz#c4c79dcfc31d6e9cc2870efb4806367b67101b56"
integrity sha512-jZUv3z/NIPTEZRMDvLrQBC9tWpo+gAMmFNi9Pp8VksRGd87j0ndgmPSUuRJQX6OQxnXhlCGltIxpwX2j+289MQ==
dependencies:
"@formatjs/ecma402-abstract" "1.11.6"
"@formatjs/intl-localematcher" "0.2.27"
"@formatjs/ecma402-abstract" "1.11.7"
"@formatjs/intl-localematcher" "0.2.28"
tslib "2.4.0"
"@formatjs/intl@2.2.5":
version "2.2.5"
resolved "https://registry.yarnpkg.com/@formatjs/intl/-/intl-2.2.5.tgz#888f42750eacdcfb836a062eb889cb429d13f7f7"
integrity sha512-b0+5Bjsl3KDAII2frBPRO7ck9Ec/xqZ25BoiJATJhe//e4n6FOvVXk5QKYwBQPDt3JPu/Qa14oqHDiZlZmVdSg==
"@formatjs/intl@2.3.0":
version "2.3.0"
resolved "https://registry.yarnpkg.com/@formatjs/intl/-/intl-2.3.0.tgz#848edf81c95d608757662b3feada0eb2dacc5424"
integrity sha512-mE8zGqP+Flrd8tS3AsdvSucnblqwR5gsGM4Bd5711abkabrz52F2TDrU88rVvVfCdHV4dFHFYEmUBVZZ4pATtg==
dependencies:
"@formatjs/ecma402-abstract" "1.11.6"
"@formatjs/fast-memoize" "1.2.3"
"@formatjs/icu-messageformat-parser" "2.1.2"
"@formatjs/intl-displaynames" "6.0.1"
"@formatjs/intl-listformat" "7.0.1"
intl-messageformat "10.0.1"
"@formatjs/ecma402-abstract" "1.11.7"
"@formatjs/fast-memoize" "1.2.4"
"@formatjs/icu-messageformat-parser" "2.1.3"
"@formatjs/intl-displaynames" "6.0.2"
"@formatjs/intl-listformat" "7.0.2"
intl-messageformat "10.1.0"
tslib "2.4.0"
"@gar/promisify@^1.0.1":
@ -2081,6 +2039,13 @@
"@jridgewell/resolve-uri" "^3.0.3"
"@jridgewell/sourcemap-codec" "^1.4.10"
"@mattermost/react-native-paste-input@^0.4.2":
version "0.4.2"
resolved "https://registry.yarnpkg.com/@mattermost/react-native-paste-input/-/react-native-paste-input-0.4.2.tgz#f08f94a921fee89d84a14e2acdbf1905c13ea427"
integrity sha512-iHwJkQ3iODVniEi8Qsr8FNOLTAIksjmxcsbbBU4SPiduNKEotOy/qAgexiW1Vsp9jO/WJnQ7Gn59bFNpxVGzmQ==
dependencies:
deprecated-react-native-prop-types "^2.3.0"
"@neverdull-agency/expo-unlimited-secure-store@1.0.10":
version "1.0.10"
resolved "https://registry.yarnpkg.com/@neverdull-agency/expo-unlimited-secure-store/-/expo-unlimited-secure-store-1.0.10.tgz#1e78b502257b267fc918a85eaa41aa01a46d2007"
@ -2125,10 +2090,10 @@
mkdirp "^1.0.4"
rimraf "^3.0.2"
"@react-native-async-storage/async-storage@1.17.4":
version "1.17.4"
resolved "https://registry.yarnpkg.com/@react-native-async-storage/async-storage/-/async-storage-1.17.4.tgz#f03837565f085c487bc9e8a9f1862c3e4e3f8fb6"
integrity sha512-c6GglKBRaWkjNyYd0FM8f0/neQEcwQom4MjZNqYSsIz55LcddJb7W8GM/n2dkzZ0JnaMylMX3+Vo+fpmkFEGTQ==
"@react-native-async-storage/async-storage@1.17.6":
version "1.17.6"
resolved "https://registry.yarnpkg.com/@react-native-async-storage/async-storage/-/async-storage-1.17.6.tgz#ddb3520d051f71698c8a0e79e8959a7bf6d9f43b"
integrity sha512-XXnoheQI3vQTQmjphdXNLTmtiKZeRqvI8kPQ25X5Eae7nZjdYEEGN+0z8N2qyelbUIQwKgmW0aagJk56q7DyNg==
dependencies:
merge-options "^3.0.4"
@ -2313,10 +2278,10 @@
sudo-prompt "^9.0.0"
wcwidth "^1.0.1"
"@react-native-community/netinfo@8.3.0":
version "8.3.0"
resolved "https://registry.yarnpkg.com/@react-native-community/netinfo/-/netinfo-8.3.0.tgz#e3d19381c7d9f885b44ea1785ed892bc39c4b0f1"
integrity sha512-VlmjD7Vg1BacbNhwuJCel1eeD8N2Ps6BEcZe9qoSoeIptpCbC86o4ZqD0meSjJzioKSvgalrkmPgMaVYsVipKw==
"@react-native-community/netinfo@9.0.0":
version "9.0.0"
resolved "https://registry.yarnpkg.com/@react-native-community/netinfo/-/netinfo-9.0.0.tgz#d5f64158af5c0772b11bdf0aaba569f944b99589"
integrity sha512-oodeVNdL0IT/Fv0cpL7CdxnnESNKiiAzf29yhvGSJBvuFmyHxvmYOtBIX27HWG9tQU8Yzeh+MyQcKBCNmkva+Q==
"@react-native-community/segmented-control@2.2.2":
version "2.2.2"
@ -2539,10 +2504,10 @@
"@sentry/types" "6.19.2"
tslib "^1.9.3"
"@sentry/react-native@3.4.2":
version "3.4.2"
resolved "https://registry.yarnpkg.com/@sentry/react-native/-/react-native-3.4.2.tgz#9d8f60411d73f897e2bc1facafdf3e59e686627d"
integrity sha512-rty+Mj77wBslxZ91dUlMwlvTtDpYpYehXMlrP8+AG/qRf2b1yFfLmtehGIwsZrTETp84WIL5evbKMd5nzYO+VQ==
"@sentry/react-native@3.4.3":
version "3.4.3"
resolved "https://registry.yarnpkg.com/@sentry/react-native/-/react-native-3.4.3.tgz#00c90f87b5c8e24bf4698656a0994cf38b99dfba"
integrity sha512-q1m/KaPWkv9/nXMXo5S5VzZNngC9gxJrtfPnMQPCXzLwLiGMlc2FBMBDJmZGzeSkQMr163Xb+2UYZEPqCUvdvg==
dependencies:
"@sentry/browser" "6.19.2"
"@sentry/cli" "^1.74.2"
@ -2758,10 +2723,10 @@
resolved "https://registry.yarnpkg.com/@types/react-native-share-menu/-/react-native-share-menu-5.0.2.tgz#c9c8854a3d091cdb046df22dafe4a92332b322f3"
integrity sha512-Qa9DGfL6Bvng2DXgCK0fFzdi9SJMGfs06MLSkCfSXBCGKlFLzSHCsXztvXlCCChn3dQArFHyz/uRUN3Sbt6LtQ==
"@types/react-native@0.67.7":
version "0.67.7"
resolved "https://registry.yarnpkg.com/@types/react-native/-/react-native-0.67.7.tgz#903b7d56fb6a95ca0de897f2925c04ee0d4adee3"
integrity sha512-G7vi9vE226diNNXVNbIa8HH/wPxMWHTHkn9iQtQSezaWzO5WNsKZZW2/TOzCehtBDVx4MZieTs6GsdtpBBttMA==
"@types/react-native@0.67.8":
version "0.67.8"
resolved "https://registry.yarnpkg.com/@types/react-native/-/react-native-0.67.8.tgz#edaaa0527b835fffbfc34c09874722b6e4a4d1f3"
integrity sha512-xA8rYiTHvO6RoZv/LFnmEeqRuhA2y34mGB8zX3bGHe/pCt9jEStUPyUO4q1KcDc9GiGIOBD8ArfRtThprAjSfQ==
dependencies:
"@types/react" "*"
@ -4463,13 +4428,6 @@ expo-image-loader@~3.2.0:
resolved "https://registry.yarnpkg.com/expo-image-loader/-/expo-image-loader-3.2.0.tgz#d98b021660edef7243f7c5ec011b8d0545626d41"
integrity sha512-LU3Q2prn64/HxdToDmxgMIRXS1ZvD9Q3iCxRVTZn1fPQNNDciIQFE5okaa74Ogx20DFHs90r6WoUd7w9Af1OGQ==
expo-image-manipulator@10.3.1:
version "10.3.1"
resolved "https://registry.yarnpkg.com/expo-image-manipulator/-/expo-image-manipulator-10.3.1.tgz#e16dd76a550c7f5d653a2a666f26429eba311a6b"
integrity sha512-D08dMD6MerxBu23DmCIhurySQih+eLP7VxKvY5mWqYz9payjDOS1cAGs3HvXPrEDusPQFQ0uIfqc+oqeMNFBIA==
dependencies:
expo-image-loader "~3.2.0"
expo-image-picker@13.1.1:
version "13.1.1"
resolved "https://registry.yarnpkg.com/expo-image-picker/-/expo-image-picker-13.1.1.tgz#e039bf9748ccb7b89370ff2969c3ef07cc949192"
@ -4599,10 +4557,10 @@ expo-updates-interface@~0.6.0:
resolved "https://registry.yarnpkg.com/expo-updates-interface/-/expo-updates-interface-0.6.0.tgz#cef5250106e59572afdfcf245c534754c8c844ba"
integrity sha512-oQcGTsE8mSkSxENPlWjZPGoJpt3RFDNPuO5d8shBDxBb4ZNH/W2ojavSFQMaLbNMoS0bYQQhzbRNEIBd306QMg==
expo-updates@0.13.1:
version "0.13.1"
resolved "https://registry.yarnpkg.com/expo-updates/-/expo-updates-0.13.1.tgz#ffed4216eda6afbc15f06399115a5b9e649102a6"
integrity sha512-6GTqgAco3da/rk0/AvDRFmrWxEIxlmIs7vvoCWWxyvifLBlQAaU08+AdZH5RwdN3aUaIep70pnqlc67xe/y0og==
expo-updates@0.13.2:
version "0.13.2"
resolved "https://registry.yarnpkg.com/expo-updates/-/expo-updates-0.13.2.tgz#c96d664d8a97eb3af6841e46a137962b8bf06361"
integrity sha512-2ZthmyxYuN/c71y2oLNz9fdVXLf7ggxAw3Tfy+kwtGOTKNyOf/YoG+SwX7a0v+jZTJnuorEj5FuNf3wrniQ7+w==
dependencies:
"@expo/code-signing-certificates" "0.0.1"
"@expo/config" "^6.0.14"
@ -4622,20 +4580,27 @@ expo-video-thumbnails@6.3.0:
resolved "https://registry.yarnpkg.com/expo-video-thumbnails/-/expo-video-thumbnails-6.3.0.tgz#07daa798ad175242fef4e62c8f2040b11606a41b"
integrity sha512-oVy9XlzNxnpXFDz3FiWOrMOBWmYtZrNYnXc3XaVyj8ayRqwDNvW4P95kQeUhB04uwaMDOm4vIxc5SQxDAyxPGg==
expo-web-browser@10.2.0, expo-web-browser@~10.2.0:
expo-web-browser@10.2.1:
version "10.2.1"
resolved "https://registry.yarnpkg.com/expo-web-browser/-/expo-web-browser-10.2.1.tgz#4bd9214f7aefcd1af6ea26ae068a0e3e25a1e0bd"
integrity sha512-om34EL7OX5ouBM/hq2PrjHDLKmjVhAy+1H7YqRY6nS8dWsewnLFdLq4d8GPwWQBYb6kHKYVzwFRj+WLTfpAOBQ==
dependencies:
compare-urls "^2.0.0"
expo-web-browser@~10.2.0:
version "10.2.0"
resolved "https://registry.yarnpkg.com/expo-web-browser/-/expo-web-browser-10.2.0.tgz#e37450e5a0dd2eb1cf5ea5511589ed207da8aafe"
integrity sha512-Y8LCiktTUdPQVgO5BR6uHFU8FGu/8ysT2ynpO0WFeD4odX8JBX5JwQ9HokEa3XvRJNpefgQmXETDjzkMznXMvQ==
dependencies:
compare-urls "^2.0.0"
expo@45.0.4:
version "45.0.4"
resolved "https://registry.yarnpkg.com/expo/-/expo-45.0.4.tgz#a34d250605d5603e3cea4acf169aedd288df477b"
integrity sha512-S/6rwmgG+1cyHP1hCmylk9FLnavUWd/haWgVc8sUNiavaMBu/vCjeLUESFVJdYRKKN9i+avSzHdAVdKytNYo6A==
expo@45.0.5:
version "45.0.5"
resolved "https://registry.yarnpkg.com/expo/-/expo-45.0.5.tgz#ff99ad44a59ffabf473c43abbff35d17b10862fe"
integrity sha512-ND+Fo/iLZK1ubMvPFzraIQBvtGL7a4ZHGIP8N1PjcOtTGrCc6X7IWyLkfPMAck2yhd80ZTbos8vTU3SAUuBcJw==
dependencies:
"@babel/runtime" "^7.14.0"
"@expo/cli" "0.1.4"
"@expo/cli" "0.1.5"
"@expo/vector-icons" "^13.0.0"
babel-preset-expo "~9.1.0"
cross-spawn "^6.0.5"
@ -5406,14 +5371,14 @@ internal-slot@^1.0.3:
has "^1.0.3"
side-channel "^1.0.4"
intl-messageformat@10.0.1:
version "10.0.1"
resolved "https://registry.yarnpkg.com/intl-messageformat/-/intl-messageformat-10.0.1.tgz#dae7ae81a477e92ea8691dd73c60d5eb5003f866"
integrity sha512-oZWDsNbauuWmPd98+zLEfNojuJkBdVpEWIcWQVCTxSJrhag2/czZnwKBsYa8NcVf4t0fWo0k77v+CBCudKEcjw==
intl-messageformat@10.1.0:
version "10.1.0"
resolved "https://registry.yarnpkg.com/intl-messageformat/-/intl-messageformat-10.1.0.tgz#ffbbcbf1068af8466ad5497f78c30c3d96ef5505"
integrity sha512-diGMDv9Zo2Mggf6AkJszq/BIR5+rarkwcr4g5JGgREwbwAHY9hR/dYd8FbIgQx2RTxhJsABfAWCiENFLbaTZjg==
dependencies:
"@formatjs/ecma402-abstract" "1.11.6"
"@formatjs/fast-memoize" "1.2.3"
"@formatjs/icu-messageformat-parser" "2.1.2"
"@formatjs/ecma402-abstract" "1.11.7"
"@formatjs/fast-memoize" "1.2.4"
"@formatjs/icu-messageformat-parser" "2.1.3"
tslib "2.4.0"
invariant@*, invariant@^2.2.4:
@ -7449,20 +7414,20 @@ react-i18next@11.17.0:
html-escaper "^2.0.2"
html-parse-stringify "^3.0.1"
react-intl@^6.0.3:
version "6.0.3"
resolved "https://registry.yarnpkg.com/react-intl/-/react-intl-6.0.3.tgz#eb5857f2fd525c83255bf6c8339562a7fea9f970"
integrity sha512-c6wHOnYjOBTbqIt+6TVV2QwdKrqYiFP713tMsw/sJWYgzfaRTjsvGkcxOXhX3SoBrqbUhKTEzjdniuwpAN/qKA==
react-intl@^6.0.4:
version "6.0.4"
resolved "https://registry.yarnpkg.com/react-intl/-/react-intl-6.0.4.tgz#635d7639aa7b70e837c75796535cf1d534016acf"
integrity sha512-eBIP4QuFOdr67+ZmNOA7WGzJ6dj0qgsGQbx3phzcel2B0kVLvfojTJuvYiFuLgbZTrRJMjHwYJZO5zsmibsfug==
dependencies:
"@formatjs/ecma402-abstract" "1.11.6"
"@formatjs/icu-messageformat-parser" "2.1.2"
"@formatjs/intl" "2.2.5"
"@formatjs/intl-displaynames" "6.0.1"
"@formatjs/intl-listformat" "7.0.1"
"@formatjs/ecma402-abstract" "1.11.7"
"@formatjs/icu-messageformat-parser" "2.1.3"
"@formatjs/intl" "2.3.0"
"@formatjs/intl-displaynames" "6.0.2"
"@formatjs/intl-listformat" "7.0.2"
"@types/hoist-non-react-statics" "^3.3.1"
"@types/react" "16 || 17 || 18"
hoist-non-react-statics "^3.3.2"
intl-messageformat "10.0.1"
intl-messageformat "10.1.0"
tslib "2.4.0"
"react-is@^16.12.0 || ^17.0.0", react-is@^17.0.1:
@ -7510,6 +7475,10 @@ react-native-codegen@^0.0.17:
jscodeshift "^0.13.1"
nullthrows "^1.1.1"
react-native-context-menu-view@xmflsct/react-native-context-menu-view:
version "1.5.4"
resolved "https://codeload.github.com/xmflsct/react-native-context-menu-view/tar.gz/bff5773d318970cd67b5cf114d4bec1e200178eb"
react-native-fast-image@8.5.11:
version "8.5.11"
resolved "https://registry.yarnpkg.com/react-native-fast-image/-/react-native-fast-image-8.5.11.tgz#e3dc969d0e4e8df026646bf18194465aa55cbc2b"
@ -7552,10 +7521,10 @@ react-native-htmlview@0.16.0:
entities "^1.1.1"
htmlparser2-without-node-native "^3.9.2"
react-native-image-keyboard@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/react-native-image-keyboard/-/react-native-image-keyboard-2.2.0.tgz#dc3f90aaaac20a79315015a330e62e85547e0674"
integrity sha512-2JzKCXMBYiIUR6OtGV7F/lEWqwIU/6HS1CGOBulxwYNxoa7m1nZk45hNEZPP8SA5yE2pLNXEQePjc3WGAtXo3w==
react-native-image-crop-picker@^0.37.3:
version "0.37.3"
resolved "https://registry.yarnpkg.com/react-native-image-crop-picker/-/react-native-image-crop-picker-0.37.3.tgz#f260e40b6a6ba8e98f4db3dde25a8f09e0936385"
integrity sha512-ih+0pWWRUNEFQyaHwGbH9rqJNOb7EBYMwKJhTY0VmsKIA9E+usfwMmQXAFIfOnee7fTn0A2vOXkBCPQZwyvnQw==
react-native-iphone-x-helper@^1.3.1:
version "1.3.1"
@ -7580,10 +7549,10 @@ react-native-reanimated@2.8.0:
setimmediate "^1.0.5"
string-hash-64 "^1.0.3"
react-native-safe-area-context@4.2.5:
version "4.2.5"
resolved "https://registry.yarnpkg.com/react-native-safe-area-context/-/react-native-safe-area-context-4.2.5.tgz#23006dc1a398bb825d7d795c27f1c46119efe8a5"
integrity sha512-nUil2de1gk/8ZB9IzIxFyGCiKeAYcHzJb/Tks2NzSkev1qH4MNR05DWYDSmW6vLT+y4mospLVyG/H5dyUd+KQQ==
react-native-safe-area-context@4.3.1:
version "4.3.1"
resolved "https://registry.yarnpkg.com/react-native-safe-area-context/-/react-native-safe-area-context-4.3.1.tgz#5cf97b25b395e0d09bc1f828920cd7da0d792ade"
integrity sha512-cEr7fknJCToTrSyDCVNg0GRdRMhyLeQa2NZwVCuzEQcWedOw/59ExomjmzCE4rxrKXs6OJbyfNtFRNyewDaHuA==
react-native-screens@3.13.1:
version "3.13.1"
@ -8072,13 +8041,13 @@ send@0.18.0:
range-parser "~1.2.1"
statuses "2.0.1"
sentry-expo@4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/sentry-expo/-/sentry-expo-4.1.1.tgz#ff0f494e191a3c43f94043e26ffa18aa4b6059c6"
integrity sha512-Nc9w5znsIBUYZ/rpM1uDEeS2KKc1f+tK4FJ8pA0emJjpBQO4cDJHXb0zyDtFYN7T1+vdEZ0cSe7kwgoPSw5V6g==
sentry-expo@4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/sentry-expo/-/sentry-expo-4.2.0.tgz#8dc626b39cc1538c1284cc383b9d18c3252859c4"
integrity sha512-UL+i+kLDmB27kmY/BvkhY7aBqAxnVdy+IVg4jMX20V/iAgA1HBk2szf2gCt9vHDja6Q0HzgkdC91xiQ89jKiOw==
dependencies:
"@expo/config-plugins" "^2.0.2"
"@expo/config-types" "^41.0.0"
"@expo/config-plugins" "~4.1.4"
"@expo/config-types" "^45.0.0"
"@expo/spawn-async" "^1.2.8"
"@sentry/browser" "^6.12.0"
"@sentry/integrations" "^6.12.0"
@ -9171,7 +9140,7 @@ xml-js@^1.6.11:
dependencies:
sax "^1.2.4"
xml2js@0.4.23, xml2js@^0.4.23:
xml2js@0.4.23:
version "0.4.23"
resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66"
integrity sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==
@ -9201,11 +9170,6 @@ xmldoc@^1.1.2:
dependencies:
sax "^1.2.1"
xmldom@~0.5.0:
version "0.5.0"
resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.5.0.tgz#193cb96b84aa3486127ea6272c4596354cb4962e"
integrity sha512-Foaj5FXVzgn7xFzsKeNIde9g6aFBxTPi37iwsno8QvApmtg7KYrr+OPyRHcJF7dud2a5nGRBXK3n0dL62Gf7PA==
xtend@~4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"