From 366e33a4fa0656a2109ea43b5f7f2bd80d73c220 Mon Sep 17 00:00:00 2001 From: Douglas Lowder Date: Thu, 6 Oct 2022 13:06:55 -0700 Subject: [PATCH] [Android][iOS] Upgrade react-native-view-shot to 3.4.0 (#19405) * [Android][iOS] Upgrade react-native-view-shot to 3.4.0 * CHANGELOG * Fix CHANGELOG * Restore fix in RNViewShot.m * Restore Expo changes to RNViewShotModules.java * Update bare-expo Podfile.lock * Remove unused RNViewShotPackage.java --- CHANGELOG.md | 1 + .../api/viewshot/RNViewShotModule.java | 7 +- .../modules/api/viewshot/ViewShot.java | 83 ++++++++++++++----- apps/bare-expo/ios/Podfile.lock | 4 +- apps/bare-expo/package.json | 2 +- apps/native-component-list/package.json | 2 +- packages/expo/bundledNativeModules.json | 2 +- yarn.lock | 8 +- 8 files changed, 76 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 354ba41a1ebc3..0f8e397f3df80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ Package-specific changes not released in any SDK will be added here just before - Updated `@stripe/stripe-react-native` from `0.13.1` to `0.18.1` on iOS. ([#19055](https://github.com/expo/expo/pull/19055) by [@tsapeta](https://github.com/tsapeta)) - Updated `@shopify/flash-list` from `1.1.0` to `1.3.0`. ([#19317](https://github.com/expo/expo/pull/19317) by [@kudo](https://github.com/kudo)) +- Updated `react-native-view-shot` from `3.3.0` to `3.4.0`. ([#19405](https://github.com/expo/expo/pull/19405) by [@douglowder](https://github.com/douglowder)) - Updated `react-native-webview` from `11.23.0` to `11.23.1`. ([#19375](https://github.com/expo/expo/pull/19375) by [@aleqsio](https://github.com/aleqsio)) - Updated `react-native-gesture-handler` from `2.5.0` to `2.7.0`. ([#19362](https://github.com/expo/expo/pull/19362) by [@tsapeta](https://github.com/tsapeta)) - Updated `@react-native-community/netinfo` from `9.3.0` to `9.3.3`. ([#19421](https://github.com/expo/expo/pull/19421) by [@douglowder](https://github.com/douglowder)) diff --git a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/viewshot/RNViewShotModule.java b/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/viewshot/RNViewShotModule.java index 4dd2a1276af0e..0df298f679805 100644 --- a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/viewshot/RNViewShotModule.java +++ b/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/viewshot/RNViewShotModule.java @@ -84,11 +84,12 @@ public void captureRef(int tag, ReadableMap options, Promise promise) { : Formats.PNG; final double quality = options.getDouble("quality"); - final Integer scaleWidth = options.hasKey("width") ? (int) (dm.density * options.getDouble("width")) : null; - final Integer scaleHeight = options.hasKey("height") ? (int) (dm.density * options.getDouble("height")) : null; + final Integer scaleWidth = options.hasKey("width") ? options.getInt("width") : null; + final Integer scaleHeight = options.hasKey("height") ? options.getInt("height") : null; final String resultStreamFormat = options.getString("result"); final String fileName = options.hasKey("fileName") ? options.getString("fileName") : null; final Boolean snapshotContentContainer = options.getBoolean("snapshotContentContainer"); + final boolean handleGLSurfaceView = options.hasKey("handleGLSurfaceViewOnAndroid") && options.getBoolean("handleGLSurfaceViewOnAndroid"); try { File outputFile = null; @@ -102,7 +103,7 @@ public void captureRef(int tag, ReadableMap options, Promise promise) { uiManager.addUIBlock(new ViewShot( tag, extension, imageFormat, quality, scaleWidth, scaleHeight, outputFile, resultStreamFormat, - snapshotContentContainer, reactContext, activity, promise) + snapshotContentContainer, reactContext, activity, handleGLSurfaceView, promise) ); } catch (final Throwable ex) { Log.e(RNVIEW_SHOT, "Failed to snapshot view tag " + tag, ex); diff --git a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/viewshot/ViewShot.java b/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/viewshot/ViewShot.java index 938ec9bf289cc..25cff85c6f627 100644 --- a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/viewshot/ViewShot.java +++ b/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/viewshot/ViewShot.java @@ -8,7 +8,8 @@ import android.graphics.Paint; import android.graphics.Point; import android.net.Uri; - +import android.os.Build; +import android.os.Handler; import androidx.annotation.IntDef; import androidx.annotation.NonNull; import androidx.annotation.StringDef; @@ -16,8 +17,11 @@ import android.os.Build; import android.os.Handler; import android.os.HandlerThread; +import android.os.Looper; import android.util.Base64; import android.util.Log; +import android.view.PixelCopy; +import android.view.SurfaceView; import android.view.TextureView; import android.view.View; import android.view.ViewGroup; @@ -44,6 +48,8 @@ import java.util.Locale; import java.util.Set; import java.util.WeakHashMap; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import java.util.zip.Deflater; import javax.annotation.Nullable; @@ -71,6 +77,10 @@ public class ViewShot implements UIBlock, LifecycleEventListener { * ARGB size in bytes. */ private static final int ARGB_SIZE = 4; + /** + * Wait timeout for surface view capture. + */ + private static final int SURFACE_VIEW_READ_PIXELS_TIMEOUT = 5; private HandlerThread mBgThread; private Handler mBgHandler; @@ -157,6 +167,7 @@ public void run() { private final Boolean snapshotContentContainer; @SuppressWarnings({"unused", "FieldCanBeLocal"}) private final ReactApplicationContext reactContext; + private final boolean handleGLSurfaceView; private final Activity currentActivity; //endregion @@ -174,6 +185,7 @@ public ViewShot( final Boolean snapshotContentContainer, final ReactApplicationContext reactContext, final Activity currentActivity, + final boolean handleGLSurfaceView, final Promise promise) { this.tag = tag; this.extension = extension; @@ -186,6 +198,7 @@ public ViewShot( this.snapshotContentContainer = snapshotContentContainer; this.reactContext = reactContext; this.currentActivity = currentActivity; + this.handleGLSurfaceView = handleGLSurfaceView; this.promise = promise; reactContext.addLifecycleEventListener(this); @@ -405,26 +418,54 @@ private Point captureViewImpl(@NonNull final View view, @NonNull final OutputStr for (final View child : childrenList) { // skip any child that we don't know how to process - if (!(child instanceof TextureView)) continue; - - // skip all invisible to user child views - if (child.getVisibility() != VISIBLE) continue; - - final TextureView tvChild = (TextureView) child; - tvChild.setOpaque(false); // <-- switch off background fill - - // NOTE (olku): get re-usable bitmap. TextureView should use bitmaps with matching size, - // otherwise content of the TextureView will be scaled to provided bitmap dimensions - final Bitmap childBitmapBuffer = tvChild.getBitmap(getExactBitmapForScreenshot(child.getWidth(), child.getHeight())); - - final int countCanvasSave = c.save(); - applyTransformations(c, view, child); - - // due to re-use of bitmaps for screenshot, we can get bitmap that is bigger in size than requested - c.drawBitmap(childBitmapBuffer, 0, 0, paint); - - c.restoreToCount(countCanvasSave); - recycleBitmap(childBitmapBuffer); + if (child instanceof TextureView) { + // skip all invisible to user child views + if (child.getVisibility() != VISIBLE) continue; + + final TextureView tvChild = (TextureView) child; + tvChild.setOpaque(false); // <-- switch off background fill + + // NOTE (olku): get re-usable bitmap. TextureView should use bitmaps with matching size, + // otherwise content of the TextureView will be scaled to provided bitmap dimensions + final Bitmap childBitmapBuffer = tvChild.getBitmap(getExactBitmapForScreenshot(child.getWidth(), child.getHeight())); + + final int countCanvasSave = c.save(); + applyTransformations(c, view, child); + + // due to re-use of bitmaps for screenshot, we can get bitmap that is bigger in size than requested + c.drawBitmap(childBitmapBuffer, 0, 0, paint); + + c.restoreToCount(countCanvasSave); + recycleBitmap(childBitmapBuffer); + } else if (child instanceof SurfaceView && handleGLSurfaceView) { + final SurfaceView svChild = (SurfaceView)child; + final CountDownLatch latch = new CountDownLatch(1); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + final Bitmap childBitmapBuffer = getExactBitmapForScreenshot(child.getWidth(), child.getHeight()); + try { + PixelCopy.request(svChild, childBitmapBuffer, new PixelCopy.OnPixelCopyFinishedListener() { + @Override + public void onPixelCopyFinished(int copyResult) { + final int countCanvasSave = c.save(); + applyTransformations(c, view, child); + c.drawBitmap(childBitmapBuffer, 0, 0, paint); + c.restoreToCount(countCanvasSave); + recycleBitmap(childBitmapBuffer); + latch.countDown(); + } + }, new Handler(Looper.getMainLooper())); + latch.await(SURFACE_VIEW_READ_PIXELS_TIMEOUT, TimeUnit.SECONDS); + } catch (Exception e) { + Log.e(TAG, "Cannot PixelCopy for " + svChild, e); + } + } else { + Bitmap cache = svChild.getDrawingCache(); + if (cache != null) { + c.drawBitmap(svChild.getDrawingCache(), 0, 0, paint); + } + } + } } if (width != null && height != null && (width != w || height != h)) { diff --git a/apps/bare-expo/ios/Podfile.lock b/apps/bare-expo/ios/Podfile.lock index 49258abbd9bd0..79a3177a3b89e 100644 --- a/apps/bare-expo/ios/Podfile.lock +++ b/apps/bare-expo/ios/Podfile.lock @@ -634,7 +634,7 @@ PODS: - React-Core - react-native-slider (4.2.3): - React-Core - - react-native-view-shot (3.3.0): + - react-native-view-shot (3.4.0): - React-Core - react-native-viewpager (5.0.11): - React-Core @@ -1354,7 +1354,7 @@ SPEC CHECKSUMS: react-native-safe-area-context: 6c12e3859b6f27b25de4fee8201cfb858432d8de react-native-segmented-control: 06607462630512ff8eef652ec560e6235a30cc3e react-native-slider: 98b724cd3e44c3454a6d0724e796d4e9c52189ce - react-native-view-shot: da768466e1cd371de50a3a5c722d1e95456b5b2c + react-native-view-shot: a60a98a18c72bcaaaf2138f9aab960ae9b0d96c7 react-native-viewpager: b99b53127d830885917ef84809c5065edd614a78 react-native-webview: d33e2db8925d090871ffeb232dfa50cb3a727581 React-perflogger: 6009895616a455781293950bbd63d53cfc7ffbc5 diff --git a/apps/bare-expo/package.json b/apps/bare-expo/package.json index 75bd4a489d943..916198c2d4c99 100644 --- a/apps/bare-expo/package.json +++ b/apps/bare-expo/package.json @@ -118,7 +118,7 @@ "react-native-screens": "~3.18.0", "react-native-shared-element": "0.8.4", "react-native-svg": "12.3.0", - "react-native-view-shot": "3.3.0", + "react-native-view-shot": "3.4.0", "react-native-webview": "11.23.1", "test-suite": "*" }, diff --git a/apps/native-component-list/package.json b/apps/native-component-list/package.json index 5ac5155b7934b..de53b90c8d124 100644 --- a/apps/native-component-list/package.json +++ b/apps/native-component-list/package.json @@ -155,7 +155,7 @@ "react-native-screens": "~3.18.0", "react-native-shared-element": "0.8.4", "react-native-svg": "12.3.0", - "react-native-view-shot": "3.3.0", + "react-native-view-shot": "3.4.0", "react-native-web": "~0.18.9", "react-native-webview": "11.23.1", "react-navigation": "^4.4.0", diff --git a/packages/expo/bundledNativeModules.json b/packages/expo/bundledNativeModules.json index c31f0e3919e0b..e6f1a9f15b321 100644 --- a/packages/expo/bundledNativeModules.json +++ b/packages/expo/bundledNativeModules.json @@ -99,7 +99,7 @@ "react-native-screens": "~3.18.0", "react-native-shared-element": "0.8.4", "react-native-svg": "12.3.0", - "react-native-view-shot": "3.3.0", + "react-native-view-shot": "3.4.0", "react-native-webview": "11.23.1", "sentry-expo": "~5.0.0", "unimodules-app-loader": "~3.1.0", diff --git a/yarn.lock b/yarn.lock index 3c376360bca21..8ba61f13961f4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17298,10 +17298,10 @@ react-native-svg@12.3.0: css-select "^4.2.1" css-tree "^1.0.0-alpha.39" -react-native-view-shot@3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/react-native-view-shot/-/react-native-view-shot-3.3.0.tgz#7f0c6d2e09e5af770f5b74231a72625b379d60f8" - integrity sha512-dc3ZHCd0lvn1jtSI8bPQDta8YxzCvZ73vA8zzFH4S3TRlXLe8r5DF3wUUBlWv1p/bxbEa/A0J4kMUPeVt/v8TQ== +react-native-view-shot@3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/react-native-view-shot/-/react-native-view-shot-3.4.0.tgz#787b31b2d0525a197864e12aaea214e905e97f9a" + integrity sha512-b0CcWJGO0xLCXRsstIYRUEg/UStrR7uujQV9jFHRIVyPfBH0gRplT7Vlgimr+PX+Xg+9/rCyIKPjqK1Knv8hxg== react-native-web@~0.18.9: version "0.18.9"