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