Browse Source

fixed bug with child views transformation loss

Oleksandr Kucherenko 5 years ago
parent
commit
2a865fa1db

+ 223
- 0
android/src/main/java/fr/greweb/reactnativeviewshot/DebugViews.java View File

@@ -0,0 +1,223 @@
1
+package fr.greweb.reactnativeviewshot;
2
+
3
+import android.annotation.TargetApi;
4
+import android.app.Activity;
5
+import android.content.res.Resources;
6
+import android.graphics.Matrix;
7
+import android.os.Build;
8
+import android.support.annotation.NonNull;
9
+import android.support.v4.util.Pair;
10
+import android.util.Log;
11
+import android.view.View;
12
+import android.view.ViewGroup;
13
+import android.widget.TextView;
14
+
15
+import java.util.Locale;
16
+import java.util.Stack;
17
+
18
+import javax.annotation.Nullable;
19
+
20
+import static android.view.View.GONE;
21
+import static android.view.View.INVISIBLE;
22
+import static android.view.View.VISIBLE;
23
+
24
+/**
25
+ * @see <a href="https://gist.github.com/OleksandrKucherenko/054b0331ec791edb39db76bd690ecdb2">Author of the Snippet</a>
26
+ */
27
+@SuppressWarnings("WeakerAccess")
28
+public final class DebugViews {
29
+    /**
30
+     * Chunk of the long log line.
31
+     */
32
+    public static final int LOG_MSG_LIMIT = 200;
33
+    /**
34
+     * Initial matrix without transformations.
35
+     */
36
+    public static final Matrix EMPTY_MATRIX = new Matrix();
37
+
38
+    /**
39
+     * Log long message by chunks
40
+     *
41
+     * @param message message to log.
42
+     */
43
+    @SuppressWarnings("UnusedReturnValue")
44
+    public static int longDebug(@NonNull final String tag, @NonNull final String message) {
45
+        int counter = 0;
46
+
47
+        String msg = message;
48
+
49
+        while (msg.length() > 0) {
50
+            final int endOfLine = msg.indexOf("\n"); // -1, 0, 1
51
+            final int breakPoint = Math.min(endOfLine < 0 ? LOG_MSG_LIMIT : endOfLine + 1, LOG_MSG_LIMIT);
52
+            final int last = Math.min(msg.length(), breakPoint);
53
+            final String out = String.format(Locale.US, "%02d: %s", counter, msg.substring(0, last));
54
+            Log.d(tag, out);
55
+
56
+            msg = msg.substring(last);
57
+            counter++;
58
+        }
59
+
60
+        return counter;
61
+    }
62
+
63
+    /**
64
+     * Print into log activity views hierarchy.
65
+     */
66
+    @NonNull
67
+    public static String logViewHierarchy(@NonNull final Activity activity) {
68
+        final View view = activity.findViewById(android.R.id.content);
69
+
70
+        if (null == view)
71
+            return "Activity [" + activity.getClass().getSimpleName() + "] is not initialized yet. ";
72
+
73
+        return logViewHierarchy(view);
74
+    }
75
+
76
+    /**
77
+     * Print into log view hierarchy.
78
+     */
79
+    @NonNull
80
+    private static String logViewHierarchy(@NonNull final View root) {
81
+        final StringBuilder output = new StringBuilder(8192).append("\n");
82
+        final Resources r = root.getResources();
83
+        final Stack<Pair<String, View>> stack = new Stack<>();
84
+        stack.push(Pair.create("", root));
85
+
86
+        while (!stack.empty()) {
87
+            @NonNull final Pair<String, View> p = stack.pop();
88
+            @NonNull final View v = p.second;
89
+            @NonNull final String prefix = p.first;
90
+
91
+            final boolean isLastOnLevel = stack.empty() || !prefix.equals(stack.peek().first);
92
+            final String graphics = "" + prefix + (isLastOnLevel ? "└── " : "├── ");
93
+
94
+            final String className = v.getClass().getSimpleName();
95
+            final String line = graphics + className + dumpProperties(r, v);
96
+
97
+            output.append(line).append("\n");
98
+
99
+            if (v instanceof ViewGroup) {
100
+                final ViewGroup vg = (ViewGroup) v;
101
+                for (int i = vg.getChildCount() - 1; i >= 0; i--) {
102
+                    stack.push(Pair.create(prefix + (isLastOnLevel ? "    " : "│   "), vg.getChildAt(i)));
103
+                }
104
+            }
105
+        }
106
+
107
+        return output.toString();
108
+    }
109
+
110
+    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
111
+    @NonNull
112
+    private static String dumpProperties(@NonNull final Resources r, @NonNull final View v) {
113
+        final StringBuilder sb = new StringBuilder();
114
+
115
+        sb.append(" ").append("id=").append(v.getId()).append(resolveIdToName(r, v));
116
+
117
+        switch (v.getVisibility()) {
118
+            case VISIBLE:
119
+                sb.append(", V--");
120
+                break;
121
+            case INVISIBLE:
122
+                sb.append(", -I-");
123
+                break;
124
+            case GONE:
125
+                sb.append(", --G");
126
+                break;
127
+            default:
128
+                sb.append(", ---");
129
+                break;
130
+        }
131
+
132
+        // transformation matrix exists, rotate/scale/skew/translate/
133
+        if (!v.getMatrix().equals(EMPTY_MATRIX)) {
134
+            sb.append(", ").append("matrix=").append(v.getMatrix().toShortString());
135
+
136
+            if (0.0f != v.getRotation() || 0.0f != v.getRotationX() || 0.0f != v.getRotationY()) {
137
+                sb.append(", rotate=[")
138
+                        .append(v.getRotation()).append(",")
139
+                        .append(v.getRotationX()).append(",")
140
+                        .append(v.getRotationY())
141
+                        .append("]");
142
+
143
+                // print pivote only if its not default
144
+                if (v.getWidth() / 2 != v.getPivotX() || v.getHeight() / 2 != v.getPivotY()) {
145
+                    sb.append(", pivot=[")
146
+                            .append(v.getPivotX()).append(",")
147
+                            .append(v.getPivotY())
148
+                            .append("]");
149
+                }
150
+            }
151
+
152
+            if (0.0f != v.getTranslationX() || 0.0f != v.getTranslationY() || 0.0f != v.getTranslationZ()) {
153
+                sb.append(", translate=[")
154
+                        .append(v.getTranslationX()).append(",")
155
+                        .append(v.getTranslationY()).append(",")
156
+                        .append(v.getTranslationZ())
157
+                        .append("]");
158
+            }
159
+
160
+            if (1.0f != v.getScaleX() || 1.0f != v.getScaleY()) {
161
+                sb.append(", scale=[")
162
+                        .append(v.getScaleX()).append(",")
163
+                        .append(v.getScaleY())
164
+                        .append("]");
165
+            }
166
+        }
167
+
168
+        // padding's
169
+        if (0 != v.getPaddingStart() || 0 != v.getPaddingTop() ||
170
+                0 != v.getPaddingEnd() || 0 != v.getPaddingBottom()) {
171
+            sb.append(", ")
172
+                    .append("padding=[")
173
+                    .append(v.getPaddingStart()).append(",")
174
+                    .append(v.getPaddingTop()).append(",")
175
+                    .append(v.getPaddingEnd()).append(",")
176
+                    .append(v.getPaddingBottom())
177
+                    .append("]");
178
+        }
179
+
180
+        // margin's
181
+        final ViewGroup.LayoutParams lp = v.getLayoutParams();
182
+        if (lp instanceof ViewGroup.MarginLayoutParams) {
183
+            final ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams) lp;
184
+
185
+            if (0 != mlp.leftMargin || 0 != mlp.topMargin ||
186
+                    0 != mlp.rightMargin || 0 != mlp.bottomMargin) {
187
+                sb.append(", ").append("margin=[")
188
+                        .append(mlp.leftMargin).append(",")
189
+                        .append(mlp.topMargin).append(",")
190
+                        .append(mlp.rightMargin).append(",")
191
+                        .append(mlp.bottomMargin)
192
+                        .append("]");
193
+            }
194
+        }
195
+
196
+        // width, height, size
197
+        sb.append(", position=[").append(v.getLeft()).append(",").append(v.getTop()).append("]");
198
+        sb.append(", size=[").append(v.getWidth()).append(",").append(v.getHeight()).append("]");
199
+
200
+        // texts
201
+        if (v instanceof TextView) {
202
+            final TextView tv = (TextView) v;
203
+
204
+            sb.append(", text=\"").append(tv.getText()).append("\"");
205
+        }
206
+
207
+        return sb.toString();
208
+    }
209
+
210
+    /**
211
+     * @see <a href="https://stackoverflow.com/questions/10137692/how-to-get-resource-name-from-resource-id">Lookup resource name</a>
212
+     */
213
+    @NonNull
214
+    private static String resolveIdToName(@Nullable final Resources r, @NonNull final View v) {
215
+        if (null == r) return "";
216
+
217
+        try {
218
+            return " / " + r.getResourceEntryName(v.getId());
219
+        } catch (Throwable ignored) {
220
+            return "";
221
+        }
222
+    }
223
+}

+ 2
- 1
android/src/main/java/fr/greweb/reactnativeviewshot/RNViewShotModule.java View File

@@ -100,7 +100,8 @@ public class RNViewShotModule extends ReactContextBaseJavaModule {
100 100
                     scaleWidth, scaleHeight, outputFile, resultStreamFormat,
101 101
                     snapshotContentContainer, reactContext, activity, promise)
102 102
             );
103
-        } catch (final Throwable ignored) {
103
+        } catch (final Throwable ex) {
104
+            Log.e(RNVIEW_SHOT, "Failed to snapshot view tag " + tag, ex);
104 105
             promise.reject(ViewShot.ERROR_UNABLE_TO_SNAPSHOT, "Failed to snapshot view tag " + tag);
105 106
         }
106 107
     }

+ 85
- 44
android/src/main/java/fr/greweb/reactnativeviewshot/ViewShot.java View File

@@ -5,18 +5,17 @@ import android.graphics.Bitmap;
5 5
 import android.graphics.Canvas;
6 6
 import android.graphics.Color;
7 7
 import android.graphics.Matrix;
8
+import android.graphics.Paint;
8 9
 import android.graphics.Point;
9
-import android.graphics.Rect;
10
-import android.graphics.RectF;
11 10
 import android.net.Uri;
12 11
 import android.support.annotation.IntDef;
13 12
 import android.support.annotation.NonNull;
14 13
 import android.support.annotation.StringDef;
15 14
 import android.util.Base64;
15
+import android.util.Log;
16 16
 import android.view.TextureView;
17 17
 import android.view.View;
18 18
 import android.view.ViewGroup;
19
-import android.view.ViewParent;
20 19
 import android.widget.ScrollView;
21 20
 
22 21
 import com.facebook.react.bridge.Promise;
@@ -34,6 +33,7 @@ import java.nio.charset.Charset;
34 33
 import java.util.ArrayList;
35 34
 import java.util.Arrays;
36 35
 import java.util.Collections;
36
+import java.util.LinkedList;
37 37
 import java.util.List;
38 38
 import java.util.Locale;
39 39
 import java.util.Set;
@@ -42,12 +42,21 @@ import java.util.zip.Deflater;
42 42
 
43 43
 import javax.annotation.Nullable;
44 44
 
45
+import static android.view.View.VISIBLE;
46
+
45 47
 /**
46 48
  * Snapshot utility class allow to screenshot a view.
47 49
  */
48 50
 public class ViewShot implements UIBlock {
49 51
     //region Constants
50
-    static final String ERROR_UNABLE_TO_SNAPSHOT = "E_UNABLE_TO_SNAPSHOT";
52
+    /**
53
+     * Tag fort Class logs.
54
+     */
55
+    private static final String TAG = ViewShot.class.getSimpleName();
56
+    /**
57
+     * Error code that we return to RN.
58
+     */
59
+    public static final String ERROR_UNABLE_TO_SNAPSHOT = "E_UNABLE_TO_SNAPSHOT";
51 60
     /**
52 61
      * pre-allocated output stream size for screenshot. In real life example it will eb around 7Mb.
53 62
      */
@@ -163,6 +172,7 @@ public class ViewShot implements UIBlock {
163 172
         }
164 173
 
165 174
         if (view == null) {
175
+            Log.e(TAG, "No view found with reactTag: " + tag, new AssertionError());
166 176
             promise.reject(ERROR_UNABLE_TO_SNAPSHOT, "No view found with reactTag: " + tag);
167 177
             return;
168 178
         }
@@ -181,7 +191,8 @@ public class ViewShot implements UIBlock {
181 191
             } else if (Results.DATA_URI.equals(result)) {
182 192
                 saveToDataUriString(view);
183 193
             }
184
-        } catch (final Throwable ignored) {
194
+        } catch (final Throwable ex) {
195
+            Log.e(TAG, "Failed to capture view snapshot", ex);
185 196
             promise.reject(ERROR_UNABLE_TO_SNAPSHOT, "Failed to capture view snapshot");
186 197
         }
187 198
     }
@@ -290,6 +301,8 @@ public class ViewShot implements UIBlock {
290 301
      */
291 302
     private Point captureView(@NonNull final View view, @NonNull final OutputStream os) throws IOException {
292 303
         try {
304
+            DebugViews.longDebug(TAG, DebugViews.logViewHierarchy(this.currentActivity));
305
+
293 306
             return captureViewImpl(view, os);
294 307
         } finally {
295 308
             os.close();
@@ -310,7 +323,7 @@ public class ViewShot implements UIBlock {
310 323
             throw new RuntimeException("Impossible to snapshot the view: view is invalid");
311 324
         }
312 325
 
313
-        //evaluate real height
326
+        // evaluate real height
314 327
         if (snapshotContentContainer) {
315 328
             h = 0;
316 329
             ScrollView scrollView = (ScrollView) view;
@@ -322,6 +335,14 @@ public class ViewShot implements UIBlock {
322 335
         final Point resolution = new Point(w, h);
323 336
         Bitmap bitmap = getBitmapForScreenshot(w, h);
324 337
 
338
+        final Paint paint = new Paint();
339
+        paint.setAntiAlias(true);
340
+        paint.setFilterBitmap(true);
341
+        paint.setDither(true);
342
+
343
+        // Uncomment next line if you want to wait attached android studio debugger:
344
+        //   Debug.waitForDebugger();
345
+
325 346
         final Canvas c = new Canvas(bitmap);
326 347
         view.draw(c);
327 348
 
@@ -331,28 +352,24 @@ public class ViewShot implements UIBlock {
331 352
         for (final View child : childrenList) {
332 353
             // skip any child that we don't know how to process
333 354
             if (!(child instanceof TextureView)) continue;
355
+
334 356
             // skip all invisible to user child views
335
-            if (child.getVisibility() != View.VISIBLE) continue;
357
+            if (child.getVisibility() != VISIBLE) continue;
336 358
 
337 359
             final TextureView tvChild = (TextureView) child;
338
-            tvChild.setOpaque(false);
360
+            tvChild.setOpaque(false); // <-- switch off background fill
339 361
 
340
-            final Point offsets = getParentOffsets(view, child);
341
-            final int left = child.getLeft() + child.getPaddingLeft() + offsets.x;
342
-            final int top = child.getTop() + child.getPaddingTop() + offsets.y;
343
-            final int childWidth = child.getWidth();
344
-            final int childHeight = child.getHeight();
345
-            final Rect source = new Rect(0, 0, childWidth, childHeight);
346
-            final RectF destination = new RectF(left, top, left + childWidth, top + childHeight);
362
+            // NOTE (olku): get re-usable bitmap. TextureView should use bitmaps with matching size,
363
+            // otherwise content of the TextureView will be scaled to provided bitmap dimensions
364
+            final Bitmap childBitmapBuffer = tvChild.getBitmap(getExactBitmapForScreenshot(child.getWidth(), child.getHeight()));
347 365
 
348
-            // get re-usable bitmap
349
-            final Bitmap childBitmapBuffer = tvChild.getBitmap(getBitmapForScreenshot(child.getWidth(), child.getHeight()));
366
+            final int countCanvasSave = c.save();
367
+            applyTransformations(c, view, child);
350 368
 
351
-            c.save();
352
-            c.setMatrix(concatMatrix(view, child));
353 369
             // due to re-use of bitmaps for screenshot, we can get bitmap that is bigger in size than requested
354
-            c.drawBitmap(childBitmapBuffer, source, destination, null);
355
-            c.restore();
370
+            c.drawBitmap(childBitmapBuffer, 0, 0, paint);
371
+
372
+            c.restoreToCount(countCanvasSave);
356 373
             recycleBitmap(childBitmapBuffer);
357 374
         }
358 375
 
@@ -380,38 +397,43 @@ public class ViewShot implements UIBlock {
380 397
         return resolution; // return image width and height
381 398
     }
382 399
 
383
-    /** Concat all the transformation matrix's from child to parent. */
400
+    /**
401
+     * Concat all the transformation matrix's from parent to child.
402
+     */
384 403
     @NonNull
385
-    private Matrix concatMatrix(@NonNull final View view, @NonNull final View child){
404
+    @SuppressWarnings("UnusedReturnValue")
405
+    private Matrix applyTransformations(final Canvas c, @NonNull final View root, @NonNull final View child) {
386 406
         final Matrix transform = new Matrix();
407
+        final LinkedList<View> ms = new LinkedList<>();
387 408
 
409
+        // find all parents of the child view
388 410
         View iterator = child;
389 411
         do {
412
+            ms.add(iterator);
390 413
 
391
-            final Matrix m = iterator.getMatrix();
392
-            transform.preConcat(m);
393
-
394
-            iterator = (View)iterator.getParent();
395
-        } while( iterator != view );
414
+            iterator = (View) iterator.getParent();
415
+        } while (iterator != root);
396 416
 
397
-        return transform;
398
-    }
399
-
400
-    @NonNull
401
-    private Point getParentOffsets(@NonNull final View view, @NonNull final View child) {
402
-        int left = 0;
403
-        int top = 0;
417
+        // apply transformations from parent --> child order
418
+        Collections.reverse(ms);
404 419
 
405
-        View parentElem = (View) child.getParent();
406
-        while (parentElem != null) {
407
-            if (parentElem == view) break;
420
+        for (final View v : ms) {
421
+            c.save();
408 422
 
409
-            left += parentElem.getLeft();
410
-            top += parentElem.getTop();
411
-            parentElem = (View) parentElem.getParent();
423
+            // apply each view transformations, so each child will be affected by them
424
+            final float dx = v.getLeft() + ((v != child) ? v.getPaddingLeft() : 0) + v.getTranslationX();
425
+            final float dy = v.getTop() + ((v != child) ? v.getPaddingTop() : 0) + v.getTranslationY();
426
+            c.translate(dx, dy);
427
+            c.rotate(v.getRotation(), v.getPivotX(), v.getPivotY());
428
+            c.scale(v.getScaleX(), v.getScaleY());
429
+
430
+            // compute the matrix just for any future use
431
+            transform.postTranslate(dx, dy);
432
+            transform.postRotate(v.getRotation(), v.getPivotX(), v.getPivotY());
433
+            transform.postScale(v.getScaleX(), v.getScaleY());
412 434
         }
413 435
 
414
-        return new Point(left, top);
436
+        return transform;
415 437
     }
416 438
 
417 439
     @SuppressWarnings("unchecked")
@@ -450,13 +472,32 @@ public class ViewShot implements UIBlock {
450 472
     }
451 473
 
452 474
     /**
453
-     * Try to find a bitmap for screenshot in reusabel set and if not found create a new one.
475
+     * Try to find a bitmap for screenshot in reusable set and if not found create a new one.
454 476
      */
455 477
     @NonNull
456 478
     private static Bitmap getBitmapForScreenshot(final int width, final int height) {
457 479
         synchronized (guardBitmaps) {
458 480
             for (final Bitmap bmp : weakBitmaps) {
459
-                if (bmp.getWidth() * bmp.getHeight() <= width * height) {
481
+                if (bmp.getWidth() >= width && bmp.getHeight() >= height) {
482
+                    weakBitmaps.remove(bmp);
483
+                    bmp.eraseColor(Color.TRANSPARENT);
484
+                    return bmp;
485
+                }
486
+            }
487
+        }
488
+
489
+        return Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
490
+    }
491
+
492
+    /**
493
+     * Try to find a bitmap with exact width and height for screenshot in reusable set and if
494
+     * not found create a new one.
495
+     */
496
+    @NonNull
497
+    private static Bitmap getExactBitmapForScreenshot(final int width, final int height) {
498
+        synchronized (guardBitmaps) {
499
+            for (final Bitmap bmp : weakBitmaps) {
500
+                if (bmp.getWidth() == width && bmp.getHeight() == height) {
460 501
                     weakBitmaps.remove(bmp);
461 502
                     bmp.eraseColor(Color.TRANSPARENT);
462 503
                     return bmp;

+ 18
- 2
example/App.js View File

@@ -263,7 +263,7 @@ export default class App extends Component {
263 263
         <View ref="empty" collapsable={false} />
264 264
         <View style={styles.experimental} ref="complex" collapsable={false}>
265 265
           <Text style={styles.experimentalTitle}>Experimental Stuff</Text>
266
-          <View ref="transformParent" collapsable={false}>
266
+          <View ref="transformParent" collapsable={false} style={{flex: 1, flexDirection: 'row'}}>
267 267
               <View ref="transformInner" collapsable={false} style={styles.experimentalTransform}>
268 268
                 <Text ref="transform" >Transform</Text>
269 269
                 <ART.Surface ref="surface" width={20} height={20}>
@@ -280,6 +280,17 @@ export default class App extends Component {
280 280
                     </ART.Shape>
281 281
                 </ART.Surface>
282 282
               </View>
283
+              <View ref="right" style={styles.experimentalTransformV2}>
284
+                <ART.Surface ref="surface2" width={20} height={20}>
285
+                    <ART.Shape
286
+                            x={0}
287
+                            y={10}
288
+                            d='M2.876,10.6499757 L16.375,18.3966817 C16.715,18.5915989 17.011,18.4606545 17.125,18.3956822 C17.237,18.3307098 17.499,18.1367923 17.499,17.746958 L17.499,2.25254636 C17.499,1.86271212 17.237,1.66879457 17.125,1.6038222 C17.011,1.53884983 16.715,1.4079055 16.375,1.60282262 L2.876,9.34952866 C2.537,9.54544536 2.5,9.86930765 2.5,10.000252 C2.5,10.1301967 2.537,10.4550586 2.876,10.6499757 M16.749,20 C16.364,20 15.98,19.8990429 15.629,19.6971288 L2.13,11.9504227 L2.129,11.9504227 C1.422,11.5445953 1,10.8149056 1,10.000252 C1,9.18459879 1.422,8.45590864 2.129,8.04908162 L15.629,0.302375584 C16.332,-0.10245228 17.173,-0.10045313 17.876,0.306373884 C18.579,0.713200898 18.999,1.44089148 18.999,2.25254636 L18.999,17.746958 C18.999,18.5586129 18.579,19.2863035 17.876,19.6931305 C17.523,19.8970438 17.136,20 16.749,20'
289
+                            fill="red"
290
+                            >
291
+                    </ART.Shape>
292
+                </ART.Surface>
293
+              </View>
283 294
           </View>
284 295
           <View ref="svg" collapsable={false}>
285 296
             <SvgUri
@@ -351,7 +362,12 @@ const styles = StyleSheet.create({
351 362
     margin: 10
352 363
   },
353 364
   experimentalTransform: {
354
-    transform: [{ rotate: '180deg' }]
365
+    transform: [{ rotate: '180deg' }],
366
+    backgroundColor: 'powderblue',
367
+  },
368
+  experimentalTransformV2: {
369
+//    transform: [{ rotate: '180deg' }],
370
+    backgroundColor: 'skyblue',
355 371
   },
356 372
   p1: {
357 373
     marginBottom: 10,

+ 8
- 8
example/android/app/build.gradle View File

@@ -142,16 +142,16 @@ repositories {
142 142
 }
143 143
 
144 144
 dependencies {
145
-    compile project(':react-native-svg')
146
-    compile fileTree(dir: "libs", include: ["*.jar"])
145
+    implementation project(':react-native-svg')
146
+    implementation fileTree(dir: "libs", include: ["*.jar"])
147 147
 
148
-    compile "com.android.support:appcompat-v7:27.+"
149
-    compile "com.facebook.react:react-native:+"  // From node_modules
148
+    implementation "com.android.support:appcompat-v7:27.+"
149
+    implementation "com.facebook.react:react-native:+"  // From node_modules
150 150
 
151
-    compile project(':react-native-view-shot')
152
-    compile project(':gl-react-native')
153
-    compile project(':react-native-maps')
154
-    compile project(':react-native-video')
151
+    implementation project(':react-native-view-shot')
152
+    implementation project(':gl-react-native')
153
+    implementation project(':react-native-maps')
154
+    implementation project(':react-native-video')
155 155
 }
156 156
 
157 157
 // Run this once to be able to run the application with BUCK