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