about summary refs log tree commit diff
path: root/modules/expo-receive-android-intents/android/src
diff options
context:
space:
mode:
authorHailey <me@haileyok.com>2024-02-27 15:22:03 -0800
committerGitHub <noreply@github.com>2024-02-27 15:22:03 -0800
commitd451f82f54974b7b3da1477a7e1f221628860f62 (patch)
tree631d60b9d9ef47129529068753b10029fb99c34f /modules/expo-receive-android-intents/android/src
parentac726497a475f7492ee0269851979817b17d98c2 (diff)
downloadvoidsky-d451f82f54974b7b3da1477a7e1f221628860f62.tar.zst
Share Extension/Intents (#2587)
* add native ios code outside of ios project

* helper script

* going to be a lot of these commits to squash...backing up

* save

* start of an expo plugin

* create info.plist

* copy the view controller

* maybe working

* working

* wait working now

* working plugin

* use current scheme

* update intent path

* use better params

* support text in uri

* build

* use better encoding

* handle images

* cleanup ios plugin

* android

* move bash script to /scripts

* handle cases where loaded data is uiimage rather than uri

* remove unnecessary logic, allow more than 4 images and just take first 4

* android build plugin

* limit images to four on android

* use js for plugins, no need to build

* revert changes to app config

* use correct scheme on android

* android readme

* move ios extension to /modules

* remove unnecessary event

* revert typo

* plugin readme

* scripts readme

* add configurable scheme to .env, default to `bluesky`

* remove debug

* revert .gitignore change

* add comment about updating .env to app.config.js for those modifying scheme

* modify .env

* update android module to use the proper url

* update ios extension

* remove comment

* parse and validate incoming image uris

* fix types

* rm oops

* fix a few typos
Diffstat (limited to 'modules/expo-receive-android-intents/android/src')
-rw-r--r--modules/expo-receive-android-intents/android/src/main/AndroidManifest.xml2
-rw-r--r--modules/expo-receive-android-intents/android/src/main/java/xyz/blueskyweb/app/exporeceiveandroidintents/ExpoReceiveAndroidIntentsModule.kt119
2 files changed, 121 insertions, 0 deletions
diff --git a/modules/expo-receive-android-intents/android/src/main/AndroidManifest.xml b/modules/expo-receive-android-intents/android/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..bdae66c8f
--- /dev/null
+++ b/modules/expo-receive-android-intents/android/src/main/AndroidManifest.xml
@@ -0,0 +1,2 @@
+<manifest>
+</manifest>
diff --git a/modules/expo-receive-android-intents/android/src/main/java/xyz/blueskyweb/app/exporeceiveandroidintents/ExpoReceiveAndroidIntentsModule.kt b/modules/expo-receive-android-intents/android/src/main/java/xyz/blueskyweb/app/exporeceiveandroidintents/ExpoReceiveAndroidIntentsModule.kt
new file mode 100644
index 000000000..c2e17fb80
--- /dev/null
+++ b/modules/expo-receive-android-intents/android/src/main/java/xyz/blueskyweb/app/exporeceiveandroidintents/ExpoReceiveAndroidIntentsModule.kt
@@ -0,0 +1,119 @@
+package xyz.blueskyweb.app.exporeceiveandroidintents
+
+import android.content.Intent
+import android.graphics.Bitmap
+import android.net.Uri
+import android.os.Build
+import android.provider.MediaStore
+import androidx.core.net.toUri
+import expo.modules.kotlin.modules.Module
+import expo.modules.kotlin.modules.ModuleDefinition
+import java.io.File
+import java.io.FileOutputStream
+import java.net.URLEncoder
+
+class ExpoReceiveAndroidIntentsModule : Module() {
+  override fun definition() = ModuleDefinition {
+    Name("ExpoReceiveAndroidIntents")
+
+    OnNewIntent {
+      handleIntent(it)
+    }
+  }
+
+  private fun handleIntent(intent: Intent?) {
+    if(appContext.currentActivity == null || intent == null) return
+
+    if (intent.action == Intent.ACTION_SEND) {
+      if (intent.type == "text/plain") {
+        handleTextIntent(intent)
+      } else if (intent.type.toString().startsWith("image/")) {
+        handleImageIntent(intent)
+      }
+    } else if (intent.action == Intent.ACTION_SEND_MULTIPLE) {
+      if (intent.type.toString().startsWith("image/")) {
+        handleImagesIntent(intent)
+      }
+    }
+  }
+
+  private fun handleTextIntent(intent: Intent) {
+    intent.getStringExtra(Intent.EXTRA_TEXT)?.let {
+      val encoded = URLEncoder.encode(it, "UTF-8")
+      "bluesky://intent/compose?text=${encoded}".toUri().let { uri ->
+        val newIntent = Intent(Intent.ACTION_VIEW, uri)
+        appContext.currentActivity?.startActivity(newIntent)
+      }
+    }
+  }
+
+  private fun handleImageIntent(intent: Intent) {
+    val uri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+      intent.getParcelableExtra(Intent.EXTRA_STREAM, Uri::class.java)
+    } else {
+      intent.getParcelableExtra(Intent.EXTRA_STREAM)
+    }
+    if (uri == null) return
+
+    handleImageIntents(listOf(uri))
+  }
+
+  private fun handleImagesIntent(intent: Intent) {
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+      intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM, Uri::class.java)?.let {
+        handleImageIntents(it.filterIsInstance<Uri>().take(4))
+      }
+    } else {
+      intent.getParcelableArrayListExtra<Uri>(Intent.EXTRA_STREAM)?.let {
+        handleImageIntents(it.filterIsInstance<Uri>().take(4))
+      }
+    }
+  }
+
+  private fun handleImageIntents(uris: List<Uri>) {
+    var allParams = ""
+
+    uris.forEachIndexed { index, uri ->
+      val info = getImageInfo(uri)
+      val params = buildUriData(info)
+      allParams = "${allParams}${params}"
+
+      if (index < uris.count() - 1) {
+        allParams = "${allParams},"
+      }
+    }
+
+    val encoded = URLEncoder.encode(allParams, "UTF-8")
+
+    "bluesky://intent/compose?imageUris=${encoded}".toUri().let {
+      val newIntent = Intent(Intent.ACTION_VIEW, it)
+      appContext.currentActivity?.startActivity(newIntent)
+    }
+  }
+
+  private fun getImageInfo(uri: Uri): Map<String, Any> {
+    val bitmap = MediaStore.Images.Media.getBitmap(appContext.currentActivity?.contentResolver, uri)
+    // We have to save this so that we can access it later when uploading the image.
+    // createTempFile will automatically place a unique string between "img" and "temp.jpeg"
+    val file = File.createTempFile("img", "temp.jpeg", appContext.currentActivity?.cacheDir)
+    val out = FileOutputStream(file)
+    bitmap.compress(Bitmap.CompressFormat.JPEG, 100, out)
+    out.flush()
+    out.close()
+
+    return mapOf(
+      "width" to bitmap.width,
+      "height" to bitmap.height,
+      "path" to file.path.toString()
+    )
+  }
+
+  // We will pas the width and height to the app here, since getting measurements
+  // on the RN side is a bit more involved, and we already have them here anyway.
+  private fun buildUriData(info: Map<String, Any>): String {
+    val path = info.getValue("path")
+    val width = info.getValue("width")
+    val height = info.getValue("height")
+    return "file://${path}|${width}|${height}"
+  }
+}