Skip to content

Commit

Permalink
Android impl
Browse files Browse the repository at this point in the history
  • Loading branch information
janicduplessis committed Jan 27, 2024
1 parent 881c8a7 commit ed058ee
Show file tree
Hide file tree
Showing 10 changed files with 394 additions and 203 deletions.
23 changes: 21 additions & 2 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,13 @@ buildscript {
}
}

apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
def isNewArchitectureEnabled() {
// To opt-in for the New Architecture, you can either:
// - Set `newArchEnabled` to true inside the `gradle.properties` file
// - Invoke gradle with `-newArchEnabled=true`
// - Set an environment variable `ORG_GRADLE_PROJECT_newArchEnabled=true`
return project.hasProperty("newArchEnabled") && project.newArchEnabled == "true"
}

def getExtOrDefault(name) {
return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties['RNBlurhash' + name]
Expand All @@ -25,6 +30,13 @@ def getExtOrIntegerDefault(name) {
return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties['RNBlurhash' + name]).toInteger()
}

apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'

if (isNewArchitectureEnabled()) {
apply plugin: "com.facebook.react"
}

android {
compileSdkVersion getExtOrIntegerDefault('compileSdkVersion')
defaultConfig {
Expand All @@ -41,6 +53,13 @@ android {
lintOptions {
disable 'GradleCompatible'
}
sourceSets.main {
java {
if (!isNewArchitectureEnabled()) {
srcDirs += ["src/paper/java"]
}
}
}
}

repositories {
Expand Down
38 changes: 7 additions & 31 deletions android/src/main/java/com/mrousavy/blurhash/BlurhashImageView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,9 @@ package com.mrousavy.blurhash

import android.content.Context
import android.graphics.Bitmap
import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.ReactContext
import com.facebook.react.bridge.WritableMap
import com.facebook.react.uimanager.events.RCTEventEmitter
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch


internal class BlurhashCache(private val _blurhash: String?, private val _decodeWidth: Int, private val _decodeHeight: Int, private val _decodePunch: Float) {
fun isDifferent(blurhash: String?, decodeWidth: Int, decodeHeight: Int, decodePunch: Float): Boolean {
return !safeStringEquals(_blurhash, blurhash) || _decodeWidth != decodeWidth || _decodeHeight != decodeHeight || _decodePunch != decodePunch
Expand Down Expand Up @@ -42,18 +37,16 @@ class BlurhashImageView(context: Context): androidx.appcompat.widget.AppCompatIm
var decodeHeight = 32
var decodePunch = 1.0f
var decodeAsync = false
var onLoadStart: (() -> Unit)? = null
var onLoadEnd: (() -> Unit)? = null
var onLoadError: ((String?) -> Unit)? = null

private var _cachedBlurhash: BlurhashCache? = null
private var _mainThreadId = Thread.currentThread().id
private var _bitmap: Bitmap? = null

private fun getThreadDescriptor(): String {
return if (Thread.currentThread().id == this._mainThreadId) "main"
else "separate"
}

private fun renderBlurhash() {
try {
emitBlurhashLoadStart()
onLoadStart?.invoke()
if (blurhash == null) {
throw Exception("The provided Blurhash string must not be null!")
}
Expand All @@ -71,10 +64,10 @@ class BlurhashImageView(context: Context): androidx.appcompat.widget.AppCompatIm
throw Exception("The provided Blurhash string was invalid.")
}
setImageBitmap(_bitmap)
emitBlurhashLoadEnd()
onLoadEnd?.invoke()
} catch (e: Exception) {
setImageBitmap(null)
emitBlurhashLoadError(e.message)
onLoadError?.invoke(e.message)
}
}

Expand Down Expand Up @@ -106,23 +99,6 @@ class BlurhashImageView(context: Context): androidx.appcompat.widget.AppCompatIm
}
}

private fun emitBlurhashLoadStart() {
val reactContext = context as ReactContext
reactContext.getJSModule(RCTEventEmitter::class.java).receiveEvent(id, "blurhashLoadStart", null)
}

private fun emitBlurhashLoadEnd() {
val reactContext = context as ReactContext
reactContext.getJSModule(RCTEventEmitter::class.java).receiveEvent(id, "blurhashLoadEnd", null)
}

private fun emitBlurhashLoadError(message: String?) {
val event = Arguments.createMap()
event.putString("message", message)
val reactContext = context as ReactContext
reactContext.getJSModule(RCTEventEmitter::class.java).receiveEvent(id, "blurhashLoadError", event)
}

companion object {
const val REACT_CLASS = "BlurhashImageView"
}
Expand Down
94 changes: 94 additions & 0 deletions android/src/main/java/com/mrousavy/blurhash/BlurhashModule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package com.mrousavy.blurhash

import android.graphics.Bitmap
import android.net.Uri
import com.facebook.common.executors.CallerThreadExecutor
import com.facebook.common.references.CloseableReference
import com.facebook.datasource.DataSource
import com.facebook.drawee.backends.pipeline.Fresco
import com.facebook.imagepipeline.datasource.BaseBitmapDataSubscriber
import com.facebook.imagepipeline.image.CloseableImage
import com.facebook.imagepipeline.request.ImageRequestBuilder
import com.facebook.react.bridge.LifecycleEventListener
import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactMethod
import com.facebook.react.module.annotations.ReactModule
import kotlin.concurrent.thread

@ReactModule(name = NativeBlurhashModuleSpec.NAME)
class BlurhashModule(reactContext: ReactApplicationContext) :
NativeBlurhashModuleSpec(reactContext), LifecycleEventListener {
@ReactMethod
override fun createBlurhashFromImage(
imageUri: String,
componentsX: Double,
componentsY: Double,
promise: Promise
) {
thread(true) {
if (componentsX < 1 || componentsY < 1) {
promise.reject(
"INVALID_COMPONENTS",
Exception("The componentX and componentY arguments must be greater than 0!"))
return@thread
}
try {
val formattedUri = imageUri.trim()

val imageRequest = ImageRequestBuilder.newBuilderWithSource(Uri.parse(formattedUri)).build()
val imagePipeline = Fresco.getImagePipeline()
val dataSource = imagePipeline.fetchDecodedImage(imageRequest, reactApplicationContext)
dataSource.subscribe(
object : BaseBitmapDataSubscriber() {
override fun onNewResultImpl(bitmap: Bitmap?) {
try {
if (dataSource.isFinished && bitmap != null) {
val blurhash = BlurHashEncoder.encode(bitmap, componentsX.toInt(), componentsY.toInt())
promise.resolve(blurhash)
} else {
if (dataSource.failureCause != null) {
promise.reject("LOAD_ERROR", dataSource.failureCause)
} else {
promise.reject("LOAD_ERROR", Exception("Failed to load URI!"))
}
}
} finally {
dataSource.close()
}
}

override fun onFailureImpl(
dataSource: DataSource<CloseableReference<CloseableImage>>
) {
try {
if (dataSource.failureCause != null) {
promise.reject("LOAD_ERROR", dataSource.failureCause)
} else {
promise.reject("LOAD_ERROR", Exception("Failed to load URI!"))
}
} finally {
dataSource.close()
}
}
},
CallerThreadExecutor.getInstance())
} catch (e: Exception) {
promise.reject("INTERNAL", e)
}
}
}

@ReactMethod
override fun clearCosineCache() {
BlurHashDecoder.clearCache()
}

override fun onHostResume() {}

override fun onHostPause() {}

override fun onHostDestroy() {
BlurHashDecoder.clearCache()
}
}
44 changes: 32 additions & 12 deletions android/src/main/java/com/mrousavy/blurhash/BlurhashPackage.kt
Original file line number Diff line number Diff line change
@@ -1,22 +1,42 @@
package com.mrousavy.blurhash

import com.facebook.react.ReactPackage
import com.facebook.react.bridge.JavaScriptModule
import com.facebook.react.TurboReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.module.annotations.ReactModule
import com.facebook.react.module.model.ReactModuleInfo
import com.facebook.react.module.model.ReactModuleInfoProvider
import com.facebook.react.turbomodule.core.interfaces.TurboModule
import com.facebook.react.uimanager.ViewManager

class BlurhashPackage : ReactPackage {
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
return listOf<NativeModule>(BlurhashViewModule(reactContext))
class BlurhashPackage : TurboReactPackage() {
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
return when (name) {
NativeBlurhashModuleSpec.NAME -> BlurhashModule(reactContext)
else -> null
}
}

// Created for legacy support, it's deprecated now.
fun createJSModules(): List<Class<out JavaScriptModule?>> {
return emptyList()
override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
val moduleList: Array<Class<out NativeModule?>> = arrayOf(BlurhashModule::class.java)
val reactModuleInfoMap: MutableMap<String, ReactModuleInfo> = HashMap()
for (moduleClass in moduleList) {
val reactModule = moduleClass.getAnnotation(ReactModule::class.java) ?: continue
reactModuleInfoMap[reactModule.name] =
ReactModuleInfo(
reactModule.name,
moduleClass.name,
true,
reactModule.needsEagerInit,
/** TODO remove the parameter once support for RN < 0.73 is dropped */
reactModule.hasConstants,
reactModule.isCxxModule,
TurboModule::class.java.isAssignableFrom(moduleClass))
}
return ReactModuleInfoProvider { reactModuleInfoMap }
}

override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
return listOf<ViewManager<*, *>>(BlurhashViewManager())
}
}
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
return listOf<ViewManager<*, *>>(BlurhashViewManager())
}
}
Loading

0 comments on commit ed058ee

Please sign in to comment.