about summary refs log tree commit diff
path: root/src/state/models/media
diff options
context:
space:
mode:
Diffstat (limited to 'src/state/models/media')
-rw-r--r--src/state/models/media/gallery.ts85
-rw-r--r--src/state/models/media/image.ts85
2 files changed, 170 insertions, 0 deletions
diff --git a/src/state/models/media/gallery.ts b/src/state/models/media/gallery.ts
new file mode 100644
index 000000000..fbe6c92a0
--- /dev/null
+++ b/src/state/models/media/gallery.ts
@@ -0,0 +1,85 @@
+import {makeAutoObservable, runInAction} from 'mobx'
+import {RootStoreModel} from 'state/index'
+import {ImageModel} from './image'
+import {Image as RNImage} from 'react-native-image-crop-picker'
+import {openPicker} from 'lib/media/picker'
+import {getImageDim} from 'lib/media/manip'
+import {getDataUriSize} from 'lib/media/util'
+
+export class GalleryModel {
+  images: ImageModel[] = []
+
+  constructor(public rootStore: RootStoreModel) {
+    makeAutoObservable(this, {
+      rootStore: false,
+    })
+  }
+
+  get isEmpty() {
+    return this.size === 0
+  }
+
+  get size() {
+    return this.images.length
+  }
+
+  get paths() {
+    return this.images.map(image =>
+      image.compressed === undefined ? image.path : image.compressed.path,
+    )
+  }
+
+  async add(image_: RNImage) {
+    if (this.size >= 4) {
+      return
+    }
+
+    // Temporarily enforce uniqueness but can eventually also use index
+    if (!this.images.some(i => i.path === image_.path)) {
+      const image = new ImageModel(this.rootStore, image_)
+      await image.compress()
+
+      runInAction(() => {
+        this.images.push(image)
+      })
+    }
+  }
+
+  async paste(uri: string) {
+    if (this.size >= 4) {
+      return
+    }
+
+    const {width, height} = await getImageDim(uri)
+
+    const image: RNImage = {
+      path: uri,
+      height,
+      width,
+      size: getDataUriSize(uri),
+      mime: 'image/jpeg',
+    }
+
+    runInAction(() => {
+      this.add(image)
+    })
+  }
+
+  crop(image: ImageModel) {
+    image.crop()
+  }
+
+  remove(image: ImageModel) {
+    const index = this.images.findIndex(image_ => image_.path === image.path)
+    this.images.splice(index, 1)
+  }
+
+  async pick() {
+    const images = await openPicker(this.rootStore, {
+      multiple: true,
+      maxFiles: 4 - this.images.length,
+    })
+
+    await Promise.all(images.map(image => this.add(image)))
+  }
+}
diff --git a/src/state/models/media/image.ts b/src/state/models/media/image.ts
new file mode 100644
index 000000000..584bf90cc
--- /dev/null
+++ b/src/state/models/media/image.ts
@@ -0,0 +1,85 @@
+import {Image as RNImage} from 'react-native-image-crop-picker'
+import {RootStoreModel} from 'state/index'
+import {compressAndResizeImageForPost} from 'lib/media/manip'
+import {makeAutoObservable, runInAction} from 'mobx'
+import {openCropper} from 'lib/media/picker'
+import {POST_IMG_MAX} from 'lib/constants'
+import {scaleDownDimensions} from 'lib/media/util'
+
+// TODO: EXIF embed
+// Cases to consider: ExternalEmbed
+export class ImageModel implements RNImage {
+  path: string
+  mime = 'image/jpeg'
+  width: number
+  height: number
+  size: number
+  cropped?: RNImage = undefined
+  compressed?: RNImage = undefined
+  scaledWidth: number = POST_IMG_MAX.width
+  scaledHeight: number = POST_IMG_MAX.height
+
+  constructor(public rootStore: RootStoreModel, image: RNImage) {
+    makeAutoObservable(this, {
+      rootStore: false,
+    })
+
+    this.path = image.path
+    this.width = image.width
+    this.height = image.height
+    this.size = image.size
+    this.calcScaledDimensions()
+  }
+
+  calcScaledDimensions() {
+    const {width, height} = scaleDownDimensions(
+      {width: this.width, height: this.height},
+      POST_IMG_MAX,
+    )
+
+    this.scaledWidth = width
+    this.scaledHeight = height
+  }
+
+  async crop() {
+    try {
+      const cropped = await openCropper(this.rootStore, {
+        mediaType: 'photo',
+        path: this.path,
+        freeStyleCropEnabled: true,
+        width: this.scaledWidth,
+        height: this.scaledHeight,
+      })
+
+      runInAction(() => {
+        this.cropped = cropped
+      })
+    } catch (err) {
+      this.rootStore.log.error('Failed to crop photo', err)
+    }
+
+    this.compress()
+  }
+
+  async compress() {
+    try {
+      const {width, height} = scaleDownDimensions(
+        this.cropped
+          ? {width: this.cropped.width, height: this.cropped.height}
+          : {width: this.width, height: this.height},
+        POST_IMG_MAX,
+      )
+      const compressed = await compressAndResizeImageForPost({
+        ...(this.cropped === undefined ? this : this.cropped),
+        width,
+        height,
+      })
+
+      runInAction(() => {
+        this.compressed = compressed
+      })
+    } catch (err) {
+      this.rootStore.log.error('Failed to compress photo', err)
+    }
+  }
+}