Bluesky app fork with some witchin' additions 💫 witchsky.app
bluesky fork

[Share Extension] Support on Android for sharing videos to app (#5466)

authored by hailey.at and committed by GitHub d8f72c1e 4553e6b6

Changed files
+102 -21
modules
expo-receive-android-intents
android
src
main
java
xyz
blueskyweb
app
exporeceiveandroidintents
plugins
shareExtension
+79 -21
modules/expo-receive-android-intents/android/src/main/java/xyz/blueskyweb/app/exporeceiveandroidintents/ExpoReceiveAndroidIntentsModule.kt
··· 12 12 import java.io.FileOutputStream 13 13 import java.net.URLEncoder 14 14 15 + enum class AttachmentType { 16 + IMAGE, 17 + VIDEO, 18 + } 19 + 15 20 class ExpoReceiveAndroidIntentsModule : Module() { 16 21 override fun definition() = 17 22 ModuleDefinition { ··· 23 28 } 24 29 25 30 private fun handleIntent(intent: Intent?) { 26 - if (appContext.currentActivity == null || intent == null) return 31 + if (appContext.currentActivity == null) return 32 + intent?.let { 33 + if (it.action == Intent.ACTION_SEND && it.type == "text/plain") { 34 + handleTextIntent(it) 35 + return 36 + } 27 37 28 - if (intent.action == Intent.ACTION_SEND) { 29 - if (intent.type == "text/plain") { 30 - handleTextIntent(intent) 31 - } else if (intent.type.toString().startsWith("image/")) { 32 - handleImageIntent(intent) 33 - } 34 - } else if (intent.action == Intent.ACTION_SEND_MULTIPLE) { 35 - if (intent.type.toString().startsWith("image/")) { 36 - handleImagesIntent(intent) 38 + val type = 39 + if (it.type.toString().startsWith("image/")) { 40 + AttachmentType.IMAGE 41 + } else if (it.type.toString().startsWith("video/")) { 42 + AttachmentType.VIDEO 43 + } else { 44 + return 45 + } 46 + 47 + if (it.action == Intent.ACTION_SEND) { 48 + handleAttachmentIntent(it, type) 49 + } else if (it.action == Intent.ACTION_SEND_MULTIPLE) { 50 + handleAttachmentsIntent(it, type) 37 51 } 38 52 } 39 53 } ··· 48 62 } 49 63 } 50 64 51 - private fun handleImageIntent(intent: Intent) { 65 + private fun handleAttachmentIntent( 66 + intent: Intent, 67 + type: AttachmentType, 68 + ) { 52 69 val uri = 53 70 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { 54 71 intent.getParcelableExtra(Intent.EXTRA_STREAM, Uri::class.java) 55 72 } else { 56 73 intent.getParcelableExtra(Intent.EXTRA_STREAM) 57 74 } 58 - if (uri == null) return 59 75 60 - handleImageIntents(listOf(uri)) 76 + uri?.let { 77 + when (type) { 78 + AttachmentType.IMAGE -> handleImageIntents(listOf(it)) 79 + AttachmentType.VIDEO -> handleVideoIntents(listOf(it)) 80 + } 81 + } 61 82 } 62 83 63 - private fun handleImagesIntent(intent: Intent) { 64 - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { 65 - intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM, Uri::class.java)?.let { 66 - handleImageIntents(it.filterIsInstance<Uri>().take(4)) 84 + private fun handleAttachmentsIntent( 85 + intent: Intent, 86 + type: AttachmentType, 87 + ) { 88 + val uris = 89 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { 90 + intent 91 + .getParcelableArrayListExtra(Intent.EXTRA_STREAM, Uri::class.java) 92 + ?.filterIsInstance<Uri>() 93 + ?.take(4) 94 + } else { 95 + intent 96 + .getParcelableArrayListExtra<Uri>(Intent.EXTRA_STREAM) 97 + ?.filterIsInstance<Uri>() 98 + ?.take(4) 67 99 } 68 - } else { 69 - intent.getParcelableArrayListExtra<Uri>(Intent.EXTRA_STREAM)?.let { 70 - handleImageIntents(it.filterIsInstance<Uri>().take(4)) 100 + 101 + uris?.let { 102 + when (type) { 103 + AttachmentType.IMAGE -> handleImageIntents(it) 104 + else -> return 71 105 } 72 106 } 73 107 } ··· 93 127 } 94 128 } 95 129 130 + private fun handleVideoIntents(uris: List<Uri>) { 131 + val uri = uris[0] 132 + // If there is no extension for the file, substringAfterLast returns the original string - not 133 + // null, so we check for that below 134 + // It doesn't actually matter what the extension is, so defaulting to mp4 is fine, even if the 135 + // video isn't actually an mp4 136 + var extension = uri.path?.substringAfterLast(".") 137 + if (extension == null || extension == uri.path) { 138 + extension = "mp4" 139 + } 140 + val file = createFile(extension) 141 + 142 + val out = FileOutputStream(file) 143 + appContext.currentActivity?.contentResolver?.openInputStream(uri)?.use { 144 + it.copyTo(out) 145 + } 146 + "bluesky://intent/compose?videoUri=${URLEncoder.encode(file.path, "UTF-8")}".toUri().let { 147 + val newIntent = Intent(Intent.ACTION_VIEW, it) 148 + appContext.currentActivity?.startActivity(newIntent) 149 + } 150 + } 151 + 96 152 private fun getImageInfo(uri: Uri): Map<String, Any> { 97 153 val bitmap = MediaStore.Images.Media.getBitmap(appContext.currentActivity?.contentResolver, uri) 98 154 // We have to save this so that we can access it later when uploading the image. 99 155 // createTempFile will automatically place a unique string between "img" and "temp.jpeg" 100 - val file = File.createTempFile("img", "temp.jpeg", appContext.currentActivity?.cacheDir) 156 + val file = createFile("jpeg") 101 157 val out = FileOutputStream(file) 102 158 bitmap.compress(Bitmap.CompressFormat.JPEG, 100, out) 103 159 out.flush() ··· 109 165 "path" to file.path.toString(), 110 166 ) 111 167 } 168 + 169 + private fun createFile(extension: String): File = File.createTempFile(extension, "temp.$extension", appContext.currentActivity?.cacheDir) 112 170 113 171 // We will pas the width and height to the app here, since getting measurements 114 172 // on the RN side is a bit more involved, and we already have them here anyway.
+23
plugins/shareExtension/withIntentFilters.js
··· 45 45 data: [ 46 46 { 47 47 $: { 48 + 'android:mimeType': 'video/*', 49 + }, 50 + }, 51 + ], 52 + }, 53 + { 54 + action: [ 55 + { 56 + $: { 57 + 'android:name': 'android.intent.action.SEND', 58 + }, 59 + }, 60 + ], 61 + category: [ 62 + { 63 + $: { 64 + 'android:name': 'android.intent.category.DEFAULT', 65 + }, 66 + }, 67 + ], 68 + data: [ 69 + { 70 + $: { 48 71 'android:mimeType': 'text/plain', 49 72 }, 50 73 },