From 7c48b31241140e5fd321eda7aca3629446bdde2d Mon Sep 17 00:00:00 2001 From: Zhiyuan Zheng Date: Fri, 3 Jun 2022 23:44:27 +0200 Subject: [PATCH 01/31] Bump packages --- ios/Podfile.lock | 20 +++++----- package.json | 18 ++++----- yarn.lock | 102 ++++++++++++++--------------------------------- 3 files changed, 50 insertions(+), 90 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index d455be6c..41a66d7e 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -55,7 +55,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 +67,7 @@ PODS: - EXStoreReview (5.2.0): - ExpoModulesCore - EXStructuredHeaders (2.2.1) - - EXUpdates (0.13.1): + - EXUpdates (0.13.2): - ASN1Decoder (~> 1.8) - EASClient - EXManifests @@ -417,7 +417,7 @@ PODS: - React-Core - react-native-image-keyboard (2.2.0): - React - - react-native-netinfo (8.3.0): + - react-native-netinfo (9.0.0): - React-Core - react-native-pager-view (5.4.11): - React-Core @@ -523,7 +523,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 @@ -561,7 +561,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): @@ -875,13 +875,13 @@ SPEC CHECKSUMS: 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 @@ -919,7 +919,7 @@ SPEC CHECKSUMS: react-native-blurhash: add4df9a937b4e021a24bc67a0714f13e0bd40b7 react-native-cameraroll: 2957f2bce63ae896a848fbe0d5352c1bd4d20866 react-native-image-keyboard: adbf5996b8592a7d8cb8d3e431a9607f9cf3b270 - react-native-netinfo: 3671b091c4843fda5e153612866ef4024b8f5d62 + react-native-netinfo: 5b664b2945a8f02102b296f0f812bddd6827ed9c react-native-pager-view: 7f00d63688f7df9fad86dfb0154814419cc5eb8d react-native-safe-area-context: ebf8c413eb8b5f7c392a036a315eb7b46b96845f react-native-segmented-control: 65df6cd0619b780b3843d574a72d4c7cec396097 @@ -935,12 +935,12 @@ SPEC CHECKSUMS: React-RCTVibration: 79040b92bfa9c3c2d2cb4f57e981164ec7ab9374 React-runtimeexecutor: b960b687d2dfef0d3761fbb187e01812ebab8b23 ReactCommon: 095366164a276d91ea704ce53cb03825c487a3f2 - RNCAsyncStorage: 9367a646dc24e3ab7b6874d79bc1bfd0832dce58 + RNCAsyncStorage: 466b9df1a14bccda91da86e0b7d9a345d78e1673 RNFastImage: 945abf54742505d790d9024d230c69b1e866bc88 RNGestureHandler: 61628a2c859172551aa2100d3e73d1e57878392f RNReanimated: 64573e25e078ae6bec03b891586d50b9ec284393 RNScreens: 40a2cb40a02a609938137a1e0acfbf8fc9eebf19 - RNSentry: 2cd1daa124b0d9fd0dfc2cb6094fdd168cb579bc + RNSentry: 85f6525b5fe8d2ada065858026b338605b3c09da RNShareMenu: c69282e50ac439737a86949a55c7b023b90027c8 RNSVG: 302bfc9905bd8122f08966dc2ce2d07b7b52b9f8 SDWebImage: 0905f1b7760fc8ac4198cae0036600d67478751e diff --git a/package.json b/package.json index 209e3464..b362046b 100644 --- a/package.json +++ b/package.json @@ -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", @@ -33,17 +33,17 @@ "@formatjs/intl-pluralrules": "^5.0.1", "@formatjs/intl-relativetimeformat": "^11.0.1", "@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", @@ -65,9 +65,9 @@ "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", @@ -97,7 +97,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" }, diff --git a/yarn.lock b/yarn.lock index 2171b04e..833f1382 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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" @@ -2125,10 +2083,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 +2271,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 +2497,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" @@ -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,7 +4580,14 @@ 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== @@ -8072,13 +8037,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 +9136,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 +9166,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" From fc8fdec12fc39fa4b31bd70d20109b90bb4a837b Mon Sep 17 00:00:00 2001 From: Zhiyuan Zheng Date: Sun, 5 Jun 2022 17:58:18 +0200 Subject: [PATCH 02/31] Fixed #108 --- ios/Podfile.lock | 23 +- ios/en.lproj/InfoPlist.strings | 1 - ios/tooot.xcodeproj/project.pbxproj | 4 + ios/tooot/Info.plist | 2 - ios/zh-Hans.lproj/InfoPlist.strings | 1 - package.json | 2 +- src/@types/app.d.ts | 7 - src/Screens.tsx | 156 ++++------ src/components/mediaSelector.ts | 270 ++++++++++-------- src/i18n/en/components/mediaSelector.json | 20 +- src/screens/Compose.tsx | 25 +- .../Compose/Root/Footer/addAttachment.ts | 44 +-- src/screens/Compose/Root/Header/TextInput.tsx | 6 +- src/screens/Compose/utils/types.d.ts | 8 +- .../Tabs/Me/Profile/Root/AvatarHeader.tsx | 11 +- src/utils/navigation/navigators.ts | 3 +- src/utils/queryHooks/profile.ts | 5 +- yarn.lock | 12 +- 18 files changed, 282 insertions(+), 318 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 41a66d7e..ae6b65b3 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -41,9 +41,6 @@ PODS: - ExpoModulesCore - ExpoHaptics (11.2.0): - ExpoModulesCore - - ExpoImageManipulator (10.3.1): - - EXImageLoader - - ExpoModulesCore - ExpoImagePicker (13.1.1): - ExpoModulesCore - ExpoKeepAwake (10.1.1): @@ -531,6 +528,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 @@ -577,6 +583,7 @@ PODS: - Sentry (7.11.0): - Sentry/Core (= 7.11.0) - Sentry/Core (7.11.0) + - TOCropViewController (2.6.1) - Yoga (1.14.0) DEPENDENCIES: @@ -599,7 +606,6 @@ 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`) @@ -658,6 +664,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 +692,7 @@ SPEC REPOS: - SDWebImage - SDWebImageWebPCoder - Sentry + - TOCropViewController EXTERNAL SOURCES: boost: @@ -725,8 +733,6 @@ 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: @@ -835,6 +841,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: @@ -869,7 +877,6 @@ SPEC CHECKSUMS: Expo: 64d52669fa3b9342919b5b44b2b4f15f19b0cf76 ExpoCrypto: d0d0f3e20875dc450b4ec88f0fb608da5c2c6c17 ExpoHaptics: ad58ec96a25e57579c14a47c7d71f0de0de8656a - ExpoImageManipulator: b55580bbc7b10099c7707949903e7176a8542ee8 ExpoImagePicker: d9d6b4f29db437fc7796f13cee5f133f5b4b5f7c ExpoKeepAwake: c0c494b442ecd8122974c13b93ccfb57bd408e88 ExpoLocalization: 8f619bb6eec64575cd5220bfabbd7b4e2d6f33f8 @@ -938,6 +945,7 @@ SPEC CHECKSUMS: RNCAsyncStorage: 466b9df1a14bccda91da86e0b7d9a345d78e1673 RNFastImage: 945abf54742505d790d9024d230c69b1e866bc88 RNGestureHandler: 61628a2c859172551aa2100d3e73d1e57878392f + RNImageCropPicker: 44e2807bc410741f35d4c45b6586aedfe3da39d2 RNReanimated: 64573e25e078ae6bec03b891586d50b9ec284393 RNScreens: 40a2cb40a02a609938137a1e0acfbf8fc9eebf19 RNSentry: 85f6525b5fe8d2ada065858026b338605b3c09da @@ -946,6 +954,7 @@ SPEC CHECKSUMS: SDWebImage: 0905f1b7760fc8ac4198cae0036600d67478751e SDWebImageWebPCoder: f93010f3f6c031e2f8fb3081ca4ee6966c539815 Sentry: 0c5cd63d714187b4a39c331c1f0eb04ba7868341 + TOCropViewController: edfd4f25713d56905ad1e0b9f5be3fbe0f59c863 Yoga: 99652481fcd320aefa4a7ef90095b95acd181952 PODFILE CHECKSUM: d6d20fa7c51228cebc309aed987ed7d8f4274844 diff --git a/ios/en.lproj/InfoPlist.strings b/ios/en.lproj/InfoPlist.strings index 1787913e..f9728054 100644 --- a/ios/en.lproj/InfoPlist.strings +++ b/ios/en.lproj/InfoPlist.strings @@ -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"; diff --git a/ios/tooot.xcodeproj/project.pbxproj b/ios/tooot.xcodeproj/project.pbxproj index ffe81c03..690f50bc 100644 --- a/ios/tooot.xcodeproj/project.pbxproj +++ b/ios/tooot.xcodeproj/project.pbxproj @@ -386,13 +386,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; diff --git a/ios/tooot/Info.plist b/ios/tooot/Info.plist index 2f610bc1..7bac557c 100644 --- a/ios/tooot/Info.plist +++ b/ios/tooot/Info.plist @@ -55,8 +55,6 @@ - NSCameraUsageDescription - Allow $(PRODUCT_NAME) to capture photo or video and attach it to your toot NSLocationWhenInUseUsageDescription NSMainNibFile diff --git a/ios/zh-Hans.lproj/InfoPlist.strings b/ios/zh-Hans.lproj/InfoPlist.strings index 41bee912..67025bd4 100644 --- a/ios/zh-Hans.lproj/InfoPlist.strings +++ b/ios/zh-Hans.lproj/InfoPlist.strings @@ -6,6 +6,5 @@ */ -"NSCameraUsageDescription" = "允许tooot拍摄图片或视频,以添加嘟文附件"; "NSPhotoLibraryAddUsageDescription" = "允许tooot保存图片至相册"; "NSPhotoLibraryUsageDescription" = "允许tooot读取相册图片或视频,以添加嘟文附件"; diff --git a/package.json b/package.json index b362046b..149e03b8 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,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", @@ -84,6 +83,7 @@ "react-native-flash-message": "0.2.1", "react-native-gesture-handler": "2.4.2", "react-native-htmlview": "0.16.0", + "react-native-image-crop-picker": "^0.37.3", "react-native-image-keyboard": "^2.2.0", "react-native-pager-view": "5.4.11", "react-native-reanimated": "2.8.0", diff --git a/src/@types/app.d.ts b/src/@types/app.d.ts index 489f987e..0db454c2 100644 --- a/src/@types/app.d.ts +++ b/src/@types/app.d.ts @@ -13,11 +13,4 @@ declare namespace App { | 'Conversations' | 'Bookmarks' | 'Favourites' - - interface IImageInfo { - uri: string - width: number - height: number - type?: 'image' | 'video' - } } diff --git a/src/Screens.tsx b/src/Screens.tsx index 805129ff..ae85cbca 100644 --- a/src/Screens.tsx +++ b/src/Screens.tsx @@ -178,8 +178,49 @@ const Screens: React.FC = ({ 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 = ({ 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 = ({ 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] diff --git a/src/components/mediaSelector.ts b/src/components/mediaSelector.ts index 50ad1adc..ad934ff2 100644 --- a/src/components/mediaSelector.ts +++ b/src/components/mediaSelector.ts @@ -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 @@ -19,134 +22,157 @@ export interface Props { } const mediaSelector = async ({ - mediaTypes = ImagePicker.MediaTypeOptions.All, + mediaType, resize, + maximum, + indicateMaximum = false, showActionSheetWithOptions -}: Props): Promise => { - 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 => { + const checkLibraryPermission = async (): Promise => { + 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 + }).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 + }).catch(() => {}) + croppedImage && croppedImages.push(croppedImage) + } + return resolve(croppedImages) } } + const selectVideo = async () => { + const video = await ImagePicker.openPicker({ + mediaType: 'video', + includeExif: false + }).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 } } ) diff --git a/src/i18n/en/components/mediaSelector.json b/src/i18n/en/components/mediaSelector.json index 6f509ea7..adc0fa47 100644 --- a/src/i18n/en/components/mediaSelector.json +++ b/src/i18n/en/components/mediaSelector.json @@ -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" } } } diff --git a/src/screens/Compose.tsx b/src/screens/Compose.tsx index d44beccf..d0695c9d 100644 --- a/src/screens/Compose.tsx +++ b/src/screens/Compose.tsx @@ -136,18 +136,6 @@ const ScreenCompose: React.FC> = ({ ]) 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> = ({ 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': diff --git a/src/screens/Compose/Root/Footer/addAttachment.ts b/src/screens/Compose/Root/Footer/addAttachment.ts index 4fd25310..5b692c9b 100644 --- a/src/screens/Compose/Root/Footer/addAttachment.ts +++ b/src/screens/Compose/Root/Footer/addAttachment.ts @@ -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 @@ -19,39 +19,38 @@ export interface Props { export const uploadAttachment = async ({ composeDispatch, - imageInfo + media }: { composeDispatch: Dispatch - imageInfo: Pick + media: Pick }) => { 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({ 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 => { - 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 diff --git a/src/screens/Compose/Root/Header/TextInput.tsx b/src/screens/Compose/Root/Header/TextInput.tsx index d9325197..cebd887f 100644 --- a/src/screens/Compose/Root/Header/TextInput.tsx +++ b/src/screens/Compose/Root/Header/TextInput.tsx @@ -83,9 +83,9 @@ const ComposeTextInput: React.FC = () => { if (nativeEvent.linkUri) { uploadAttachment({ composeDispatch, - imageInfo: { - uri: nativeEvent.linkUri, - type: 'image', + media: { + path: nativeEvent.linkUri, + mime: 'image/gif', width: 100, height: 100 } diff --git a/src/screens/Compose/utils/types.d.ts b/src/screens/Compose/utils/types.d.ts index e394e88c..ebe3305a 100644 --- a/src/screens/Compose/utils/types.d.ts +++ b/src/screens/Compose/utils/types.d.ts @@ -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 & { + type: 'image' | 'video' | 'unknown' + local_thumbnail?: string + hash?: string + } uploading?: boolean } diff --git a/src/screens/Tabs/Me/Profile/Root/AvatarHeader.tsx b/src/screens/Tabs/Me/Profile/Root/AvatarHeader.tsx index abafaaf5..7e9dfc7b 100644 --- a/src/screens/Tabs/Me/Profile/Root/AvatarHeader.tsx +++ b/src/screens/Tabs/Me/Profile/Root/AvatarHeader.tsx @@ -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 = ({ 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 = ({ type, messageRef }) => { failed: true }, type, - data: image.uri + data: image[0].path }) }} /> diff --git a/src/utils/navigation/navigators.ts b/src/utils/navigation/navigators.ts index 7800b4d5..4b8f7301 100644 --- a/src/utils/navigation/navigators.ts +++ b/src/utils/navigation/navigators.ts @@ -52,8 +52,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': { diff --git a/src/utils/queryHooks/profile.ts b/src/utils/queryHooks/profile.ts index eff5110c..d68b7966 100644 --- a/src/utils/queryHooks/profile.ts +++ b/src/utils/queryHooks/profile.ts @@ -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) diff --git a/yarn.lock b/yarn.lock index 833f1382..7d659d65 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4421,13 +4421,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" @@ -7517,6 +7510,11 @@ react-native-htmlview@0.16.0: entities "^1.1.1" htmlparser2-without-node-native "^3.9.2" +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-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" From b13ae5dafe1980eebd65064ca981928cba4becc0 Mon Sep 17 00:00:00 2001 From: Zhiyuan Zheng Date: Sun, 5 Jun 2022 18:52:33 +0200 Subject: [PATCH 03/31] Fixed #117 --- src/components/mediaSelector.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/components/mediaSelector.ts b/src/components/mediaSelector.ts index ad934ff2..ffc92b9f 100644 --- a/src/components/mediaSelector.ts +++ b/src/components/mediaSelector.ts @@ -109,7 +109,10 @@ const mediaSelector = async ({ includeExif: false, multiple: true, minFiles: 1, - maxFiles: _maximum + maxFiles: _maximum, + loadingLabelText: '', + compressImageMaxWidth: 4096, + compressImageMaxHeight: 4096 }).catch(() => {}) if (!images) { @@ -125,7 +128,10 @@ const mediaSelector = async ({ mediaType: 'photo', path: image.path, width: resize.width, - height: resize.height + height: resize.height, + cropperChooseText: i18next.t('common:buttons.apply'), + cropperCancelText: i18next.t('common:buttons.cancel'), + hideBottomControls: true }).catch(() => {}) croppedImage && croppedImages.push(croppedImage) } @@ -135,7 +141,8 @@ const mediaSelector = async ({ const selectVideo = async () => { const video = await ImagePicker.openPicker({ mediaType: 'video', - includeExif: false + includeExif: false, + loadingLabelText: '' }).catch(() => {}) if (video) { From c0d2da3f65700bc33c28c8d2e9e45c8217e2d8b2 Mon Sep 17 00:00:00 2001 From: Zhiyuan Zheng Date: Sun, 5 Jun 2022 21:17:39 +0200 Subject: [PATCH 04/31] Fixed #137 --- VERSIONING.md | 8 ------- ios/Podfile.lock | 16 +++++++++----- package.json | 2 +- src/App.tsx | 1 - src/screens/Compose/Root/Header/TextInput.tsx | 22 ++++++++++++------- yarn.lock | 12 +++++----- 6 files changed, 32 insertions(+), 29 deletions(-) diff --git a/VERSIONING.md b/VERSIONING.md index 6d9e4861..7a500bd4 100644 --- a/VERSIONING.md +++ b/VERSIONING.md @@ -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` | \ No newline at end of file diff --git a/ios/Podfile.lock b/ios/Podfile.lock index ae6b65b3..5235561c 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -412,12 +412,13 @@ PODS: - React-Core - react-native-cameraroll (4.1.2): - React-Core - - react-native-image-keyboard (2.2.0): - - React - react-native-netinfo (9.0.0): - React-Core - react-native-pager-view (5.4.11): - React-Core + - react-native-paste-input (0.4.2): + - React-Core + - Swime (= 3.0.6) - react-native-safe-area-context (4.2.5): - RCT-Folly - RCTRequired @@ -583,6 +584,7 @@ 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) @@ -644,9 +646,9 @@ 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-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`) @@ -692,6 +694,7 @@ SPEC REPOS: - SDWebImage - SDWebImageWebPCoder - Sentry + - Swime - TOCropViewController EXTERNAL SOURCES: @@ -801,12 +804,12 @@ 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-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: @@ -925,9 +928,9 @@ SPEC CHECKSUMS: react-native-blur: cad4d93b364f91e7b7931b3fa935455487e5c33c react-native-blurhash: add4df9a937b4e021a24bc67a0714f13e0bd40b7 react-native-cameraroll: 2957f2bce63ae896a848fbe0d5352c1bd4d20866 - react-native-image-keyboard: adbf5996b8592a7d8cb8d3e431a9607f9cf3b270 react-native-netinfo: 5b664b2945a8f02102b296f0f812bddd6827ed9c react-native-pager-view: 7f00d63688f7df9fad86dfb0154814419cc5eb8d + react-native-paste-input: efbf0b08fa1673f0e3131da6ea01678c1bb8003e react-native-safe-area-context: ebf8c413eb8b5f7c392a036a315eb7b46b96845f react-native-segmented-control: 65df6cd0619b780b3843d574a72d4c7cec396097 React-perflogger: a18b4f0bd933b8b24ecf9f3c54f9bf65180f3fe6 @@ -954,6 +957,7 @@ SPEC CHECKSUMS: SDWebImage: 0905f1b7760fc8ac4198cae0036600d67478751e SDWebImageWebPCoder: f93010f3f6c031e2f8fb3081ca4ee6966c539815 Sentry: 0c5cd63d714187b4a39c331c1f0eb04ba7868341 + Swime: d7b2c277503b6cea317774aedc2dce05613f8b0b TOCropViewController: edfd4f25713d56905ad1e0b9f5be3fbe0f59c863 Yoga: 99652481fcd320aefa4a7ef90095b95acd181952 diff --git a/package.json b/package.json index 149e03b8..5f66e806 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "@formatjs/intl-numberformat": "^8.0.1", "@formatjs/intl-pluralrules": "^5.0.1", "@formatjs/intl-relativetimeformat": "^11.0.1", + "@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.6", "@react-native-community/blur": "3.6.0", @@ -84,7 +85,6 @@ "react-native-gesture-handler": "2.4.2", "react-native-htmlview": "0.16.0", "react-native-image-crop-picker": "^0.37.3", - "react-native-image-keyboard": "^2.2.0", "react-native-pager-view": "5.4.11", "react-native-reanimated": "2.8.0", "react-native-safe-area-context": "4.2.5", diff --git a/src/App.tsx b/src/App.tsx index 496f2c71..96433aeb 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -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' diff --git a/src/screens/Compose/Root/Header/TextInput.tsx b/src/screens/Compose/Root/Header/TextInput.tsx index cebd887f..ff272a98 100644 --- a/src/screens/Compose/Root/Header/TextInput.tsx +++ b/src/screens/Compose/Root/Header/TextInput.tsx @@ -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 ( - { }} 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, media: { - path: nativeEvent.linkUri, - mime: 'image/gif', + path: file.uri, + mime: file.type, width: 100, height: 100 } @@ -94,7 +100,7 @@ const ComposeTextInput: React.FC = () => { }} > {composeState.text.formatted} - + ) } diff --git a/yarn.lock b/yarn.lock index 7d659d65..0397193d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2039,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" @@ -7515,11 +7522,6 @@ react-native-image-crop-picker@^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-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-iphone-x-helper@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/react-native-iphone-x-helper/-/react-native-iphone-x-helper-1.3.1.tgz#20c603e9a0e765fd6f97396638bdeb0e5a60b010" From faebd555e859a3a2042782dab90d1e8bd66a5411 Mon Sep 17 00:00:00 2001 From: Zhiyuan Zheng Date: Mon, 6 Jun 2022 22:49:43 +0200 Subject: [PATCH 05/31] New context menu largely working --- ios/Podfile.lock | 6 + package.json | 3 +- src/components/Timeline/Default.tsx | 169 ++++++----- .../Timeline/Shared/ContextMenu.tsx | 65 ++++ .../Timeline/Shared/ContextMenu/account.ts | 165 ++++++++++ .../Timeline/Shared/ContextMenu/instance.ts | 108 +++++++ .../Timeline/Shared/ContextMenu/share.ts | 40 +++ .../Timeline/Shared/ContextMenu/status.ts | 286 ++++++++++++++++++ .../Timeline/Shared/HeaderDefault.tsx | 64 ++-- src/i18n/en/_all.ts | 1 + src/i18n/en/components/contextMenu.json | 70 +++++ src/i18n/en/components/timeline.json | 88 ------ src/i18n/i18n.ts | 4 +- src/screens/Actions.tsx | 33 -- yarn.lock | 4 + 15 files changed, 872 insertions(+), 234 deletions(-) create mode 100644 src/components/Timeline/Shared/ContextMenu.tsx create mode 100644 src/components/Timeline/Shared/ContextMenu/account.ts create mode 100644 src/components/Timeline/Shared/ContextMenu/instance.ts create mode 100644 src/components/Timeline/Shared/ContextMenu/share.ts create mode 100644 src/components/Timeline/Shared/ContextMenu/status.ts create mode 100644 src/i18n/en/components/contextMenu.json diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 5235561c..885ea5d8 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -412,6 +412,8 @@ PODS: - React-Core - react-native-cameraroll (4.1.2): - React-Core + - react-native-context-menu-view (1.5.4): + - React - react-native-netinfo (9.0.0): - React-Core - react-native-pager-view (5.4.11): @@ -646,6 +648,7 @@ 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-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`)" @@ -804,6 +807,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native-blurhash" react-native-cameraroll: :path: "../node_modules/@react-native-community/cameraroll" + 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: @@ -928,6 +933,7 @@ SPEC CHECKSUMS: react-native-blur: cad4d93b364f91e7b7931b3fa935455487e5c33c react-native-blurhash: add4df9a937b4e021a24bc67a0714f13e0bd40b7 react-native-cameraroll: 2957f2bce63ae896a848fbe0d5352c1bd4d20866 + react-native-context-menu-view: b0beca02aad4bd9f9d7d932bf437e0a03baa69ef react-native-netinfo: 5b664b2945a8f02102b296f0f812bddd6827ed9c react-native-pager-view: 7f00d63688f7df9fad86dfb0154814419cc5eb8d react-native-paste-input: efbf0b08fa1673f0e3131da6ea01678c1bb8003e diff --git a/package.json b/package.json index 5f66e806..582be92e 100644 --- a/package.json +++ b/package.json @@ -79,6 +79,7 @@ "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", @@ -151,4 +152,4 @@ } } } -} \ No newline at end of file +} diff --git a/src/components/Timeline/Default.tsx b/src/components/Timeline/Default.tsx index 9d863144..6c84aec3 100644 --- a/src/components/Timeline/Default.tsx +++ b/src/components/Timeline/Default.tsx @@ -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 = ({ }, []) return ( - - {item.reblog ? ( - - ) : item._pinned ? ( - - ) : null} - - - - - - - {}} > - {typeof actualStatus.content === 'string' && - actualStatus.content.length > 0 ? ( - + ) : item._pinned ? ( + + ) : null} + + + + - ) : null} - {queryKey && actualStatus.poll ? ( - + + + {typeof actualStatus.content === 'string' && + actualStatus.content.length > 0 ? ( + + ) : null} + {queryKey && actualStatus.poll ? ( + + ) : null} + {!disableDetails && + Array.isArray(actualStatus.media_attachments) && + actualStatus.media_attachments.length ? ( + + ) : null} + {!disableDetails && actualStatus.card ? ( + + ) : null} + {!disableDetails ? ( + + ) : null} + + + + + {queryKey && !disableDetails ? ( + 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 ? ( - - ) : null} - {!disableDetails && actualStatus.card ? ( - - ) : null} - {!disableDetails ? ( - - ) : null} - - - - - {queryKey && !disableDetails ? ( - d?.id !== instanceAccount?.id), - d => d?.id - ).map(d => d?.acct)} - reblog={item.reblog ? true : false} - /> - ) : null} - + + ) } diff --git a/src/components/Timeline/Shared/ContextMenu.tsx b/src/components/Timeline/Shared/ContextMenu.tsx new file mode 100644 index 00000000..d3769c84 --- /dev/null +++ b/src/components/Timeline/Shared/ContextMenu.tsx @@ -0,0 +1,65 @@ +import { QueryKeyTimeline } from '@utils/queryHooks/timeline' +import { createContext } from 'react' +import ContextMenu, { ContextMenuAction } from 'react-native-context-menu-view' +import contextMenuAccount from './ContextMenu/account' +import contextMenuInstance from './ContextMenu/instance' +import contextMenuShare from './ContextMenu/share' +import contextMenuStatus from './ContextMenu/status' + +export interface Props { + status: Mastodon.Status + queryKey?: QueryKeyTimeline + rootQueryKey?: QueryKeyTimeline +} + +export const ContextMenuContext = createContext([]) + +const TimelineContextMenu: React.FC = ({ + children, + status, + queryKey, + rootQueryKey +}) => { + if (!queryKey) { + return <>{children} + } + + const menuItems: ContextMenuAction[] = [] + + const shareOnPress = contextMenuShare({ menuItems, status }) + const statusOnPress = contextMenuStatus({ + menuItems, + status, + queryKey, + rootQueryKey + }) + const accountOnPress = contextMenuAccount({ + menuItems, + status, + queryKey, + rootQueryKey + }) + const instanceOnPress = contextMenuInstance({ + menuItems, + status, + queryKey, + rootQueryKey + }) + + return ( + + { + shareOnPress(id) + statusOnPress(id) + accountOnPress(id) + instanceOnPress(id) + }} + children={children} + /> + + ) +} + +export default TimelineContextMenu diff --git a/src/components/Timeline/Shared/ContextMenu/account.ts b/src/components/Timeline/Shared/ContextMenu/account.ts new file mode 100644 index 00000000..76e6436b --- /dev/null +++ b/src/components/Timeline/Shared/ContextMenu/account.ts @@ -0,0 +1,165 @@ +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 { + menuItems: ContextMenuAction[] + status: Mastodon.Status + queryKey: QueryKeyTimeline + rootQueryKey?: QueryKeyTimeline +} + +const contextMenuAccount = ({ + menuItems, + status, + queryKey, + rootQueryKey +}: 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 === status.account.id + + if (!ownAccount) { + switch (Platform.OS) { + case 'ios': + menuItems.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: + menuItems.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) => { + const url = status.url || status.uri + switch (id) { + case 'account-mute': + analytics('timeline_shared_headeractions_account_mute_press', { + page: queryKey && queryKey[1].page + }) + mutateion.mutate({ + type: 'updateAccountProperty', + queryKey, + id: status.account.id, + 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: status.account.id, + 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: status.account.id, + payload: { property: 'reports' } + }) + break + } + } +} + +export default contextMenuAccount diff --git a/src/components/Timeline/Shared/ContextMenu/instance.ts b/src/components/Timeline/Shared/ContextMenu/instance.ts new file mode 100644 index 00000000..008b1b80 --- /dev/null +++ b/src/components/Timeline/Shared/ContextMenu/instance.ts @@ -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 { + menuItems: ContextMenuAction[] + status: Mastodon.Status + queryKey: QueryKeyTimeline + rootQueryKey?: QueryKeyTimeline +} + +const contextMenuInstance = ({ + menuItems, + 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': + menuItems.push({ + id: 'instance', + title: t('instance.title'), + actions: [ + { + id: 'instance-block', + title: t('instance.block.action', { instance }), + destructive: true + } + ] + }) + break + default: + menuItems.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 diff --git a/src/components/Timeline/Shared/ContextMenu/share.ts b/src/components/Timeline/Shared/ContextMenu/share.ts new file mode 100644 index 00000000..68e4213f --- /dev/null +++ b/src/components/Timeline/Shared/ContextMenu/share.ts @@ -0,0 +1,40 @@ +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 { + menuItems: ContextMenuAction[] + status: Mastodon.Status +} + +const contextMenuShare = ({ menuItems, status }: Props) => { + const { t } = useTranslation('componentContextMenu') + + if (status.visibility !== 'direct') { + menuItems.push({ + id: 'share', + title: t(`share.status.action`), + systemIcon: 'square.and.arrow.up' + }) + } + + return (id: string) => { + const url = status.url || status.uri + 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 diff --git a/src/components/Timeline/Shared/ContextMenu/status.ts b/src/components/Timeline/Shared/ContextMenu/status.ts new file mode 100644 index 00000000..858c3522 --- /dev/null +++ b/src/components/Timeline/Shared/ContextMenu/status.ts @@ -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 { + menuItems: ContextMenuAction[] + status: Mastodon.Status + queryKey: QueryKeyTimeline + rootQueryKey?: QueryKeyTimeline +} + +const contextMenuStatus = ({ + menuItems, + status, + queryKey, + rootQueryKey +}: Props) => { + const navigation = + useNavigation< + NativeStackNavigationProp + >() + 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': + menuItems.push({ + id: 'status', + title: t('status.title'), + inlineChildren: true, + actions: accountMenuItems + }) + break + default: + menuItems.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({ + 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({ + method: 'get', + url: `statuses/${status.in_reply_to_id}` + }).then(res => res.body) + } + apiInstance<{ + id: Mastodon.Status['id'] + text: NonNullable + 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 diff --git a/src/components/Timeline/Shared/HeaderDefault.tsx b/src/components/Timeline/Shared/HeaderDefault.tsx index b4db437b..ebfc2135 100644 --- a/src/components/Timeline/Shared/HeaderDefault.tsx +++ b/src/components/Timeline/Shared/HeaderDefault.tsx @@ -1,13 +1,13 @@ 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, useRef } from 'react' import { useTranslation } from 'react-i18next' import { Pressable, View } from 'react-native' +import ContextMenu from 'react-native-context-menu-view' +import { TouchableWithoutFeedback } from 'react-native-gesture-handler' +import { ContextMenuContext } from './ContextMenu' import HeaderSharedAccount from './HeaderShared/Account' import HeaderSharedApplication from './HeaderShared/Application' import HeaderSharedCreated from './HeaderShared/Created' @@ -16,24 +16,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>() +const TimelineHeaderDefault = ({ queryKey, status, highlighted }: Props) => { + const { t } = useTranslation('componentContextMenu') const { colors } = useTheme() + const contextMenuItems = useContext(ContextMenuContext) + return ( - + - navigation.navigate('Screen-Actions', { - queryKey, - rootQueryKey, - status, - type: 'status' - }) - } - children={ - - } - /> + > + { + // console.log('index', index) + // console.log('name', name) + // // shareOnPress(name) + // // statusOnPress(name) + // accountOnPress(name) + // // instanceOnPress(name) + // }} + children={ + + } + /> + ) : null} ) diff --git a/src/i18n/en/_all.ts b/src/i18n/en/_all.ts index fa81be2e..5f2a7a82 100644 --- a/src/i18n/en/_all.ts +++ b/src/i18n/en/_all.ts @@ -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'), diff --git a/src/i18n/en/components/contextMenu.json b/src/i18n/en/components/contextMenu.json new file mode 100644 index 00000000..538c7a4f --- /dev/null +++ b/src/i18n/en/components/contextMenu.json @@ -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" + } + } +} \ No newline at end of file diff --git a/src/i18n/en/components/timeline.json b/src/i18n/en/components/timeline.json index caeb15ed..6a3341a1 100644 --- a/src/i18n/en/components/timeline.json +++ b/src/i18n/en/components/timeline.json @@ -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": { diff --git a/src/i18n/i18n.ts b/src/i18n/i18n.ts index 5e951716..37ace006 100644 --- a/src/i18n/i18n.ts +++ b/src/i18n/i18n.ts @@ -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: { diff --git a/src/screens/Actions.tsx b/src/screens/Actions.tsx index a633cda1..c1cf3d7e 100644 --- a/src/screens/Actions.tsx +++ b/src/screens/Actions.tsx @@ -49,7 +49,6 @@ const ScreenActions = ({ 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': @@ -117,38 +116,6 @@ const ScreenActions = ({ dismiss={dismiss} /> ) : null} - {sameAccount && params.status ? ( - - ) : null} - {!sameDomain && statusDomain ? ( - - ) : null} - {params.status.visibility !== 'direct' ? ( - - ) : null} -