@honeypathkar/react-native-lq-bottom-bar 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +349 -2
- package/android/build.gradle +78 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/java/com/honeypathkar/lqbottombar/BackdropBlur.kt +428 -0
- package/android/src/main/java/com/honeypathkar/lqbottombar/LQBottomBarPackage.kt +34 -0
- package/android/src/main/java/com/honeypathkar/lqbottombar/LQBottomBarView.kt +503 -0
- package/android/src/main/java/com/honeypathkar/lqbottombar/LQBottomBarViewManager.kt +119 -0
- package/android/src/main/java/com/honeypathkar/lqbottombar/TabSelectedEvent.kt +30 -0
- package/ios/LQBottomBarView.swift +328 -0
- package/ios/LQBottomBarViewManager.m +21 -0
- package/ios/LQBottomBarViewManager.swift +36 -0
- package/lib/commonjs/NativeBottomBar.d.ts +33 -0
- package/lib/commonjs/NativeBottomBar.d.ts.map +1 -0
- package/lib/commonjs/NativeBottomBar.js +9 -0
- package/lib/commonjs/NativeBottomBar.js.map +1 -0
- package/lib/commonjs/index.d.ts +6 -0
- package/lib/commonjs/index.d.ts.map +1 -0
- package/lib/commonjs/index.js +131 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/rasterise.d.ts +20 -0
- package/lib/commonjs/rasterise.d.ts.map +1 -0
- package/lib/commonjs/rasterise.js +398 -0
- package/lib/commonjs/rasterise.js.map +1 -0
- package/lib/commonjs/types.d.ts +119 -0
- package/lib/commonjs/types.d.ts.map +1 -0
- package/lib/commonjs/types.js +3 -0
- package/lib/commonjs/types.js.map +1 -0
- package/package.json +65 -4
- package/react-native-lq-bottom-bar.podspec +27 -0
- package/src/NativeBottomBar.ts +43 -0
- package/src/index.tsx +139 -0
- package/src/rasterise.tsx +444 -0
- package/src/types.ts +136 -0
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
package com.honeypathkar.lqbottombar
|
|
2
|
+
|
|
3
|
+
import android.graphics.Bitmap
|
|
4
|
+
import android.graphics.Canvas
|
|
5
|
+
import android.graphics.RenderEffect
|
|
6
|
+
import android.graphics.Shader
|
|
7
|
+
import android.os.Build
|
|
8
|
+
import android.os.SystemClock
|
|
9
|
+
import android.view.View
|
|
10
|
+
import android.view.ViewTreeObserver
|
|
11
|
+
import android.widget.ImageView
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* BLURRING WHAT IS BEHIND A VIEW, on a platform that has no API for it.
|
|
15
|
+
*
|
|
16
|
+
* This is the one genuinely awkward corner of the package, so it is worth
|
|
17
|
+
* being precise about why it looks like this.
|
|
18
|
+
*
|
|
19
|
+
* iOS hands the whole problem to UIKit: a `UITabBar` with
|
|
20
|
+
* `configureWithDefaultBackground()` gets the system material, and the system
|
|
21
|
+
* knows what is underneath it. Android has no equivalent. The two things that
|
|
22
|
+
* sound like one are not:
|
|
23
|
+
*
|
|
24
|
+
* - `RenderEffect.createBlurEffect` (API 31+) blurs a view's OWN content.
|
|
25
|
+
* Setting it on this bar would blur the bar's icons, not the page behind
|
|
26
|
+
* them.
|
|
27
|
+
* - `Window.setBackgroundBlurRadius` (API 31+) blurs behind a WINDOW, and
|
|
28
|
+
* only for a window that is not fullscreen — a dialog. A tab bar inside
|
|
29
|
+
* the activity's own window cannot use it.
|
|
30
|
+
*
|
|
31
|
+
* So the backdrop has to be produced by hand, which is what every Android
|
|
32
|
+
* blur library does and what this class does:
|
|
33
|
+
*
|
|
34
|
+
* 1. Draw the root view into a small off-screen bitmap, offset so the
|
|
35
|
+
* bar's own rectangle lands at the bitmap's origin.
|
|
36
|
+
* 2. Blur that bitmap.
|
|
37
|
+
* 3. Draw it under the bar's contents, scaled back up.
|
|
38
|
+
*
|
|
39
|
+
* The downscale in step 1 is not only for speed: a bilinear upscale in step 3
|
|
40
|
+
* is itself a blur, so the explicit blur in step 2 has much less work to do.
|
|
41
|
+
*
|
|
42
|
+
* KNOWN LIMITATION, inherent to the technique rather than to this code.
|
|
43
|
+
* `View.draw(Canvas)` onto a bitmap is a SOFTWARE draw, and content the GPU
|
|
44
|
+
* owns exclusively does not participate: a `SurfaceView` or `TextureView` —
|
|
45
|
+
* video, a map, a camera preview — captures as a blank region, so the bar
|
|
46
|
+
* reads as frosted over nothing there. There is no fix available from inside
|
|
47
|
+
* the view hierarchy. Where that matters, pass `blurAmount={0}` and let the
|
|
48
|
+
* flat scrim carry the bar.
|
|
49
|
+
*/
|
|
50
|
+
internal class BackdropBlur(private val target: View) {
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* How far the capture is downscaled.
|
|
54
|
+
*
|
|
55
|
+
* Eight is BlurView's default and a good trade: a 1080x150px bar region
|
|
56
|
+
* becomes 135x19px, which is ~2.5k pixels to blur instead of 162k, and the
|
|
57
|
+
* upscale smooths what the small radius leaves behind.
|
|
58
|
+
*/
|
|
59
|
+
private val downscale = 8
|
|
60
|
+
|
|
61
|
+
/** Radius in downscaled pixels; set from the JS `blurAmount`. */
|
|
62
|
+
var radius: Float = 0f
|
|
63
|
+
set(value) {
|
|
64
|
+
val clamped = value.coerceIn(0f, 25f)
|
|
65
|
+
if (clamped == field) return
|
|
66
|
+
field = clamped
|
|
67
|
+
applyEffect()
|
|
68
|
+
if (clamped == 0f) {
|
|
69
|
+
detach()
|
|
70
|
+
image.setImageBitmap(null)
|
|
71
|
+
attachedBitmap = null
|
|
72
|
+
} else {
|
|
73
|
+
attach()
|
|
74
|
+
invalidateBlur()
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* The blurred backdrop, as a real child view rather than something painted
|
|
80
|
+
* in `onDraw`.
|
|
81
|
+
*
|
|
82
|
+
* On API 31+ this is what makes the GPU do the blur: a `RenderEffect` set on
|
|
83
|
+
* a view blurs that view's content, and this view's content is the captured
|
|
84
|
+
* page. That is the one case where the "wrong" API is exactly right — the
|
|
85
|
+
* capture has already turned the backdrop INTO our own content.
|
|
86
|
+
*/
|
|
87
|
+
val image: ImageView = ImageView(target.context).apply {
|
|
88
|
+
scaleType = ImageView.ScaleType.FIT_XY
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
private var bitmap: Bitmap? = null
|
|
92
|
+
|
|
93
|
+
/** Which bitmap instance the ImageView currently holds. See `invalidateBlur`. */
|
|
94
|
+
private var attachedBitmap: Bitmap? = null
|
|
95
|
+
|
|
96
|
+
private val canvas = Canvas()
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Set while capturing, and read by the target's own `draw` override.
|
|
100
|
+
*
|
|
101
|
+
* The bar is a DESCENDANT of the view it is capturing, so an unguarded
|
|
102
|
+
* capture would draw the bar into its own backdrop, then blur that, then
|
|
103
|
+
* capture the result — feedback, visible as a smear that deepens every
|
|
104
|
+
* frame. The target skips drawing itself entirely while this is set.
|
|
105
|
+
*/
|
|
106
|
+
var isCapturing = false
|
|
107
|
+
private set
|
|
108
|
+
|
|
109
|
+
private var attached = false
|
|
110
|
+
private var lastCaptureMs = 0L
|
|
111
|
+
|
|
112
|
+
private var blurPixels: IntArray? = null
|
|
113
|
+
private var blurScratch: IntArray? = null
|
|
114
|
+
|
|
115
|
+
private val location = IntArray(2)
|
|
116
|
+
private val rootLocation = IntArray(2)
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Capture on every frame the root draws.
|
|
120
|
+
*
|
|
121
|
+
* A pre-draw listener rather than a timer, because the backdrop is only
|
|
122
|
+
* stale when something behind the bar actually changed, and that is exactly
|
|
123
|
+
* when the root redraws. The listener is removed whenever blur is off, so a
|
|
124
|
+
* bar with `blurAmount={0}` costs nothing at all.
|
|
125
|
+
*/
|
|
126
|
+
private val preDrawListener =
|
|
127
|
+
ViewTreeObserver.OnPreDrawListener {
|
|
128
|
+
invalidateBlur()
|
|
129
|
+
true
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* THE ACTIVITY'S CONTENT VIEW, not the decor view.
|
|
134
|
+
*
|
|
135
|
+
* `getRootView()` returns the decor view, which also holds the status and
|
|
136
|
+
* navigation bar backgrounds. Capturing from there is not WRONG — the offset
|
|
137
|
+
* is computed from both views' on-screen positions, so the maths lands in
|
|
138
|
+
* the same place either way — but it means a full software draw of the
|
|
139
|
+
* system bar decorations on every frame for pixels the bar never samples.
|
|
140
|
+
*
|
|
141
|
+
* `android.R.id.content` is the standard handle on the content view, and it
|
|
142
|
+
* falls back to the decor view for the case where this is somehow hosted
|
|
143
|
+
* outside an activity's content frame.
|
|
144
|
+
*/
|
|
145
|
+
private fun rootView(): View? {
|
|
146
|
+
val root = target.rootView ?: return null
|
|
147
|
+
return root.findViewById(android.R.id.content) ?: root
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
fun attach() {
|
|
151
|
+
if (attached || radius == 0f) return
|
|
152
|
+
val root = rootView() ?: return
|
|
153
|
+
root.viewTreeObserver.addOnPreDrawListener(preDrawListener)
|
|
154
|
+
attached = true
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
fun detach() {
|
|
158
|
+
if (!attached) return
|
|
159
|
+
rootView()?.viewTreeObserver?.removeOnPreDrawListener(preDrawListener)
|
|
160
|
+
attached = false
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
fun release() {
|
|
164
|
+
detach()
|
|
165
|
+
image.setImageBitmap(null)
|
|
166
|
+
attachedBitmap = null
|
|
167
|
+
bitmap?.recycle()
|
|
168
|
+
bitmap = null
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** Re-capture and re-blur, unless it has already happened this frame. */
|
|
172
|
+
fun invalidateBlur() {
|
|
173
|
+
if (radius == 0f) return
|
|
174
|
+
if (target.width == 0 || target.height == 0) return
|
|
175
|
+
|
|
176
|
+
/*
|
|
177
|
+
ONE CAPTURE PER FRAME.
|
|
178
|
+
|
|
179
|
+
A single frame can fire several pre-draws — a layout pass that dirties
|
|
180
|
+
an ancestor, an animation committing, a child invalidating — and each
|
|
181
|
+
would otherwise run a full software draw of the whole tree. Capping at
|
|
182
|
+
frame cadence makes the repeats free without the backdrop ever lagging
|
|
183
|
+
more than it already does.
|
|
184
|
+
*/
|
|
185
|
+
val now = SystemClock.uptimeMillis()
|
|
186
|
+
if (now - lastCaptureMs < 16L) return
|
|
187
|
+
lastCaptureMs = now
|
|
188
|
+
|
|
189
|
+
val root = rootView() ?: return
|
|
190
|
+
if (root.width == 0 || root.height == 0) return
|
|
191
|
+
|
|
192
|
+
val width = (target.width / downscale).coerceAtLeast(1)
|
|
193
|
+
val height = (target.height / downscale).coerceAtLeast(1)
|
|
194
|
+
|
|
195
|
+
var current = bitmap
|
|
196
|
+
if (current == null || current.width != width || current.height != height) {
|
|
197
|
+
current?.recycle()
|
|
198
|
+
// Recycled, so the ImageView must be given the replacement rather than
|
|
199
|
+
// being told to repaint a bitmap that no longer has pixels.
|
|
200
|
+
attachedBitmap = null
|
|
201
|
+
current =
|
|
202
|
+
try {
|
|
203
|
+
Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
|
204
|
+
} catch (error: OutOfMemoryError) {
|
|
205
|
+
// A bar-sized bitmap at 1/8 scale is a few kilobytes, so this is
|
|
206
|
+
// effectively unreachable — but failing to a flat scrim is much
|
|
207
|
+
// better than taking the app down for a decoration.
|
|
208
|
+
bitmap = null
|
|
209
|
+
return
|
|
210
|
+
}
|
|
211
|
+
bitmap = current
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
target.getLocationOnScreen(location)
|
|
215
|
+
root.getLocationOnScreen(rootLocation)
|
|
216
|
+
val offsetX = (location[0] - rootLocation[0]).toFloat()
|
|
217
|
+
val offsetY = (location[1] - rootLocation[1]).toFloat()
|
|
218
|
+
|
|
219
|
+
current.eraseColor(0)
|
|
220
|
+
canvas.setBitmap(current)
|
|
221
|
+
canvas.save()
|
|
222
|
+
|
|
223
|
+
/*
|
|
224
|
+
SCALE THEN TRANSLATE, and the order is not interchangeable.
|
|
225
|
+
|
|
226
|
+
Canvas transforms compose right-to-left on the points being drawn, so
|
|
227
|
+
this pair maps a root pixel p to (p - offset) / downscale: shift the
|
|
228
|
+
bar's own rectangle to the origin first, then shrink. Scaling after
|
|
229
|
+
translating would shrink the offset too and the capture would come from
|
|
230
|
+
a point an eighth of the way towards the bar.
|
|
231
|
+
*/
|
|
232
|
+
canvas.scale(1f / downscale, 1f / downscale)
|
|
233
|
+
canvas.translate(-offsetX, -offsetY)
|
|
234
|
+
|
|
235
|
+
isCapturing = true
|
|
236
|
+
try {
|
|
237
|
+
root.draw(canvas)
|
|
238
|
+
} catch (error: Throwable) {
|
|
239
|
+
// A custom view in the tree that cannot tolerate a software canvas
|
|
240
|
+
// should cost the blur, not the frame.
|
|
241
|
+
} finally {
|
|
242
|
+
isCapturing = false
|
|
243
|
+
canvas.restore()
|
|
244
|
+
canvas.setBitmap(null)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
|
|
248
|
+
// No RenderEffect here, so the blur is done to the pixels directly.
|
|
249
|
+
stackBlur(current, radius.toInt().coerceAtLeast(1))
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/*
|
|
253
|
+
HANDED OVER ONCE, THEN JUST INVALIDATED.
|
|
254
|
+
|
|
255
|
+
`setImageBitmap` wraps the bitmap in a fresh `BitmapDrawable` every call,
|
|
256
|
+
so doing it per frame allocates a drawable per frame for a bitmap the view
|
|
257
|
+
already holds. The pixels are mutated in place, so once the ImageView has
|
|
258
|
+
this instance an `invalidate()` is all that is needed — the software blur
|
|
259
|
+
writes straight into it, and a `RenderEffect` re-reads it when the
|
|
260
|
+
drawable is next drawn.
|
|
261
|
+
*/
|
|
262
|
+
if (attachedBitmap !== current) {
|
|
263
|
+
image.setImageBitmap(current)
|
|
264
|
+
attachedBitmap = current
|
|
265
|
+
} else {
|
|
266
|
+
image.invalidate()
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
private fun applyEffect() {
|
|
271
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) return
|
|
272
|
+
image.setRenderEffect(
|
|
273
|
+
if (radius == 0f) {
|
|
274
|
+
null
|
|
275
|
+
} else {
|
|
276
|
+
/*
|
|
277
|
+
CLAMP, not DECAL. A blur samples outside its own bounds at the edges;
|
|
278
|
+
clamping extends the edge pixels, while the default would fade them
|
|
279
|
+
towards transparent and leave a lighter hairline along the top of the
|
|
280
|
+
bar — which on a dark page reads as a seam.
|
|
281
|
+
*/
|
|
282
|
+
RenderEffect.createBlurEffect(radius, radius, Shader.TileMode.CLAMP)
|
|
283
|
+
}
|
|
284
|
+
)
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* A three-pass box blur, which is a close enough Gaussian for API < 31.
|
|
289
|
+
*
|
|
290
|
+
* Three box passes converge on a Gaussian well within what the eye can tell
|
|
291
|
+
* apart at this scale, and each pass is a running sum — so the cost is per
|
|
292
|
+
* pixel rather than per pixel per radius. On a bitmap this small (a few
|
|
293
|
+
* thousand pixels after the downscale) the whole thing is well under a
|
|
294
|
+
* millisecond, which is what makes doing it on the UI thread acceptable.
|
|
295
|
+
*/
|
|
296
|
+
private fun stackBlur(target: Bitmap, radius: Int) {
|
|
297
|
+
val width = target.width
|
|
298
|
+
val height = target.height
|
|
299
|
+
if (width < 2 || height < 2) return
|
|
300
|
+
|
|
301
|
+
/*
|
|
302
|
+
The two buffers are fields, not locals.
|
|
303
|
+
|
|
304
|
+
This runs on the UI thread on every captured frame, and a pair of
|
|
305
|
+
`IntArray(width * height)` allocations per frame is steady garbage for no
|
|
306
|
+
reason — the size only changes when the bar is resized. Re-allocated only
|
|
307
|
+
when that happens.
|
|
308
|
+
*/
|
|
309
|
+
val count = width * height
|
|
310
|
+
if (blurPixels?.size != count) {
|
|
311
|
+
blurPixels = IntArray(count)
|
|
312
|
+
blurScratch = IntArray(count)
|
|
313
|
+
}
|
|
314
|
+
val pixels = blurPixels ?: return
|
|
315
|
+
val scratch = blurScratch ?: return
|
|
316
|
+
|
|
317
|
+
target.getPixels(pixels, 0, width, 0, 0, width, height)
|
|
318
|
+
|
|
319
|
+
val boxes = boxesForGaussian(radius.toFloat(), 3)
|
|
320
|
+
for (box in boxes) {
|
|
321
|
+
val r = ((box - 1) / 2).coerceAtLeast(1)
|
|
322
|
+
boxBlurHorizontal(pixels, scratch, width, height, r)
|
|
323
|
+
boxBlurVertical(scratch, pixels, width, height, r)
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
target.setPixels(pixels, 0, width, 0, 0, width, height)
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Box sizes whose successive application approximates a Gaussian of the
|
|
331
|
+
* given standard deviation. Ivan Kutskir's derivation, which is the
|
|
332
|
+
* standard one for this trick.
|
|
333
|
+
*/
|
|
334
|
+
private fun boxesForGaussian(sigma: Float, passes: Int): IntArray {
|
|
335
|
+
val idealWidth = Math.sqrt((12.0 * sigma * sigma / passes) + 1.0)
|
|
336
|
+
var lower = Math.floor(idealWidth).toInt()
|
|
337
|
+
if (lower % 2 == 0) lower--
|
|
338
|
+
val upper = lower + 2
|
|
339
|
+
|
|
340
|
+
val idealCrossover =
|
|
341
|
+
(12.0 * sigma * sigma - passes * lower * lower - 4.0 * passes * lower - 3.0 * passes) /
|
|
342
|
+
(-4.0 * lower - 4.0)
|
|
343
|
+
val crossover = Math.round(idealCrossover).toInt()
|
|
344
|
+
|
|
345
|
+
return IntArray(passes) { if (it < crossover) lower else upper }
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
private fun boxBlurHorizontal(
|
|
349
|
+
source: IntArray,
|
|
350
|
+
destination: IntArray,
|
|
351
|
+
width: Int,
|
|
352
|
+
height: Int,
|
|
353
|
+
radius: Int,
|
|
354
|
+
) {
|
|
355
|
+
val divisor = radius + radius + 1
|
|
356
|
+
for (y in 0 until height) {
|
|
357
|
+
val row = y * width
|
|
358
|
+
var alpha = 0
|
|
359
|
+
var red = 0
|
|
360
|
+
var green = 0
|
|
361
|
+
var blue = 0
|
|
362
|
+
|
|
363
|
+
// Prime the running sum with the window centred on the first pixel,
|
|
364
|
+
// clamping both ends to the row's own edge pixels.
|
|
365
|
+
for (x in -radius..radius) {
|
|
366
|
+
val pixel = source[row + x.coerceIn(0, width - 1)]
|
|
367
|
+
alpha += pixel ushr 24 and 0xFF
|
|
368
|
+
red += pixel ushr 16 and 0xFF
|
|
369
|
+
green += pixel ushr 8 and 0xFF
|
|
370
|
+
blue += pixel and 0xFF
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
for (x in 0 until width) {
|
|
374
|
+
destination[row + x] =
|
|
375
|
+
(alpha / divisor shl 24) or
|
|
376
|
+
(red / divisor shl 16) or
|
|
377
|
+
(green / divisor shl 8) or
|
|
378
|
+
(blue / divisor)
|
|
379
|
+
|
|
380
|
+
val leaving = source[row + (x - radius).coerceIn(0, width - 1)]
|
|
381
|
+
val entering = source[row + (x + radius + 1).coerceIn(0, width - 1)]
|
|
382
|
+
alpha += (entering ushr 24 and 0xFF) - (leaving ushr 24 and 0xFF)
|
|
383
|
+
red += (entering ushr 16 and 0xFF) - (leaving ushr 16 and 0xFF)
|
|
384
|
+
green += (entering ushr 8 and 0xFF) - (leaving ushr 8 and 0xFF)
|
|
385
|
+
blue += (entering and 0xFF) - (leaving and 0xFF)
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
private fun boxBlurVertical(
|
|
391
|
+
source: IntArray,
|
|
392
|
+
destination: IntArray,
|
|
393
|
+
width: Int,
|
|
394
|
+
height: Int,
|
|
395
|
+
radius: Int,
|
|
396
|
+
) {
|
|
397
|
+
val divisor = radius + radius + 1
|
|
398
|
+
for (x in 0 until width) {
|
|
399
|
+
var alpha = 0
|
|
400
|
+
var red = 0
|
|
401
|
+
var green = 0
|
|
402
|
+
var blue = 0
|
|
403
|
+
|
|
404
|
+
for (y in -radius..radius) {
|
|
405
|
+
val pixel = source[y.coerceIn(0, height - 1) * width + x]
|
|
406
|
+
alpha += pixel ushr 24 and 0xFF
|
|
407
|
+
red += pixel ushr 16 and 0xFF
|
|
408
|
+
green += pixel ushr 8 and 0xFF
|
|
409
|
+
blue += pixel and 0xFF
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
for (y in 0 until height) {
|
|
413
|
+
destination[y * width + x] =
|
|
414
|
+
(alpha / divisor shl 24) or
|
|
415
|
+
(red / divisor shl 16) or
|
|
416
|
+
(green / divisor shl 8) or
|
|
417
|
+
(blue / divisor)
|
|
418
|
+
|
|
419
|
+
val leaving = source[(y - radius).coerceIn(0, height - 1) * width + x]
|
|
420
|
+
val entering = source[(y + radius + 1).coerceIn(0, height - 1) * width + x]
|
|
421
|
+
alpha += (entering ushr 24 and 0xFF) - (leaving ushr 24 and 0xFF)
|
|
422
|
+
red += (entering ushr 16 and 0xFF) - (leaving ushr 16 and 0xFF)
|
|
423
|
+
green += (entering ushr 8 and 0xFF) - (leaving ushr 8 and 0xFF)
|
|
424
|
+
blue += (entering and 0xFF) - (leaving and 0xFF)
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/*
|
|
2
|
+
ReactShadowNode IS DEPRECATED AND UNAVOIDABLE. It appears only in
|
|
3
|
+
`ReactPackage.createViewManagers`'s own return type, so implementing that
|
|
4
|
+
interface at all means importing it and naming it in an override. The
|
|
5
|
+
suppression is file-level because the import itself is what gets flagged, and
|
|
6
|
+
this file contains nothing but that one implementation — so it cannot hide a
|
|
7
|
+
deprecation that is actually ours.
|
|
8
|
+
*/
|
|
9
|
+
@file:Suppress("DEPRECATION", "OVERRIDE_DEPRECATION")
|
|
10
|
+
|
|
11
|
+
package com.honeypathkar.lqbottombar
|
|
12
|
+
|
|
13
|
+
import android.view.View
|
|
14
|
+
import com.facebook.react.ReactPackage
|
|
15
|
+
import com.facebook.react.bridge.NativeModule
|
|
16
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
17
|
+
import com.facebook.react.uimanager.ReactShadowNode
|
|
18
|
+
import com.facebook.react.uimanager.ViewManager
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Autolinking entry point. Found by `react-native config`, so no app-side
|
|
22
|
+
* registration is needed.
|
|
23
|
+
*/
|
|
24
|
+
class LQBottomBarPackage : ReactPackage {
|
|
25
|
+
|
|
26
|
+
override fun createNativeModules(
|
|
27
|
+
reactContext: ReactApplicationContext
|
|
28
|
+
): List<NativeModule> = emptyList()
|
|
29
|
+
|
|
30
|
+
override fun createViewManagers(
|
|
31
|
+
reactContext: ReactApplicationContext
|
|
32
|
+
): List<ViewManager<out View, out ReactShadowNode<*>>> =
|
|
33
|
+
listOf(LQBottomBarViewManager())
|
|
34
|
+
}
|