expo-screen-capture 5.8.1 → 6.0.0

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/CHANGELOG.md CHANGED
@@ -10,7 +10,21 @@
10
10
 
11
11
  ### 💡 Others
12
12
 
13
- ## 5.8.1 — 2024-01-23
13
+ ## 6.0.0 — 2024-04-18
14
+
15
+ ### 🐛 Bug fixes
16
+
17
+ - Reverse api level constraint on the `DETECT_SCREEN_CAPTURE` permission. ([#27148](https://github.com/expo/expo/pull/27148) by [@alanjhughes](https://github.com/alanjhughes))
18
+ - [Android] Fixes memory leaks caused by the event emitter. ([#28161](https://github.com/expo/expo/pull/28161) by [@lukmccall](https://github.com/lukmccall))
19
+ - [Android] Fix accessing activity too early on bridgeless. ([#28244](https://github.com/expo/expo/pull/28244) by [@alanjhughes](https://github.com/alanjhughes))
20
+
21
+ ### 💡 Others
22
+
23
+ - drop unused web `name` property. ([#27437](https://github.com/expo/expo/pull/27437) by [@EvanBacon](https://github.com/EvanBacon))
24
+ - Native module on iOS is now written in Swift using the Sweet API. ([#26103](https://github.com/expo/expo/pull/26103) by [@fobos531](https://github.com/fobos531))
25
+ - Removed deprecated backward compatible Gradle settings. ([#28083](https://github.com/expo/expo/pull/28083) by [@kudo](https://github.com/kudo))
26
+
27
+ ## 5.8.1 - 2024-01-23
14
28
 
15
29
  ### 🐛 Bug fixes
16
30
 
@@ -1,112 +1,23 @@
1
1
  apply plugin: 'com.android.library'
2
- apply plugin: 'kotlin-android'
3
- apply plugin: 'maven-publish'
4
2
 
5
3
  group = 'host.exp.exponent'
6
- version = '5.8.1'
4
+ version = '6.0.0'
7
5
 
8
6
  def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
9
- if (expoModulesCorePlugin.exists()) {
10
- apply from: expoModulesCorePlugin
11
- applyKotlinExpoModulesCorePlugin()
12
- // Remove this check, but keep the contents after SDK49 support is dropped
13
- if (safeExtGet("expoProvidesDefaultConfig", false)) {
14
- useExpoPublishing()
15
- useCoreDependencies()
16
- }
17
- }
18
-
19
- buildscript {
20
- // Simple helper that allows the root project to override versions declared by this library.
21
- ext.safeExtGet = { prop, fallback ->
22
- rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
23
- }
24
-
25
- // Ensures backward compatibility
26
- ext.getKotlinVersion = {
27
- if (ext.has("kotlinVersion")) {
28
- ext.kotlinVersion()
29
- } else {
30
- ext.safeExtGet("kotlinVersion", "1.8.10")
31
- }
32
- }
33
-
34
- repositories {
35
- mavenCentral()
36
- }
37
-
38
- dependencies {
39
- classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${getKotlinVersion()}")
40
- }
41
- }
42
-
43
- // Remove this if and it's contents, when support for SDK49 is dropped
44
- if (!safeExtGet("expoProvidesDefaultConfig", false)) {
45
- afterEvaluate {
46
- publishing {
47
- publications {
48
- release(MavenPublication) {
49
- from components.release
50
- }
51
- }
52
- repositories {
53
- maven {
54
- url = mavenLocal().url
55
- }
56
- }
57
- }
58
- }
59
- }
7
+ apply from: expoModulesCorePlugin
8
+ applyKotlinExpoModulesCorePlugin()
9
+ useCoreDependencies()
10
+ useDefaultAndroidSdkVersions()
11
+ useExpoPublishing()
60
12
 
61
13
  android {
62
- // Remove this if and it's contents, when support for SDK49 is dropped
63
- if (!safeExtGet("expoProvidesDefaultConfig", false)) {
64
- compileSdkVersion safeExtGet("compileSdkVersion", 34)
65
-
66
- defaultConfig {
67
- minSdkVersion safeExtGet("minSdkVersion", 23)
68
- targetSdkVersion safeExtGet("targetSdkVersion", 34)
69
- }
70
-
71
- publishing {
72
- singleVariant("release") {
73
- withSourcesJar()
74
- }
75
- }
76
-
77
- lintOptions {
78
- abortOnError false
79
- }
80
- }
81
-
82
- def agpVersion = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION
83
- if (agpVersion.tokenize('.')[0].toInteger() < 8) {
84
- compileOptions {
85
- sourceCompatibility JavaVersion.VERSION_11
86
- targetCompatibility JavaVersion.VERSION_11
87
- }
88
-
89
- kotlinOptions {
90
- jvmTarget = JavaVersion.VERSION_11.majorVersion
91
- }
92
- }
93
-
94
14
  namespace "expo.modules.screencapture"
95
15
  defaultConfig {
96
16
  versionCode 7
97
- versionName '5.8.1'
17
+ versionName '6.0.0'
98
18
  }
99
19
  }
100
20
 
101
- repositories {
102
- mavenCentral()
103
- }
104
-
105
21
  dependencies {
106
- // Remove this if and it's contents, when support for SDK49 is dropped
107
- if (!safeExtGet("expoProvidesDefaultConfig", false)) {
108
- implementation project(':expo-modules-core')
109
- implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${getKotlinVersion()}"
110
- }
111
22
  implementation 'androidx.appcompat:appcompat:1.2.0'
112
23
  }
@@ -1,5 +1,5 @@
1
1
  <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
2
  <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
3
3
  <uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
4
- <uses-permission android:name="android.permission.DETECT_SCREEN_CAPTURE" android:maxSdkVersion="34" />
4
+ <uses-permission android:name="android.permission.DETECT_SCREEN_CAPTURE" android:minSdkVersion="34" />
5
5
  </manifest>
@@ -20,6 +20,8 @@ class ScreenCaptureModule : Module() {
20
20
  private val currentActivity
21
21
  get() = appContext.currentActivity ?: throw Exceptions.MissingActivity()
22
22
  private var screenCaptureCallback: Activity.ScreenCaptureCallback? = null
23
+ private var screenshotEventEmitter: ScreenshotEventEmitter? = null
24
+ private var isRegistered = false
23
25
 
24
26
  override fun definition() = ModuleDefinition {
25
27
  Name("ExpoScreenCapture")
@@ -31,9 +33,8 @@ class ScreenCaptureModule : Module() {
31
33
  screenCaptureCallback = Activity.ScreenCaptureCallback {
32
34
  sendEvent(eventName)
33
35
  }
34
- currentActivity.registerScreenCaptureCallback(currentActivity.mainExecutor, screenCaptureCallback!!)
35
36
  } else {
36
- ScreenshotEventEmitter(context) {
37
+ screenshotEventEmitter = ScreenshotEventEmitter(context) {
37
38
  sendEvent(eventName)
38
39
  }
39
40
  }
@@ -55,15 +56,26 @@ class ScreenCaptureModule : Module() {
55
56
  }
56
57
  }
57
58
 
58
- AsyncFunction("preventScreenCapture") {
59
+ AsyncFunction<Unit>("preventScreenCapture") {
60
+ registerCallback()
59
61
  currentActivity.window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
60
62
  }.runOnQueue(Queues.MAIN)
61
63
 
62
- AsyncFunction("allowScreenCapture") {
64
+ AsyncFunction<Unit>("allowScreenCapture") {
65
+ registerCallback()
63
66
  currentActivity.window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
64
67
  }.runOnQueue(Queues.MAIN)
65
68
 
69
+ OnActivityEntersForeground {
70
+ screenshotEventEmitter?.onHostResume()
71
+ }
72
+
73
+ OnActivityEntersBackground {
74
+ screenshotEventEmitter?.onHostPause()
75
+ }
76
+
66
77
  OnDestroy {
78
+ screenshotEventEmitter?.onHostDestroy()
67
79
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
68
80
  screenCaptureCallback?.let {
69
81
  currentActivity.unregisterScreenCaptureCallback(it)
@@ -71,4 +83,14 @@ class ScreenCaptureModule : Module() {
71
83
  }
72
84
  }
73
85
  }
86
+
87
+ private fun registerCallback() {
88
+ if (isRegistered) {
89
+ return
90
+ }
91
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
92
+ currentActivity.registerScreenCaptureCallback(currentActivity.mainExecutor, screenCaptureCallback!!)
93
+ isRegistered = true
94
+ }
95
+ }
74
96
  }
@@ -18,23 +18,24 @@ class ScreenshotEventEmitter(val context: Context, onCapture: () -> Unit) : Life
18
18
  private var isListening: Boolean = true
19
19
  private var previousPath: String = ""
20
20
 
21
- init {
22
- val contentObserver: ContentObserver = object : ContentObserver(Handler(Looper.getMainLooper())) {
23
- override fun onChange(selfChange: Boolean, uri: Uri?) {
24
- super.onChange(selfChange, uri)
25
- if (isListening) {
26
- if (!hasPermissions(context)) {
27
- Log.e("expo-screen-capture", "Could not listen for screenshots, do not have READ_EXTERNAL_STORAGE permission.")
28
- return
29
- }
30
- val path = getFilePathFromContentResolver(context, uri)
31
- if (path != null && isPathOfNewScreenshot(path)) {
32
- previousPath = path
33
- onCapture()
34
- }
21
+ private val contentObserver: ContentObserver = object : ContentObserver(Handler(Looper.getMainLooper())) {
22
+ override fun onChange(selfChange: Boolean, uri: Uri?) {
23
+ super.onChange(selfChange, uri)
24
+ if (isListening) {
25
+ if (!hasPermissions(context)) {
26
+ Log.e("expo-screen-capture", "Could not listen for screenshots, do not have READ_EXTERNAL_STORAGE permission.")
27
+ return
28
+ }
29
+ val path = getFilePathFromContentResolver(context, uri)
30
+ if (path != null && isPathOfNewScreenshot(path)) {
31
+ previousPath = path
32
+ onCapture()
35
33
  }
36
34
  }
37
35
  }
36
+ }
37
+
38
+ init {
38
39
  context.contentResolver.registerContentObserver(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true, contentObserver)
39
40
  }
40
41
 
@@ -47,7 +48,7 @@ class ScreenshotEventEmitter(val context: Context, onCapture: () -> Unit) : Life
47
48
  }
48
49
 
49
50
  override fun onHostDestroy() {
50
- // Do nothing
51
+ context.contentResolver.unregisterContentObserver(contentObserver)
51
52
  }
52
53
 
53
54
  private fun hasPermissions(context: Context): Boolean {
@@ -1,5 +1,3 @@
1
- declare const _default: {
2
- readonly name: string;
3
- };
1
+ declare const _default: {};
4
2
  export default _default;
5
3
  //# sourceMappingURL=ExpoScreenCapture.web.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ExpoScreenCapture.web.d.ts","sourceRoot":"","sources":["../src/ExpoScreenCapture.web.ts"],"names":[],"mappings":";;;AAAA,wBAIE"}
1
+ {"version":3,"file":"ExpoScreenCapture.web.d.ts","sourceRoot":"","sources":["../src/ExpoScreenCapture.web.ts"],"names":[],"mappings":";AAAA,wBAAkB"}
@@ -1,6 +1,2 @@
1
- export default {
2
- get name() {
3
- return 'ExpoScreenCapture';
4
- },
5
- };
1
+ export default {};
6
2
  //# sourceMappingURL=ExpoScreenCapture.web.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ExpoScreenCapture.web.js","sourceRoot":"","sources":["../src/ExpoScreenCapture.web.ts"],"names":[],"mappings":"AAAA,eAAe;IACb,IAAI,IAAI;QACN,OAAO,mBAAmB,CAAC;IAC7B,CAAC;CACF,CAAC","sourcesContent":["export default {\n get name(): string {\n return 'ExpoScreenCapture';\n },\n};\n"]}
1
+ {"version":3,"file":"ExpoScreenCapture.web.js","sourceRoot":"","sources":["../src/ExpoScreenCapture.web.ts"],"names":[],"mappings":"AAAA,eAAe,EAAE,CAAC","sourcesContent":["export default {};\n"]}
@@ -32,7 +32,7 @@ export declare function allowScreenCaptureAsync(key?: string): Promise<void>;
32
32
  /**
33
33
  * A React hook to prevent screen capturing for as long as the owner component is mounted.
34
34
  *
35
- * @param key. If provided, this will prevent multiple instances of this hook or the
35
+ * @param key If provided, this will prevent multiple instances of this hook or the
36
36
  * `preventScreenCaptureAsync` and `allowScreenCaptureAsync` methods from conflicting with each other.
37
37
  * This argument is useful if you have multiple active components using the `allowScreenCaptureAsync`
38
38
  * hook. Defaults to `'default'`.
@@ -59,7 +59,7 @@ export async function allowScreenCaptureAsync(key = 'default') {
59
59
  /**
60
60
  * A React hook to prevent screen capturing for as long as the owner component is mounted.
61
61
  *
62
- * @param key. If provided, this will prevent multiple instances of this hook or the
62
+ * @param key If provided, this will prevent multiple instances of this hook or the
63
63
  * `preventScreenCaptureAsync` and `allowScreenCaptureAsync` methods from conflicting with each other.
64
64
  * This argument is useful if you have multiple active components using the `allowScreenCaptureAsync`
65
65
  * hook. Defaults to `'default'`.
@@ -1 +1 @@
1
- {"version":3,"file":"ScreenCapture.js","sourceRoot":"","sources":["../src/ScreenCapture.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n EventEmitter,\n Subscription,\n UnavailabilityError,\n PermissionResponse,\n PermissionStatus,\n createPermissionHook,\n PermissionHookOptions,\n} from 'expo-modules-core';\nimport { useEffect } from 'react';\n\nimport ExpoScreenCapture from './ExpoScreenCapture';\n\nconst activeTags: Set<string> = new Set();\nconst emitter = new EventEmitter(ExpoScreenCapture);\n\nconst onScreenshotEventName = 'onScreenshot';\n\n// @needsAudit\n/**\n * Returns whether the Screen Capture API is available on the current device.\n *\n * @returns A promise that resolves to a `boolean` indicating whether the Screen Capture API is available on the current\n * device. Currently, this resolves to `true` on Android and iOS only.\n */\nexport async function isAvailableAsync(): Promise<boolean> {\n return !!ExpoScreenCapture.preventScreenCapture && !!ExpoScreenCapture.allowScreenCapture;\n}\n\n// @needsAudit\n/**\n * Prevents screenshots and screen recordings until `allowScreenCaptureAsync` is called or the app is restarted. If you are\n * already preventing screen capture, this method does nothing (unless you pass a new and unique `key`).\n *\n * > Please note that on iOS, this will only prevent screen recordings, and is only available on\n * iOS 11 and newer. On older iOS versions, this method does nothing.\n *\n * @param key Optional. If provided, this will help prevent multiple instances of the `preventScreenCaptureAsync`\n * and `allowScreenCaptureAsync` methods (and `usePreventScreenCapture` hook) from conflicting with each other.\n * When using multiple keys, you'll have to re-allow each one in order to re-enable screen capturing.\n * Defaults to `'default'`.\n */\nexport async function preventScreenCaptureAsync(key: string = 'default'): Promise<void> {\n if (!ExpoScreenCapture.preventScreenCapture) {\n throw new UnavailabilityError('ScreenCapture', 'preventScreenCaptureAsync');\n }\n\n if (!activeTags.has(key)) {\n activeTags.add(key);\n await ExpoScreenCapture.preventScreenCapture();\n }\n}\n\n// @needsAudit\n/**\n * Re-allows the user to screen record or screenshot your app. If you haven't called\n * `preventScreenCapture()` yet, this method does nothing.\n *\n * @param key This will prevent multiple instances of the `preventScreenCaptureAsync` and\n * `allowScreenCaptureAsync` methods from conflicting with each other. If provided, the value must\n * be the same as the key passed to `preventScreenCaptureAsync` in order to re-enable screen\n * capturing. Defaults to 'default'.\n */\nexport async function allowScreenCaptureAsync(key: string = 'default'): Promise<void> {\n if (!ExpoScreenCapture.preventScreenCapture) {\n throw new UnavailabilityError('ScreenCapture', 'allowScreenCaptureAsync');\n }\n\n activeTags.delete(key);\n if (activeTags.size === 0) {\n await ExpoScreenCapture.allowScreenCapture();\n }\n}\n\n// @needsAudit\n/**\n * A React hook to prevent screen capturing for as long as the owner component is mounted.\n *\n * @param key. If provided, this will prevent multiple instances of this hook or the\n * `preventScreenCaptureAsync` and `allowScreenCaptureAsync` methods from conflicting with each other.\n * This argument is useful if you have multiple active components using the `allowScreenCaptureAsync`\n * hook. Defaults to `'default'`.\n */\nexport function usePreventScreenCapture(key: string = 'default'): void {\n useEffect(() => {\n preventScreenCaptureAsync(key);\n\n return () => {\n allowScreenCaptureAsync(key);\n };\n }, [key]);\n}\n\n// @needsAudit\n/**\n * Adds a listener that will fire whenever the user takes a screenshot while the app is foregrounded.\n * On Android, this method requires the `READ_EXTERNAL_STORAGE` permission. You can request this\n * with [`MediaLibrary.requestPermissionsAsync()`](./media-library/#medialibraryrequestpermissionsasync).\n *\n * @param listener The function that will be executed when the user takes a screenshot.\n * This function accepts no arguments.\n *\n * @return A `Subscription` object that you can use to unregister the listener, either by calling\n * `remove()` or passing it to `removeScreenshotListener`.\n */\nexport function addScreenshotListener(listener: () => void): Subscription {\n return emitter.addListener<void>(onScreenshotEventName, listener);\n}\n\n// @needsAudit\n/**\n * Removes the subscription you provide, so that you are no longer listening for screenshots.\n *\n * If you prefer, you can also call `remove()` on that `Subscription` object, for example:\n *\n * ```ts\n * let mySubscription = addScreenshotListener(() => {\n * console.log(\"You took a screenshot!\");\n * });\n * ...\n * mySubscription.remove();\n * // OR\n * removeScreenshotListener(mySubscription);\n * ```\n *\n * @param subscription Subscription returned by `addScreenshotListener`.\n */\nexport function removeScreenshotListener(subscription: Subscription) {\n emitter.removeSubscription(subscription);\n}\n\n/**\n * Checks user's permissions for detecting when a screenshot is taken.\n * > Only Android requires additional permissions to detect screenshots. On iOS devices, this method will always resolve to a `granted` permission response.\n * @return A promise that resolves to a [PermissionResponse](#permissionresponse) object.\n */\nexport async function getPermissionsAsync(): Promise<PermissionResponse> {\n if (ExpoScreenCapture.getPermissionsAsync) {\n return ExpoScreenCapture.getPermissionsAsync();\n }\n return defaultPermissionsResponse;\n}\n\n/**\n * Asks the user to grant permissions necessary for detecting when a screenshot is taken.\n * > Only Android requires additional permissions to detect screenshots. On iOS devices, this method will always resolve to a `granted` permission response.\n * @return A promise that resolves to a [PermissionResponse](#permissionresponse) object.\n * */\nexport async function requestPermissionsAsync(): Promise<PermissionResponse> {\n if (ExpoScreenCapture.requestPermissionsAsync) {\n return ExpoScreenCapture.requestPermissionsAsync();\n }\n return defaultPermissionsResponse;\n}\n\n/**\n * Check or request permissions necessary for detecting when a screenshot is taken.\n * This uses both [`requestPermissionsAsync`](#screencapturerequestpermissionsasync) and [`getPermissionsAsync`](#screencapturegetpermissionsasync) to interact with the permissions.\n *\n * @example\n * ```js\n * const [status, requestPermission] = ScreenCapture.useScreenCapturePermissions();\n * ```\n */\nexport const usePermissions = createPermissionHook({\n getMethod: getPermissionsAsync,\n requestMethod: requestPermissionsAsync,\n});\n\nconst defaultPermissionsResponse: PermissionResponse = {\n granted: true,\n expires: 'never',\n canAskAgain: true,\n status: PermissionStatus.GRANTED,\n};\n\nexport { Subscription, PermissionResponse, PermissionStatus, PermissionHookOptions };\n"]}
1
+ {"version":3,"file":"ScreenCapture.js","sourceRoot":"","sources":["../src/ScreenCapture.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n EventEmitter,\n Subscription,\n UnavailabilityError,\n PermissionResponse,\n PermissionStatus,\n createPermissionHook,\n PermissionHookOptions,\n} from 'expo-modules-core';\nimport { useEffect } from 'react';\n\nimport ExpoScreenCapture from './ExpoScreenCapture';\n\nconst activeTags: Set<string> = new Set();\nconst emitter = new EventEmitter(ExpoScreenCapture);\n\nconst onScreenshotEventName = 'onScreenshot';\n\n// @needsAudit\n/**\n * Returns whether the Screen Capture API is available on the current device.\n *\n * @returns A promise that resolves to a `boolean` indicating whether the Screen Capture API is available on the current\n * device. Currently, this resolves to `true` on Android and iOS only.\n */\nexport async function isAvailableAsync(): Promise<boolean> {\n return !!ExpoScreenCapture.preventScreenCapture && !!ExpoScreenCapture.allowScreenCapture;\n}\n\n// @needsAudit\n/**\n * Prevents screenshots and screen recordings until `allowScreenCaptureAsync` is called or the app is restarted. If you are\n * already preventing screen capture, this method does nothing (unless you pass a new and unique `key`).\n *\n * > Please note that on iOS, this will only prevent screen recordings, and is only available on\n * iOS 11 and newer. On older iOS versions, this method does nothing.\n *\n * @param key Optional. If provided, this will help prevent multiple instances of the `preventScreenCaptureAsync`\n * and `allowScreenCaptureAsync` methods (and `usePreventScreenCapture` hook) from conflicting with each other.\n * When using multiple keys, you'll have to re-allow each one in order to re-enable screen capturing.\n * Defaults to `'default'`.\n */\nexport async function preventScreenCaptureAsync(key: string = 'default'): Promise<void> {\n if (!ExpoScreenCapture.preventScreenCapture) {\n throw new UnavailabilityError('ScreenCapture', 'preventScreenCaptureAsync');\n }\n\n if (!activeTags.has(key)) {\n activeTags.add(key);\n await ExpoScreenCapture.preventScreenCapture();\n }\n}\n\n// @needsAudit\n/**\n * Re-allows the user to screen record or screenshot your app. If you haven't called\n * `preventScreenCapture()` yet, this method does nothing.\n *\n * @param key This will prevent multiple instances of the `preventScreenCaptureAsync` and\n * `allowScreenCaptureAsync` methods from conflicting with each other. If provided, the value must\n * be the same as the key passed to `preventScreenCaptureAsync` in order to re-enable screen\n * capturing. Defaults to 'default'.\n */\nexport async function allowScreenCaptureAsync(key: string = 'default'): Promise<void> {\n if (!ExpoScreenCapture.preventScreenCapture) {\n throw new UnavailabilityError('ScreenCapture', 'allowScreenCaptureAsync');\n }\n\n activeTags.delete(key);\n if (activeTags.size === 0) {\n await ExpoScreenCapture.allowScreenCapture();\n }\n}\n\n// @needsAudit\n/**\n * A React hook to prevent screen capturing for as long as the owner component is mounted.\n *\n * @param key If provided, this will prevent multiple instances of this hook or the\n * `preventScreenCaptureAsync` and `allowScreenCaptureAsync` methods from conflicting with each other.\n * This argument is useful if you have multiple active components using the `allowScreenCaptureAsync`\n * hook. Defaults to `'default'`.\n */\nexport function usePreventScreenCapture(key: string = 'default'): void {\n useEffect(() => {\n preventScreenCaptureAsync(key);\n\n return () => {\n allowScreenCaptureAsync(key);\n };\n }, [key]);\n}\n\n// @needsAudit\n/**\n * Adds a listener that will fire whenever the user takes a screenshot while the app is foregrounded.\n * On Android, this method requires the `READ_EXTERNAL_STORAGE` permission. You can request this\n * with [`MediaLibrary.requestPermissionsAsync()`](./media-library/#medialibraryrequestpermissionsasync).\n *\n * @param listener The function that will be executed when the user takes a screenshot.\n * This function accepts no arguments.\n *\n * @return A `Subscription` object that you can use to unregister the listener, either by calling\n * `remove()` or passing it to `removeScreenshotListener`.\n */\nexport function addScreenshotListener(listener: () => void): Subscription {\n return emitter.addListener<void>(onScreenshotEventName, listener);\n}\n\n// @needsAudit\n/**\n * Removes the subscription you provide, so that you are no longer listening for screenshots.\n *\n * If you prefer, you can also call `remove()` on that `Subscription` object, for example:\n *\n * ```ts\n * let mySubscription = addScreenshotListener(() => {\n * console.log(\"You took a screenshot!\");\n * });\n * ...\n * mySubscription.remove();\n * // OR\n * removeScreenshotListener(mySubscription);\n * ```\n *\n * @param subscription Subscription returned by `addScreenshotListener`.\n */\nexport function removeScreenshotListener(subscription: Subscription) {\n emitter.removeSubscription(subscription);\n}\n\n/**\n * Checks user's permissions for detecting when a screenshot is taken.\n * > Only Android requires additional permissions to detect screenshots. On iOS devices, this method will always resolve to a `granted` permission response.\n * @return A promise that resolves to a [PermissionResponse](#permissionresponse) object.\n */\nexport async function getPermissionsAsync(): Promise<PermissionResponse> {\n if (ExpoScreenCapture.getPermissionsAsync) {\n return ExpoScreenCapture.getPermissionsAsync();\n }\n return defaultPermissionsResponse;\n}\n\n/**\n * Asks the user to grant permissions necessary for detecting when a screenshot is taken.\n * > Only Android requires additional permissions to detect screenshots. On iOS devices, this method will always resolve to a `granted` permission response.\n * @return A promise that resolves to a [PermissionResponse](#permissionresponse) object.\n * */\nexport async function requestPermissionsAsync(): Promise<PermissionResponse> {\n if (ExpoScreenCapture.requestPermissionsAsync) {\n return ExpoScreenCapture.requestPermissionsAsync();\n }\n return defaultPermissionsResponse;\n}\n\n/**\n * Check or request permissions necessary for detecting when a screenshot is taken.\n * This uses both [`requestPermissionsAsync`](#screencapturerequestpermissionsasync) and [`getPermissionsAsync`](#screencapturegetpermissionsasync) to interact with the permissions.\n *\n * @example\n * ```js\n * const [status, requestPermission] = ScreenCapture.useScreenCapturePermissions();\n * ```\n */\nexport const usePermissions = createPermissionHook({\n getMethod: getPermissionsAsync,\n requestMethod: requestPermissionsAsync,\n});\n\nconst defaultPermissionsResponse: PermissionResponse = {\n granted: true,\n expires: 'never',\n canAskAgain: true,\n status: PermissionStatus.GRANTED,\n};\n\nexport { Subscription, PermissionResponse, PermissionStatus, PermissionHookOptions };\n"]}
@@ -3,5 +3,8 @@
3
3
  "platforms": ["ios", "android", "web"],
4
4
  "android": {
5
5
  "modules": ["expo.modules.screencapture.ScreenCaptureModule"]
6
+ },
7
+ "ios": {
8
+ "modules": ["ScreenCaptureModule"]
6
9
  }
7
10
  }
@@ -3,7 +3,7 @@ require 'json'
3
3
  package = JSON.parse(File.read(File.join(__dir__, '..', 'package.json')))
4
4
 
5
5
  Pod::Spec.new do |s|
6
- s.name = 'EXScreenCapture'
6
+ s.name = 'ExpoScreenCapture'
7
7
  s.version = package['version']
8
8
  s.summary = package['description']
9
9
  s.description = package['description']
@@ -11,15 +11,22 @@ Pod::Spec.new do |s|
11
11
  s.author = package['author']
12
12
  s.homepage = package['homepage']
13
13
  s.platform = :ios, '13.4'
14
+ s.swift_version = '5.4'
14
15
  s.source = { git: 'https://github.com/expo/expo.git' }
15
16
  s.static_framework = true
16
17
 
17
18
  s.dependency 'ExpoModulesCore'
18
19
 
20
+ # Swift/Objective-C compatibility
21
+ s.pod_target_xcconfig = {
22
+ 'DEFINES_MODULE' => 'YES',
23
+ 'SWIFT_COMPILATION_MODE' => 'wholemodule'
24
+ }
25
+
19
26
  if !$ExpoUseSources&.include?(package['name']) && ENV['EXPO_USE_SOURCE'].to_i == 0 && File.exist?("#{s.name}.xcframework") && Gem::Version.new(Pod::VERSION) >= Gem::Version.new('1.10.0')
20
- s.source_files = "#{s.name}/**/*.h"
27
+ s.source_files = "**/*.h"
21
28
  s.vendored_frameworks = "#{s.name}.xcframework"
22
29
  else
23
- s.source_files = "#{s.name}/**/*.{h,m}"
30
+ s.source_files = "**/*.{h,m,swift}"
24
31
  end
25
32
  end
@@ -0,0 +1,72 @@
1
+ import ExpoModulesCore
2
+
3
+ let onScreenshotEventName = "onScreenshot"
4
+
5
+ public final class ScreenCaptureModule: Module {
6
+ private var isBeingObserved = false
7
+ private var isListening = false
8
+ private var blockView = UIView()
9
+
10
+ public func definition() -> ModuleDefinition {
11
+ Name("ExpoScreenCapture")
12
+
13
+ Events(onScreenshotEventName)
14
+
15
+ OnCreate {
16
+ let boundLength = max(UIScreen.main.bounds.size.width, UIScreen.main.bounds.size.height)
17
+ blockView.frame = CGRect(x: 0, y: 0, width: boundLength, height: boundLength)
18
+ blockView.backgroundColor = .black
19
+ }
20
+
21
+ OnStartObserving {
22
+ self.setIsBeing(observed: true)
23
+ }
24
+
25
+ OnStopObserving {
26
+ self.setIsBeing(observed: false)
27
+ }
28
+
29
+ AsyncFunction("preventScreenCapture") {
30
+ // If already recording, block it
31
+ self.preventScreenRecording()
32
+
33
+ NotificationCenter.default.addObserver(self, selector: #selector(self.preventScreenRecording), name: UIScreen.capturedDidChangeNotification, object: nil)
34
+ }.runOnQueue(.main)
35
+
36
+ AsyncFunction("allowScreenCapture") {
37
+ NotificationCenter.default.removeObserver(self, name: UIScreen.capturedDidChangeNotification, object: nil)
38
+ }
39
+ }
40
+
41
+ private func setIsBeing(observed: Bool) {
42
+ self.isBeingObserved = observed
43
+ let shouldListen = self.isBeingObserved
44
+
45
+ if shouldListen && !isListening {
46
+ // swiftlint:disable:next line_length
47
+ NotificationCenter.default.addObserver(self, selector: #selector(self.listenForScreenCapture), name: UIApplication.userDidTakeScreenshotNotification, object: nil)
48
+ isListening = true
49
+ } else if !shouldListen && isListening {
50
+ NotificationCenter.default.removeObserver(self, name: UIApplication.userDidTakeScreenshotNotification, object: nil)
51
+ isListening = false
52
+ }
53
+ }
54
+
55
+ @objc
56
+ func preventScreenRecording() {
57
+ let isCaptured = UIScreen.main.isCaptured
58
+
59
+ if isCaptured {
60
+ UIApplication.shared.keyWindow?.subviews.first?.addSubview(blockView)
61
+ } else {
62
+ blockView.removeFromSuperview()
63
+ }
64
+ }
65
+
66
+ @objc
67
+ func listenForScreenCapture() {
68
+ sendEvent(onScreenshotEventName, [
69
+ "body": nil
70
+ ])
71
+ }
72
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-screen-capture",
3
- "version": "5.8.1",
3
+ "version": "6.0.0",
4
4
  "description": "ExpoScreenCapture standalone module",
5
5
  "main": "build/ScreenCapture.js",
6
6
  "types": "build/ScreenCapture.d.ts",
@@ -39,5 +39,5 @@
39
39
  "peerDependencies": {
40
40
  "expo": "*"
41
41
  },
42
- "gitHead": "7fb94e3c0598d0e2d428184b16eec5ec67d80388"
42
+ "gitHead": "4165b8d72e1b9a1889c2767534cc619e21468110"
43
43
  }
@@ -1,5 +1 @@
1
- export default {
2
- get name(): string {
3
- return 'ExpoScreenCapture';
4
- },
5
- };
1
+ export default {};
@@ -76,7 +76,7 @@ export async function allowScreenCaptureAsync(key: string = 'default'): Promise<
76
76
  /**
77
77
  * A React hook to prevent screen capturing for as long as the owner component is mounted.
78
78
  *
79
- * @param key. If provided, this will prevent multiple instances of this hook or the
79
+ * @param key If provided, this will prevent multiple instances of this hook or the
80
80
  * `preventScreenCaptureAsync` and `allowScreenCaptureAsync` methods from conflicting with each other.
81
81
  * This argument is useful if you have multiple active components using the `allowScreenCaptureAsync`
82
82
  * hook. Defaults to `'default'`.
@@ -1,8 +0,0 @@
1
- // Copyright © 2018 650 Industries. All rights reserved.
2
-
3
- #import <ExpoModulesCore/EXExportedModule.h>
4
- #import <ExpoModulesCore/EXModuleRegistryConsumer.h>
5
- #import <ExpoModulesCore/EXEventEmitter.h>
6
-
7
- @interface EXScreenCaptureModule : EXExportedModule <EXModuleRegistryConsumer, EXEventEmitter>
8
- @end
@@ -1,116 +0,0 @@
1
- // Copyright 2018-present 650 Industries. All rights reserved.
2
-
3
- #import <EXScreenCapture/EXScreenCaptureModule.h>
4
-
5
- #import <ExpoModulesCore/EXEventEmitterService.h>
6
-
7
- static NSString * const onScreenshotEventName = @"onScreenshot";
8
-
9
- @interface EXScreenCaptureModule ()
10
-
11
- @property (nonatomic, weak) EXModuleRegistry *moduleRegistry;
12
- @property (nonatomic, assign) BOOL isListening;
13
- @property (nonatomic, assign) BOOL isBeingObserved;
14
- @property (nonatomic, weak) id<EXEventEmitterService> eventEmitter;
15
-
16
- @end
17
-
18
- @implementation EXScreenCaptureModule {
19
- UIView *_blockView;
20
- }
21
-
22
- EX_EXPORT_MODULE(ExpoScreenCapture);
23
-
24
- # pragma mark - EXModuleRegistryConsumer
25
-
26
- - (void)setModuleRegistry:(EXModuleRegistry *)moduleRegistry
27
- {
28
- _moduleRegistry = moduleRegistry;
29
- _eventEmitter = [moduleRegistry getModuleImplementingProtocol:@protocol(EXEventEmitterService)];
30
- }
31
-
32
- - (instancetype)init {
33
- if (self = [super init]) {
34
- CGFloat boundLength = MAX([[UIScreen mainScreen] bounds].size.width, [[UIScreen mainScreen] bounds].size.height);
35
- _blockView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, boundLength, boundLength)];
36
- _blockView.backgroundColor = UIColor.blackColor;
37
- }
38
- return self;
39
- }
40
-
41
- # pragma mark - Exported methods
42
-
43
- EX_EXPORT_METHOD_AS(preventScreenCapture,
44
- preventScreenCaptureWithResolver:(EXPromiseResolveBlock)resolve
45
- reject:(EXPromiseRejectBlock)reject)
46
- {
47
- // If already recording, block it
48
- dispatch_async(dispatch_get_main_queue(), ^{
49
- [self preventScreenRecording];
50
- });
51
-
52
- // Avoid setting duplicate observers
53
- [[NSNotificationCenter defaultCenter] removeObserver:self name:UIScreenCapturedDidChangeNotification object:nil];
54
-
55
- [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(preventScreenRecording) name:UIScreenCapturedDidChangeNotification object:nil];
56
-
57
- resolve([NSNull null]);
58
- }
59
-
60
- EX_EXPORT_METHOD_AS(allowScreenCapture,
61
- allowScreenCaptureWithResolver:(EXPromiseResolveBlock)resolve
62
- rejecter:(EXPromiseRejectBlock)reject)
63
- {
64
- [[NSNotificationCenter defaultCenter] removeObserver:self name:UIScreenCapturedDidChangeNotification object:nil];
65
-
66
- resolve([NSNull null]);
67
- }
68
-
69
- - (void)preventScreenRecording {
70
- BOOL isCaptured = [[UIScreen mainScreen] isCaptured];
71
-
72
- if (isCaptured) {
73
- [UIApplication.sharedApplication.keyWindow.subviews.firstObject addSubview:_blockView];
74
- } else {
75
- [_blockView removeFromSuperview];
76
- }
77
- }
78
-
79
- # pragma mark - EXEventEmitter
80
-
81
- - (NSArray<NSString *> *)supportedEvents
82
- {
83
- return @[onScreenshotEventName];
84
- }
85
-
86
- - (void)startObserving
87
- {
88
- [self setIsBeingObserved:YES];
89
- }
90
-
91
- - (void)stopObserving
92
- {
93
- [self setIsBeingObserved:NO];
94
- }
95
-
96
- - (void)setIsBeingObserved:(BOOL)isBeingObserved
97
- {
98
- _isBeingObserved = isBeingObserved;
99
- BOOL shouldListen = _isBeingObserved;
100
- if (shouldListen && !_isListening) {
101
- // Avoid setting duplicate observers
102
- [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationUserDidTakeScreenshotNotification object:nil];
103
- [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(listenForScreenCapture) name:UIApplicationUserDidTakeScreenshotNotification object:nil];
104
- _isListening = YES;
105
- } else if (!shouldListen && _isListening) {
106
- [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationUserDidTakeScreenshotNotification object:nil];
107
- _isListening = NO;
108
- }
109
- }
110
-
111
- - (void)listenForScreenCapture
112
- {
113
- [_eventEmitter sendEventWithName:onScreenshotEventName body:nil];
114
- }
115
-
116
- @end