about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--ios/Podfile.lock10
-rw-r--r--package.json10
-rw-r--r--src/view/com/composer/ComposePost.tsx53
-rw-r--r--src/view/com/composer/PhotoCarouselPicker.tsx41
-rw-r--r--yarn.lock21
5 files changed, 101 insertions, 34 deletions
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index 80249b61a..16acc15ef 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -244,6 +244,9 @@ PODS:
     - React-Core
   - react-native-pager-view (6.1.2):
     - React-Core
+  - react-native-paste-input (0.6.0):
+    - React-Core
+    - Swime (= 3.0.6)
   - react-native-safe-area-context (4.4.1):
     - RCT-Folly
     - RCTRequired
@@ -390,6 +393,7 @@ PODS:
     - React-RCTImage
   - RNSVG (12.5.0):
     - React-Core
+  - Swime (3.0.6)
   - TOCropViewController (2.6.1)
   - Yoga (1.14.0)
 
@@ -421,6 +425,7 @@ DEPENDENCIES:
   - "react-native-cameraroll (from `../node_modules/@react-native-camera-roll/camera-roll`)"
   - "react-native-image-resizer (from `../node_modules/@bam.tech/react-native-image-resizer`)"
   - 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-splash-screen (from `../node_modules/react-native-splash-screen`)
   - react-native-version-number (from `../node_modules/react-native-version-number`)
@@ -454,6 +459,7 @@ SPEC REPOS:
   trunk:
     - fmt
     - libevent
+    - Swime
     - TOCropViewController
 
 EXTERNAL SOURCES:
@@ -507,6 +513,8 @@ EXTERNAL SOURCES:
     :path: "../node_modules/@bam.tech/react-native-image-resizer"
   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-splash-screen:
@@ -592,6 +600,7 @@ SPEC CHECKSUMS:
   react-native-cameraroll: 0ff04cc4e0ff5f19a94ff4313e5c8bc4503cd86d
   react-native-image-resizer: 794abf75ec13ed1f0dbb1f134e27504ea65e9e66
   react-native-pager-view: 54bed894cecebe28cede54c01038d9d1e122de43
+  react-native-paste-input: 5182843692fd2ec72be50f241a38a49796e225d7
   react-native-safe-area-context: 99b24a0c5acd0d5dcac2b1a7f18c49ea317be99a
   react-native-splash-screen: 4312f786b13a81b5169ef346d76d33bc0c6dc457
   react-native-version-number: b415bbec6a13f2df62bf978e85bc0d699462f37f
@@ -619,6 +628,7 @@ SPEC CHECKSUMS:
   RNReanimated: d8d9d3d3801bda5e35e85cdffc871577d044dc2e
   RNScreens: 34cc502acf1b916c582c60003dc3089fa01dc66d
   RNSVG: 6adc5c52d2488a476248413064b7f2832e639057
+  Swime: d7b2c277503b6cea317774aedc2dce05613f8b0b
   TOCropViewController: edfd4f25713d56905ad1e0b9f5be3fbe0f59c863
   Yoga: c618b544ff8bd8865cdca602f00cbcdb92fd6d31
 
diff --git a/package.json b/package.json
index fd2f4b987..b0bfdddde 100644
--- a/package.json
+++ b/package.json
@@ -24,6 +24,7 @@
     "@fortawesome/free-solid-svg-icons": "^6.1.1",
     "@fortawesome/react-native-fontawesome": "^0.3.0",
     "@gorhom/bottom-sheet": "^4",
+    "@mattermost/react-native-paste-input": "^0.6.0",
     "@react-native-async-storage/async-storage": "^1.17.6",
     "@react-native-camera-roll/camera-roll": "^5.1.0",
     "@react-native-clipboard/clipboard": "^1.10.0",
@@ -115,7 +116,9 @@
     "transformIgnorePatterns": [
       "node_modules/(?!(jest-)?react-native|react-clone-referenced-element|@react-native-community|rollbar-react-native|@fortawesome|@react-native|@react-navigation)"
     ],
-    "modulePathIgnorePatterns": ["__tests__\/.*\/__mocks__"],
+    "modulePathIgnorePatterns": [
+      "__tests__/.*/__mocks__"
+    ],
     "coveragePathIgnorePatterns": [
       "<rootDir>/node_modules/",
       "<rootDir>/src/platform",
@@ -124,7 +127,10 @@
       "<rootDir>/src/state/lib",
       "<rootDir>/__tests__/test-utils.js"
     ],
-    "reporters": [ "default", "jest-junit" ]
+    "reporters": [
+      "default",
+      "jest-junit"
+    ]
   },
   "browserslist": {
     "production": [
diff --git a/src/view/com/composer/ComposePost.tsx b/src/view/com/composer/ComposePost.tsx
index a8def6405..3614267b1 100644
--- a/src/view/com/composer/ComposePost.tsx
+++ b/src/view/com/composer/ComposePost.tsx
@@ -7,11 +7,14 @@ import {
   SafeAreaView,
   ScrollView,
   StyleSheet,
-  TextInput,
   TouchableOpacity,
   TouchableWithoutFeedback,
   View,
 } from 'react-native'
+import PasteInput, {
+  PastedFile,
+  PasteInputRef,
+} from '@mattermost/react-native-paste-input'
 import LinearGradient from 'react-native-linear-gradient'
 import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
 import {UserAutocompleteViewModel} from '../../../state/models/user-autocomplete-view'
@@ -33,7 +36,7 @@ import {detectLinkables, extractEntities} from '../../../lib/strings'
 import {getLinkMeta} from '../../../lib/link-meta'
 import {downloadAndResize} from '../../../lib/images'
 import {UserLocalPhotosModel} from '../../../state/models/user-local-photos'
-import {PhotoCarouselPicker} from './PhotoCarouselPicker'
+import {PhotoCarouselPicker, cropPhoto} from './PhotoCarouselPicker'
 import {SelectedPhoto} from './SelectedPhoto'
 import {usePalette} from '../../lib/hooks/usePalette'
 
@@ -54,7 +57,7 @@ export const ComposePost = observer(function ComposePost({
 }) {
   const pal = usePalette('default')
   const store = useStores()
-  const textInput = useRef<TextInput>(null)
+  const textInput = useRef<PasteInputRef>(null)
   const [isProcessing, setIsProcessing] = useState(false)
   const [processingState, setProcessingState] = useState('')
   const [error, setError] = useState('')
@@ -78,6 +81,16 @@ export const ComposePost = observer(function ComposePost({
     [store],
   )
 
+  // HACK
+  // there's a bug with @mattermost/react-native-paste-input where if the input
+  // is focused during unmount, an exception will throw (seems that a blur method isnt implemented)
+  // manually blurring before closing gets around that
+  // -prf
+  const hackfixOnClose = () => {
+    textInput.current?.blur()
+    onClose()
+  }
+
   // initial setup
   useEffect(() => {
     autocompleteView.setup()
@@ -134,7 +147,8 @@ export const ComposePost = observer(function ComposePost({
         isLoading: false, // done
       })
     }
-  }, [extLink])
+    return cleanup
+  }, [store, extLink])
 
   useEffect(() => {
     // HACK
@@ -196,9 +210,21 @@ export const ComposePost = observer(function ComposePost({
       }
     }
   }
-  const onPressCancel = () => {
-    onClose()
+  const onPaste = async (err: string | undefined, files: PastedFile[]) => {
+    if (err) {
+      return setError(err)
+    }
+    if (selectedPhotos.length >= 4) {
+      return
+    }
+    const imgFile = files.find(file => /\.(jpe?g|png)$/.test(file.fileName))
+    if (!imgFile) {
+      return
+    }
+    const finalImgPath = await cropPhoto(imgFile.uri)
+    onSelectPhotos([...selectedPhotos, finalImgPath])
   }
+  const onPressCancel = () => hackfixOnClose()
   const onPressPublish = async () => {
     if (isProcessing) {
       return
@@ -229,7 +255,7 @@ export const ComposePost = observer(function ComposePost({
     }
     store.me.mainFeed.loadLatest()
     onPost?.()
-    onClose()
+    hackfixOnClose()
     Toast.show(`Your ${replyTo ? 'reply' : 'post'} has been published`)
   }
   const onSelectAutocompleteItem = (item: string) => {
@@ -254,7 +280,11 @@ export const ComposePost = observer(function ComposePost({
     let i = 0
     return detectLinkables(text).map(v => {
       if (typeof v === 'string') {
-        return v
+        return (
+          <Text key={i++} style={styles.textInputFormatting}>
+            {v}
+          </Text>
+        )
       } else {
         return (
           <Text key={i++} style={[pal.link, styles.textInputFormatting]}>
@@ -263,7 +293,7 @@ export const ComposePost = observer(function ComposePost({
         )
       }
     })
-  }, [text])
+  }, [text, pal.link])
 
   return (
     <KeyboardAvoidingView
@@ -354,12 +384,13 @@ export const ComposePost = observer(function ComposePost({
                 avatar={store.me.avatar}
                 size={50}
               />
-              <TextInput
+              <PasteInput
                 testID="composerTextInput"
                 ref={textInput}
                 multiline
                 scrollEnabled
                 onChangeText={(text: string) => onChangeText(text)}
+                onPaste={onPaste}
                 placeholder={selectTextInputPlaceholder}
                 placeholderTextColor={pal.colors.textLight}
                 style={[
@@ -368,7 +399,7 @@ export const ComposePost = observer(function ComposePost({
                   styles.textInputFormatting,
                 ]}>
                 {textDecorated}
-              </TextInput>
+              </PasteInput>
             </View>
             <SelectedPhoto
               selectedPhotos={selectedPhotos}
diff --git a/src/view/com/composer/PhotoCarouselPicker.tsx b/src/view/com/composer/PhotoCarouselPicker.tsx
index 440e7d38f..6b537e9c8 100644
--- a/src/view/com/composer/PhotoCarouselPicker.tsx
+++ b/src/view/com/composer/PhotoCarouselPicker.tsx
@@ -26,6 +26,28 @@ const IMAGE_PARAMS = {
   compressImageQuality: 1.0,
 }
 
+export async function cropPhoto(
+  path: string,
+  imgWidth = MAX_WIDTH,
+  imgHeight = MAX_HEIGHT,
+) {
+  // choose target dimensions based on the original
+  // this causes the photo cropper to start with the full image "selected"
+  const {width, height} = scaleDownDimensions(
+    {width: imgWidth, height: imgHeight},
+    {width: MAX_WIDTH, height: MAX_HEIGHT},
+  )
+  const cropperRes = await openCropper({
+    mediaType: 'photo',
+    path,
+    ...IMAGE_PARAMS,
+    width,
+    height,
+  })
+  const img = await compressIfNeeded(cropperRes, MAX_SIZE)
+  return img.path
+}
+
 export const PhotoCarouselPicker = ({
   selectedPhotos,
   onSelectPhotos,
@@ -55,21 +77,12 @@ export const PhotoCarouselPicker = ({
   const handleSelectPhoto = useCallback(
     async (item: PhotoIdentifier) => {
       try {
-        // choose target dimensions based on the original
-        // this causes the photo cropper to start with the full image "selected"
-        const {width, height} = scaleDownDimensions(
-          {width: item.node.image.width, height: item.node.image.height},
-          {width: MAX_WIDTH, height: MAX_HEIGHT},
+        const imgPath = await cropPhoto(
+          item.node.image.uri,
+          item.node.image.width,
+          item.node.image.height,
         )
-        const cropperRes = await openCropper({
-          mediaType: 'photo',
-          path: item.node.image.uri,
-          ...IMAGE_PARAMS,
-          width,
-          height,
-        })
-        const img = await compressIfNeeded(cropperRes, MAX_SIZE)
-        onSelectPhotos([...selectedPhotos, img.path])
+        onSelectPhotos([...selectedPhotos, imgPath])
       } catch (err: any) {
         // ignore
         store.log.warn('Error selecting photo', err)
diff --git a/yarn.lock b/yarn.lock
index f2c983d3e..69897fc4f 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1907,6 +1907,13 @@
   resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz#b2ac626d6cb9c8718ab459166d4bb405b8ffa78b"
   integrity sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==
 
+"@mattermost/react-native-paste-input@^0.6.0":
+  version "0.6.0"
+  resolved "https://registry.yarnpkg.com/@mattermost/react-native-paste-input/-/react-native-paste-input-0.6.0.tgz#bc920c8f1b442266a6bc58f9122df137b73bc9fa"
+  integrity sha512-Hy4w8RaiiXl2AKcLXT0FjJJsh4FXtLiWCxfh6zaBtCkx7jsr4d9xwJ/zqrnjv0jkG7XbRUCp40dgNBpWYZ1pyQ==
+  dependencies:
+    semver "7.3.8"
+
 "@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1":
   version "5.1.1-v1"
   resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz#dbf733a965ca47b1973177dc0bb6c889edcfb129"
@@ -11039,6 +11046,13 @@ selfsigned@^2.1.1:
   dependencies:
     node-forge "^1"
 
+semver@7.3.8, semver@^7.3.2, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8:
+  version "7.3.8"
+  resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798"
+  integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==
+  dependencies:
+    lru-cache "^6.0.0"
+
 semver@^5.5.0, semver@^5.6.0:
   version "5.7.1"
   resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
@@ -11049,13 +11063,6 @@ semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0:
   resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
   integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
 
-semver@^7.3.2, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8:
-  version "7.3.8"
-  resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798"
-  integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==
-  dependencies:
-    lru-cache "^6.0.0"
-
 send@0.18.0:
   version "0.18.0"
   resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be"