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 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.48",
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. */