1 package expo.modules.image 2 3 import android.view.View 4 import com.bumptech.glide.Glide 5 import com.bumptech.glide.load.model.GlideUrl 6 import com.facebook.react.uimanager.PixelUtil 7 import com.facebook.react.uimanager.Spacing 8 import com.facebook.react.uimanager.ViewProps 9 import com.facebook.yoga.YogaConstants 10 import expo.modules.image.enums.ContentFit 11 import expo.modules.image.enums.Priority 12 import expo.modules.image.records.CachePolicy 13 import expo.modules.image.records.ContentPosition 14 import expo.modules.image.records.SourceMap 15 import expo.modules.kotlin.functions.Queues 16 import expo.modules.kotlin.modules.Module 17 import expo.modules.kotlin.modules.ModuleDefinition 18 import expo.modules.kotlin.views.ViewDefinitionBuilder 19 20 class ExpoImageModule : Module() { 21 override fun definition() = ModuleDefinition { 22 Name("ExpoImage") 23 24 Function("prefetch") { urls: List<String> -> 25 val context = appContext.reactContext ?: return@Function 26 urls.forEach { 27 Glide 28 .with(context) 29 .download(GlideUrl(it)) 30 .submit() 31 } 32 } 33 34 AsyncFunction("clearMemoryCache") { 35 val activity = appContext.currentActivity ?: return@AsyncFunction false 36 Glide.get(activity).clearMemory() 37 return@AsyncFunction true 38 }.runOnQueue(Queues.MAIN) 39 40 AsyncFunction("clearDiskCache") { 41 val activity = appContext.currentActivity ?: return@AsyncFunction false 42 activity.let { 43 Glide.get(activity).clearDiskCache() 44 } 45 46 return@AsyncFunction true 47 } 48 49 View(ExpoImageViewWrapper::class) { 50 Events( 51 "onLoadStart", 52 "onProgress", 53 "onError", 54 "onLoad" 55 ) 56 57 Prop("source") { view: ExpoImageViewWrapper, sources: List<SourceMap>? -> 58 view.imageView.sources = sources ?: emptyList() 59 } 60 61 Prop("contentFit") { view: ExpoImageViewWrapper, contentFit: ContentFit? -> 62 view.imageView.contentFit = contentFit ?: ContentFit.Cover 63 } 64 65 Prop("contentPosition") { view: ExpoImageViewWrapper, contentPosition: ContentPosition? -> 66 view.imageView.contentPosition = contentPosition ?: ContentPosition.center 67 } 68 69 Prop("blurRadius") { view: ExpoImageViewWrapper, blurRadius: Int -> 70 view.imageView.blurRadius = blurRadius 71 } 72 73 Prop("fadeDuration") { view: ExpoImageViewWrapper, fadeDuration: Int -> 74 view.imageView.fadeDuration = fadeDuration 75 } 76 77 PropGroup( 78 ViewProps.BORDER_RADIUS to 0, 79 ViewProps.BORDER_TOP_LEFT_RADIUS to 1, 80 ViewProps.BORDER_TOP_RIGHT_RADIUS to 2, 81 ViewProps.BORDER_BOTTOM_RIGHT_RADIUS to 3, 82 ViewProps.BORDER_BOTTOM_LEFT_RADIUS to 4, 83 ViewProps.BORDER_TOP_START_RADIUS to 5, 84 ViewProps.BORDER_TOP_END_RADIUS to 6, 85 ViewProps.BORDER_BOTTOM_START_RADIUS to 7, 86 ViewProps.BORDER_BOTTOM_END_RADIUS to 8 87 ) { view: ExpoImageViewWrapper, index: Int, borderRadius: Float? -> 88 val radius = makeYogaUndefinedIfNegative(borderRadius ?: YogaConstants.UNDEFINED) 89 view.imageView.setBorderRadius(index, radius) 90 } 91 92 PropGroup( 93 ViewProps.BORDER_WIDTH to Spacing.ALL, 94 ViewProps.BORDER_LEFT_WIDTH to Spacing.LEFT, 95 ViewProps.BORDER_RIGHT_WIDTH to Spacing.RIGHT, 96 ViewProps.BORDER_TOP_WIDTH to Spacing.TOP, 97 ViewProps.BORDER_BOTTOM_WIDTH to Spacing.BOTTOM, 98 ViewProps.BORDER_START_WIDTH to Spacing.START, 99 ViewProps.BORDER_END_WIDTH to Spacing.END 100 ) { view: ExpoImageViewWrapper, index: Int, width: Float? -> 101 val pixelWidth = makeYogaUndefinedIfNegative(width ?: YogaConstants.UNDEFINED) 102 .ifYogaDefinedUse(PixelUtil::toPixelFromDIP) 103 view.imageView.setBorderWidth(index, pixelWidth) 104 } 105 106 PropGroup( 107 ViewProps.BORDER_COLOR to Spacing.ALL, 108 ViewProps.BORDER_LEFT_COLOR to Spacing.LEFT, 109 ViewProps.BORDER_RIGHT_COLOR to Spacing.RIGHT, 110 ViewProps.BORDER_TOP_COLOR to Spacing.TOP, 111 ViewProps.BORDER_BOTTOM_COLOR to Spacing.BOTTOM, 112 ViewProps.BORDER_START_COLOR to Spacing.START, 113 ViewProps.BORDER_END_COLOR to Spacing.END 114 ) { view: ExpoImageViewWrapper, index: Int, color: Int? -> 115 val rgbComponent = if (color == null) YogaConstants.UNDEFINED else (color and 0x00FFFFFF).toFloat() 116 val alphaComponent = if (color == null) YogaConstants.UNDEFINED else (color ushr 24).toFloat() 117 view.imageView.setBorderColor(index, rgbComponent, alphaComponent) 118 } 119 120 Prop("borderStyle") { view: ExpoImageViewWrapper, borderStyle: String? -> 121 view.imageView.setBorderStyle(borderStyle) 122 } 123 124 Prop("backgroundColor") { view: ExpoImageViewWrapper, color: Int? -> 125 view.imageView.setBackgroundColor(color) 126 } 127 128 Prop("tintColor") { view: ExpoImageViewWrapper, color: Int? -> 129 view.imageView.setTintColor(color) 130 } 131 132 Prop("placeholder") { view: ExpoImageViewWrapper, placeholder: List<SourceMap>? -> 133 view.imageView.placeholders = placeholder ?: emptyList() 134 } 135 136 Prop("accessible") { view: ExpoImageViewWrapper, accessible: Boolean -> 137 view.imageView.isFocusable = accessible 138 } 139 140 Prop("priority") { view: ExpoImageViewWrapper, priority: Priority? -> 141 view.imageView.priority = priority ?: Priority.NORMAL 142 } 143 144 Prop("cachePolicy") { view: ExpoImageViewWrapper, cachePolicy: CachePolicy? -> 145 view.imageView.cachePolicy = cachePolicy ?: CachePolicy.DISK 146 } 147 148 OnViewDidUpdateProps { view: ExpoImageViewWrapper -> 149 view.imageView.onAfterUpdateTransaction() 150 } 151 152 OnViewDestroys { view: ExpoImageViewWrapper -> 153 view.imageView.onDrop() 154 } 155 } 156 } 157 } 158 159 // TODO(@lukmccall): Remove when the same functionality will be defined by the expo-modules-core in SDK 48 160 @Suppress("FunctionName") 161 private inline fun <reified T : View, reified PropType, reified CustomValueType> ViewDefinitionBuilder<T>.PropGroup( 162 vararg props: Pair<String, CustomValueType>, 163 noinline body: (view: T, value: CustomValueType, prop: PropType) -> Unit 164 ) { 165 for ((name, value) in props) { 166 Prop<T, PropType>(name) { view, prop -> body(view, value, prop) } 167 } 168 } 169