expo-app-blocker 0.1.48 → 0.1.49
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/README.md +4 -0
- package/android/src/main/java/expo/modules/appblocker/AppBlockerPrefs.kt +28 -0
- package/android/src/main/java/expo/modules/appblocker/ExpoAppBlockerModule.kt +4 -0
- package/android/src/main/java/expo/modules/appblocker/OverlayManager.kt +26 -0
- package/package.json +1 -1
- package/src/ExpoAppBlocker.types.ts +8 -0
package/README.md
CHANGED
|
@@ -255,6 +255,10 @@ The `SYSTEM_ALERT_WINDOW` overlay flashed on top of a blocked app is fully theme
|
|
|
255
255
|
| `overlayIconSize` | `number` (dp) | `96` | Square icon edge length. Only renders when `android.overlay.icon` was declared in the plugin config (build-time). |
|
|
256
256
|
| `overlayIconBottomMargin` | `number` (dp) | `20` | Vertical gap between the icon and the title. |
|
|
257
257
|
| `overlayTitleBottomMargin` | `number` (dp) | `12` | Vertical gap between the title and the body text. |
|
|
258
|
+
| `overlayShowSpinner` | `boolean` | `false` | Render an indeterminate Material circular spinner below the body text — same shape RN's `<ActivityIndicator>` produces. Useful as a "launching…" cue during the brief gap between intercept and the deep-link landing. |
|
|
259
|
+
| `overlaySpinnerSize` | `number` (dp) | `32` | Spinner edge length (square). Only used when `overlayShowSpinner` is true. |
|
|
260
|
+
| `overlaySpinnerTopMargin` | `number` (dp) | `24` | Vertical gap between the body text and the spinner. |
|
|
261
|
+
| `overlaySpinnerColor` | `string` (hex) | system primary | Tints the spinner. Useful to match your brand color. |
|
|
258
262
|
| `notificationTitle` | `string` | `"App Blocked"` | Foreground-service notification title. |
|
|
259
263
|
| `notificationText` | `string` | `"{appName} is blocked. Tap to manage."` | Foreground-service notification body. |
|
|
260
264
|
|
|
@@ -18,6 +18,10 @@ object AppBlockerPrefs {
|
|
|
18
18
|
private const val KEY_OVERLAY_ICON_SIZE = "overlay_icon_size"
|
|
19
19
|
private const val KEY_OVERLAY_ICON_GAP = "overlay_icon_gap"
|
|
20
20
|
private const val KEY_OVERLAY_TITLE_GAP = "overlay_title_gap"
|
|
21
|
+
private const val KEY_OVERLAY_SHOW_SPINNER = "overlay_show_spinner"
|
|
22
|
+
private const val KEY_OVERLAY_SPINNER_SIZE = "overlay_spinner_size"
|
|
23
|
+
private const val KEY_OVERLAY_SPINNER_GAP = "overlay_spinner_gap"
|
|
24
|
+
private const val KEY_OVERLAY_SPINNER_COLOR = "overlay_spinner_color"
|
|
21
25
|
private const val KEY_NOTIFICATION_TITLE = "notification_title"
|
|
22
26
|
private const val KEY_NOTIFICATION_TEXT = "notification_text"
|
|
23
27
|
|
|
@@ -54,6 +58,10 @@ object AppBlockerPrefs {
|
|
|
54
58
|
overlayIconSize: Float?,
|
|
55
59
|
overlayIconBottomMargin: Float?,
|
|
56
60
|
overlayTitleBottomMargin: Float?,
|
|
61
|
+
overlayShowSpinner: Boolean?,
|
|
62
|
+
overlaySpinnerSize: Float?,
|
|
63
|
+
overlaySpinnerTopMargin: Float?,
|
|
64
|
+
overlaySpinnerColor: String?,
|
|
57
65
|
notificationTitle: String?,
|
|
58
66
|
notificationText: String?,
|
|
59
67
|
) {
|
|
@@ -63,6 +71,7 @@ object AppBlockerPrefs {
|
|
|
63
71
|
.putString(KEY_OVERLAY_BG_COLOR, overlayBackgroundColor)
|
|
64
72
|
.putString(KEY_OVERLAY_TITLE_COLOR, overlayTitleColor)
|
|
65
73
|
.putString(KEY_OVERLAY_TEXT_COLOR, overlayTextColor)
|
|
74
|
+
.putString(KEY_OVERLAY_SPINNER_COLOR, overlaySpinnerColor)
|
|
66
75
|
.putString(KEY_NOTIFICATION_TITLE, notificationTitle)
|
|
67
76
|
.putString(KEY_NOTIFICATION_TEXT, notificationText)
|
|
68
77
|
putNullableFloat(editor, KEY_OVERLAY_TITLE_FONT_SIZE, overlayTitleFontSize)
|
|
@@ -71,11 +80,18 @@ object AppBlockerPrefs {
|
|
|
71
80
|
putNullableFloat(editor, KEY_OVERLAY_ICON_SIZE, overlayIconSize)
|
|
72
81
|
putNullableFloat(editor, KEY_OVERLAY_ICON_GAP, overlayIconBottomMargin)
|
|
73
82
|
putNullableFloat(editor, KEY_OVERLAY_TITLE_GAP, overlayTitleBottomMargin)
|
|
83
|
+
putNullableFloat(editor, KEY_OVERLAY_SPINNER_SIZE, overlaySpinnerSize)
|
|
84
|
+
putNullableFloat(editor, KEY_OVERLAY_SPINNER_GAP, overlaySpinnerTopMargin)
|
|
74
85
|
if (overlayTitleBold != null) {
|
|
75
86
|
editor.putBoolean(KEY_OVERLAY_TITLE_BOLD, overlayTitleBold)
|
|
76
87
|
} else {
|
|
77
88
|
editor.remove(KEY_OVERLAY_TITLE_BOLD)
|
|
78
89
|
}
|
|
90
|
+
if (overlayShowSpinner != null) {
|
|
91
|
+
editor.putBoolean(KEY_OVERLAY_SHOW_SPINNER, overlayShowSpinner)
|
|
92
|
+
} else {
|
|
93
|
+
editor.remove(KEY_OVERLAY_SHOW_SPINNER)
|
|
94
|
+
}
|
|
79
95
|
editor.apply()
|
|
80
96
|
}
|
|
81
97
|
|
|
@@ -115,6 +131,18 @@ object AppBlockerPrefs {
|
|
|
115
131
|
fun getOverlayTitleBottomMargin(context: Context): Float =
|
|
116
132
|
getOverlayFloat(context, KEY_OVERLAY_TITLE_GAP, 12f)
|
|
117
133
|
|
|
134
|
+
fun getOverlayShowSpinner(context: Context): Boolean =
|
|
135
|
+
get(context).getBoolean(KEY_OVERLAY_SHOW_SPINNER, false)
|
|
136
|
+
|
|
137
|
+
fun getOverlaySpinnerSize(context: Context): Float =
|
|
138
|
+
getOverlayFloat(context, KEY_OVERLAY_SPINNER_SIZE, 32f)
|
|
139
|
+
|
|
140
|
+
fun getOverlaySpinnerTopMargin(context: Context): Float =
|
|
141
|
+
getOverlayFloat(context, KEY_OVERLAY_SPINNER_GAP, 24f)
|
|
142
|
+
|
|
143
|
+
fun getOverlaySpinnerColor(context: Context): String? =
|
|
144
|
+
get(context).getString(KEY_OVERLAY_SPINNER_COLOR, null)
|
|
145
|
+
|
|
118
146
|
fun getNotificationTitle(context: Context): String =
|
|
119
147
|
get(context).getString(KEY_NOTIFICATION_TITLE, null) ?: "App Blocked"
|
|
120
148
|
|
|
@@ -84,6 +84,10 @@ class ExpoAppBlockerModule : Module() {
|
|
|
84
84
|
overlayIconSize = numberOrNull("overlayIconSize"),
|
|
85
85
|
overlayIconBottomMargin = numberOrNull("overlayIconBottomMargin"),
|
|
86
86
|
overlayTitleBottomMargin = numberOrNull("overlayTitleBottomMargin"),
|
|
87
|
+
overlayShowSpinner = config["overlayShowSpinner"] as? Boolean,
|
|
88
|
+
overlaySpinnerSize = numberOrNull("overlaySpinnerSize"),
|
|
89
|
+
overlaySpinnerTopMargin = numberOrNull("overlaySpinnerTopMargin"),
|
|
90
|
+
overlaySpinnerColor = config["overlaySpinnerColor"] as? String,
|
|
87
91
|
notificationTitle = config["notificationTitle"] as? String,
|
|
88
92
|
notificationText = config["notificationText"] as? String,
|
|
89
93
|
)
|
|
@@ -15,6 +15,7 @@ import android.view.View
|
|
|
15
15
|
import android.view.WindowManager
|
|
16
16
|
import android.widget.ImageView
|
|
17
17
|
import android.widget.LinearLayout
|
|
18
|
+
import android.widget.ProgressBar
|
|
18
19
|
import android.widget.TextView
|
|
19
20
|
|
|
20
21
|
class OverlayManager(private val context: Context) {
|
|
@@ -183,6 +184,25 @@ class OverlayManager(private val context: Context) {
|
|
|
183
184
|
setTextSize(TypedValue.COMPLEX_UNIT_SP, textFontSize)
|
|
184
185
|
gravity = Gravity.CENTER
|
|
185
186
|
})
|
|
187
|
+
|
|
188
|
+
// Optional indeterminate spinner — gives the user a visual cue that
|
|
189
|
+
// the app is launching during the ~150–300ms gap between intercept
|
|
190
|
+
// detection and the deep-link landing.
|
|
191
|
+
if (AppBlockerPrefs.getOverlayShowSpinner(context)) {
|
|
192
|
+
val spinnerSize = dp(AppBlockerPrefs.getOverlaySpinnerSize(context))
|
|
193
|
+
val spinnerGap = dp(AppBlockerPrefs.getOverlaySpinnerTopMargin(context))
|
|
194
|
+
addView(ProgressBar(context).apply {
|
|
195
|
+
isIndeterminate = true
|
|
196
|
+
val tint = AppBlockerPrefs.getOverlaySpinnerColor(context)
|
|
197
|
+
if (tint != null) {
|
|
198
|
+
val parsed = parseColorOrNull(tint)
|
|
199
|
+
if (parsed != null) indeterminateTintList = android.content.res.ColorStateList.valueOf(parsed)
|
|
200
|
+
}
|
|
201
|
+
layoutParams = LinearLayout.LayoutParams(spinnerSize, spinnerSize).apply {
|
|
202
|
+
topMargin = spinnerGap
|
|
203
|
+
}
|
|
204
|
+
})
|
|
205
|
+
}
|
|
186
206
|
}
|
|
187
207
|
}
|
|
188
208
|
|
|
@@ -192,6 +212,12 @@ class OverlayManager(private val context: Context) {
|
|
|
192
212
|
fallback
|
|
193
213
|
}
|
|
194
214
|
|
|
215
|
+
private fun parseColorOrNull(hex: String): Int? = try {
|
|
216
|
+
Color.parseColor(hex)
|
|
217
|
+
} catch (_: IllegalArgumentException) {
|
|
218
|
+
null
|
|
219
|
+
}
|
|
220
|
+
|
|
195
221
|
private fun buildLayoutParams(): WindowManager.LayoutParams {
|
|
196
222
|
@Suppress("DEPRECATION")
|
|
197
223
|
val type = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-app-blocker",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.49",
|
|
4
4
|
"description": "Expo module for cross-platform app blocking. Android: UsageStatsManager + Overlay. iOS: Screen Time API (FamilyControls + ManagedSettings + DeviceActivity).",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"types": "src/index.ts",
|
|
@@ -173,6 +173,14 @@ export interface AndroidConfig {
|
|
|
173
173
|
overlayIconBottomMargin?: number;
|
|
174
174
|
/** Vertical gap (dp) between the title and the body text. Default: 12. */
|
|
175
175
|
overlayTitleBottomMargin?: number;
|
|
176
|
+
/** Show an indeterminate circular spinner under the body text. Useful as a "launching…" cue during the brief gap between intercept and the deep-link landing. Default: false. */
|
|
177
|
+
overlayShowSpinner?: boolean;
|
|
178
|
+
/** Spinner edge length in dp (square). Default: 32. */
|
|
179
|
+
overlaySpinnerSize?: number;
|
|
180
|
+
/** Vertical gap (dp) between the body text and the spinner. Default: 24. */
|
|
181
|
+
overlaySpinnerTopMargin?: number;
|
|
182
|
+
/** Hex color (e.g. "#7cb518") tinting the spinner. Default: system primary. */
|
|
183
|
+
overlaySpinnerColor?: string;
|
|
176
184
|
/** Notification title when app is blocked. Use {appName} as placeholder. Default: "App Blocked" */
|
|
177
185
|
notificationTitle?: string;
|
|
178
186
|
/** Notification text when app is blocked. Use {appName} as placeholder. */
|