Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Access the Screen Time API for iOS and Wellbeing API for Android (coming soon).
- [Add FamilyControls capability to your app](#add-familycontrols-capability-to-your-app)
- [Request Family Controls capabilities](#request-family-controls-capabilities)
- [Set up for Expo](#set-up-for-expo)
- [App Store purchase restrictions](#app-store-purchase-restrictions)
- [Sample code](#sample-code)
- [Contributing](#contributing)
- [Contributors](#contributors)
Expand Down Expand Up @@ -110,6 +111,26 @@ Note: You'll need to install expo-build-properties if you haven't already:

`expo install expo-build-properties`

## App Store purchase restrictions

These methods mirror Apple's [`ManagedSettings.AppStoreSettings`](https://developer.apple.com/documentation/managedsettings/appstoresettings) `appStore` properties, and each takes a boolean matching the corresponding property:

```typescript
// in-app purchases (AppStoreSettings.denyInAppPurchases)
await ScreenTime.denyInAppPurchases(true); // block in-app purchases
await ScreenTime.denyInAppPurchases(false); // allow in-app purchases

// require a password for purchases (AppStoreSettings.requirePasswordForPurchases)
await ScreenTime.requirePasswordForPurchases(true);
await ScreenTime.requirePasswordForPurchases(false);
```

On iOS the current values can be read back from [`getStore()`](#sample-code) under `appStore`.

> **`allowInAppPurchases()` is deprecated.** Apple's `AppStoreSettings` has no `allowInAppPurchases` property, so use `denyInAppPurchases(false)` instead. `allowInAppPurchases()` is kept only for backwards compatibility.

> **⚠️ Not supported on Android.** Android exposes no system-level API to block in-app purchases or require a password for purchases for a regular app. These methods exist on Android only to mirror the API surface — they persist the flag state but do **not** enforce any restriction.

## Sample code
```typescript
import React from 'react';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.noodleofdeath.screentimeapi;

import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;

import androidx.annotation.NonNull;

Expand All @@ -16,10 +18,19 @@ public class ScreenTimeAPIModule extends ReactContextBaseJavaModule {

private static final int REQUEST_CODE_ENABLE_ADMIN = 1;

private static final String PREFS_NAME = "ScreenTimeAPIPrefs";
private static final String KEY_DENY_IN_APP_PURCHASES = "denyInAppPurchases";
private static final String KEY_REQUIRE_PASSWORD_FOR_PURCHASES = "requirePasswordForPurchases";

public ScreenTimeAPIModule(ReactApplicationContext reactContext) {
super(reactContext);
}

private SharedPreferences getPrefs() {
return getReactApplicationContext()
.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
}

@NonNull
@Override
public String getName() {
Expand Down Expand Up @@ -57,23 +68,75 @@ public void clearBlockedApplications(Promise promise) {
}

@ReactMethod
public void denyAppRemoval(Promise promise) {
public void denyAppRemoval(boolean deny, Promise promise) {
promise.resolve("success");
}

/**
* @deprecated Apple's {@code ApplicationSettings} has no {@code allowAppRemoval}
* property; use {@link #denyAppRemoval(boolean, Promise) denyAppRemoval(false)}
* instead. Kept for backwards compatibility.
*/
@Deprecated
@ReactMethod
public void allowAppRemoval(Promise promise) {
promise.resolve("success");
}

@ReactMethod
public void denyAppInstallation(Promise promise) {
public void denyAppInstallation(boolean deny, Promise promise) {
promise.resolve("success");
}

/**
* @deprecated Apple's {@code ApplicationSettings} has no {@code allowAppInstallation}
* property; use {@link #denyAppInstallation(boolean, Promise) denyAppInstallation(false)}
* instead. Kept for backwards compatibility.
*/
@Deprecated
@ReactMethod
public void allowAppInstallation(Promise promise) {
promise.resolve("success");
}

// App Store purchase restrictions are not supported on Android: the OS
// exposes no system-level API to block in-app purchases or require a
// password for purchases for a regular app. The flags are only persisted
// here to mirror the iOS API; they are NOT enforced by the OS.

/**
* Not supported on Android. In-app purchase blocking has no Android
* equivalent; the flag is persisted but never enforced by the OS.
*/
@ReactMethod
public void denyInAppPurchases(boolean deny, Promise promise) {
getPrefs().edit().putBoolean(KEY_DENY_IN_APP_PURCHASES, deny).apply();
promise.resolve("success");
}

/**
* Not supported on Android. In-app purchase blocking has no Android
* equivalent; the flag is persisted but never enforced by the OS.
*
* @deprecated Apple's {@code AppStoreSettings} has no {@code allowInAppPurchases}
* property; use {@link #denyInAppPurchases(boolean, Promise) denyInAppPurchases(false)}
* instead. Kept for backwards compatibility.
*/
@Deprecated
@ReactMethod
public void allowInAppPurchases(Promise promise) {
getPrefs().edit().putBoolean(KEY_DENY_IN_APP_PURCHASES, false).apply();
promise.resolve("success");
}

/**
* Not supported on Android. Requiring a password for purchases has no
* Android equivalent; the flag is persisted but never enforced by the OS.
*/
@ReactMethod
public void requirePasswordForPurchases(boolean req, Promise promise) {
getPrefs().edit().putBoolean(KEY_REQUIRE_PASSWORD_FOR_PURCHASES, req).apply();
promise.resolve("success");
}

}
76 changes: 73 additions & 3 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -277,33 +277,103 @@ export type IScreenTimeAPI = {
clearBlockedApplications: () => Promise<void>;

/**
* Sets whether app installation is denied.
*
* Mirrors Apple's
* [`ApplicationSettings.denyAppInstallation`](https://developer.apple.com/documentation/managedsettings/applicationsettings/denyappinstallation).
* On iOS this sets `ManagedSettingsStore.application.denyAppInstallation`; the
* current value is readable via {@link getStore} (`application.denyAppInstallation`).
* @platform ios
* @platform android
* @param {boolean} deny whether app installation is denied
* @returns {Promise<void>}
*/
denyAppInstallation: () => Promise<void>;
denyAppInstallation: (deny: boolean) => Promise<void>;

/**
* Allows app installation.
*
* @deprecated Apple's `ApplicationSettings` has no `allowAppInstallation`
* property; use {@link denyAppInstallation}`(false)` instead. Kept for
* backwards compatibility.
* @platform ios
* @platform android
* @returns {Promise<void>}
*/
allowAppInstallation: () => Promise<void>;

/**
* Sets whether app removal is denied.
*
* Mirrors Apple's
* [`ApplicationSettings.denyAppRemoval`](https://developer.apple.com/documentation/managedsettings/applicationsettings/denyappremoval).
* On iOS this sets `ManagedSettingsStore.application.denyAppRemoval`; the
* current value is readable via {@link getStore} (`application.denyAppRemoval`).
* @platform ios
* @platform android
* @param {boolean} deny whether app removal is denied
* @returns {Promise<void>}
*/
denyAppRemoval: () => Promise<void>;
denyAppRemoval: (deny: boolean) => Promise<void>;

/**
* Allows app removal.
*
* @deprecated Apple's `ApplicationSettings` has no `allowAppRemoval`
* property; use {@link denyAppRemoval}`(false)` instead. Kept for
* backwards compatibility.
* @platform ios
* @platform android
* @returns {Promise<void>}
*/
allowAppRemoval: () => Promise<void>;


/**
* Sets whether in-app purchases are denied.
*
* Mirrors Apple's
* [`AppStoreSettings.denyInAppPurchases`](https://developer.apple.com/documentation/managedsettings/appstoresettings/denyinapppurchases).
* On iOS this sets `ManagedSettingsStore.appStore.denyInAppPurchases`; the
* current value is readable via {@link getStore} (`appStore.denyInAppPurchases`).
*
* Not supported on Android — Android exposes no system-level API to block
* in-app purchases for a regular app, so this call has no effect there.
* @platform ios
* @param {boolean} deny whether in-app purchases are denied
* @returns {Promise<void>}
*/
denyInAppPurchases: (deny: boolean) => Promise<void>;

/**
* Allows in-app purchases.
*
* @deprecated Apple's `AppStoreSettings` has no `allowInAppPurchases`
* property; use {@link denyInAppPurchases}`(false)` instead. Kept for
* backwards compatibility.
*
* Not supported on Android — Android exposes no system-level API to block
* in-app purchases for a regular app, so this call has no effect there.
* @platform ios
* @returns {Promise<void>}
*/
allowInAppPurchases: () => Promise<void>;

/**
* Sets whether a password is required for purchases.
*
* Mirrors Apple's
* [`AppStoreSettings.requirePasswordForPurchases`](https://developer.apple.com/documentation/managedsettings/appstoresettings/requirepasswordforpurchases).
* On iOS this sets `ManagedSettingsStore.appStore.requirePasswordForPurchases`;
* the current value is readable via {@link getStore} (`appStore.requirePasswordForPurchases`).
*
* Not supported on Android — Android exposes no system-level API to require
* a password for purchases for a regular app, so this call has no effect there.
* @platform ios
* @param {boolean} req whether a password is required for purchases
* @returns {Promise<void>}
*/
requirePasswordForPurchases: (req: boolean) => Promise<void>;

/**
* @platform ios
* @returns {Promise<string>}
Expand Down
7 changes: 5 additions & 2 deletions ios/ReactNativeScreenTimeAPI/ReactNativeScreenTimeAPI.m
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,13 @@ @interface RCT_EXTERN_MODULE(ScreenTimeAPI, NSObject)
resolver: (RCTPromiseResolveBlock) resolve
rejecter: (RCTPromiseRejectBlock) reject)
RCT_EXTERN_METHOD(clearActivitySelection)
RCT_EXTERN_METHOD(denyAppRemoval)
RCT_EXTERN_METHOD(denyAppRemoval: (BOOL) deny)
RCT_EXTERN_METHOD(allowAppRemoval)
RCT_EXTERN_METHOD(denyAppInstallation)
RCT_EXTERN_METHOD(denyAppInstallation: (BOOL) deny)
RCT_EXTERN_METHOD(allowAppInstallation)
RCT_EXTERN_METHOD(denyInAppPurchases: (BOOL) deny)
RCT_EXTERN_METHOD(allowInAppPurchases)
RCT_EXTERN_METHOD(requirePasswordForPurchases: (BOOL) req)
RCT_EXTERN_METHOD(displayFamilyActivityPicker: (NSDictionary *) options
resolver: (RCTPromiseResolveBlock) resolve
rejecter: (RCTPromiseRejectBlock) reject)
Expand Down
37 changes: 29 additions & 8 deletions ios/ReactNativeScreenTimeAPI/ReactNativeScreenTimeAPI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -212,25 +212,46 @@ public class ScreenTimeAPI: NSObject {
}

@objc
public func denyAppRemoval() {
store.application.denyAppRemoval = true
public func denyAppRemoval(_ deny: Bool) {
store.application.denyAppRemoval = deny
}


/// Deprecated: `ApplicationSettings` has no `allowAppRemoval` property.
/// Use `denyAppRemoval(false)` instead. Kept for backwards compatibility.
@objc
public func allowAppRemoval() {
store.application.denyAppRemoval = false
}

@objc
public func denyAppInstallation() {
store.application.denyAppInstallation = true
public func denyAppInstallation(_ deny: Bool) {
store.application.denyAppInstallation = deny
}


/// Deprecated: `ApplicationSettings` has no `allowAppInstallation` property.
/// Use `denyAppInstallation(false)` instead. Kept for backwards compatibility.
@objc
public func allowAppInstallation() {
store.application.denyAppInstallation = false
}


@objc
public func denyInAppPurchases(_ deny: Bool) {
store.appStore.denyInAppPurchases = deny
}

/// Deprecated: `AppStoreSettings` has no `allowInAppPurchases` property.
/// Use `denyInAppPurchases(false)` instead. Kept for backwards compatibility.
@objc
public func allowInAppPurchases() {
store.appStore.denyInAppPurchases = false
}

@objc
public func requirePasswordForPurchases(_ req: Bool) {
store.appStore.requirePasswordForPurchases = req
}

@objc
public func initializeMonitoring(_ startTimestamp: String = "00:00",
end endTimestamp: String = "23:59",
Expand Down