diff options
author | Hailey <me@haileyok.com> | 2024-02-27 15:22:03 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-02-27 15:22:03 -0800 |
commit | d451f82f54974b7b3da1477a7e1f221628860f62 (patch) | |
tree | 631d60b9d9ef47129529068753b10029fb99c34f /modules/expo-receive-android-intents/android | |
parent | ac726497a475f7492ee0269851979817b17d98c2 (diff) | |
download | voidsky-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')
4 files changed, 228 insertions, 0 deletions
diff --git a/modules/expo-receive-android-intents/android/.gitignore b/modules/expo-receive-android-intents/android/.gitignore new file mode 100644 index 000000000..877b87e9a --- /dev/null +++ b/modules/expo-receive-android-intents/android/.gitignore @@ -0,0 +1,15 @@ +# OSX +# +.DS_Store + +# Android/IntelliJ +# +build/ +.idea +.gradle +local.properties +*.iml +*.hprof + +# Bundle artifacts +*.jsbundle diff --git a/modules/expo-receive-android-intents/android/build.gradle b/modules/expo-receive-android-intents/android/build.gradle new file mode 100644 index 000000000..3712dda40 --- /dev/null +++ b/modules/expo-receive-android-intents/android/build.gradle @@ -0,0 +1,92 @@ +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' +apply plugin: 'maven-publish' + +group = 'xyz.blueskyweb.app.exporeceiveandroidintents' +version = '0.4.1' + +buildscript { + def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle") + if (expoModulesCorePlugin.exists()) { + apply from: expoModulesCorePlugin + applyKotlinExpoModulesCorePlugin() + } + + // Simple helper that allows the root project to override versions declared by this library. + ext.safeExtGet = { prop, fallback -> + rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback + } + + // Ensures backward compatibility + ext.getKotlinVersion = { + if (ext.has("kotlinVersion")) { + ext.kotlinVersion() + } else { + ext.safeExtGet("kotlinVersion", "1.8.10") + } + } + + repositories { + mavenCentral() + } + + dependencies { + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${getKotlinVersion()}") + } +} + +afterEvaluate { + publishing { + publications { + release(MavenPublication) { + from components.release + } + } + repositories { + maven { + url = mavenLocal().url + } + } + } +} + +android { + compileSdkVersion safeExtGet("compileSdkVersion", 33) + + def agpVersion = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION + if (agpVersion.tokenize('.')[0].toInteger() < 8) { + compileOptions { + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_11.majorVersion + } + } + + namespace "xyz.blueskyweb.app.exporeceiveandroidintents" + defaultConfig { + minSdkVersion safeExtGet("minSdkVersion", 21) + targetSdkVersion safeExtGet("targetSdkVersion", 34) + versionCode 1 + versionName "0.4.1" + } + lintOptions { + abortOnError false + } + publishing { + singleVariant("release") { + withSourcesJar() + } + } +} + +repositories { + mavenCentral() +} + +dependencies { + implementation project(':expo-modules-core') + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${getKotlinVersion()}" +} 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}" + } +} |