expo-app-blocker 0.1.45 → 0.1.47

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.
@@ -6,7 +6,11 @@ import android.content.SharedPreferences
6
6
  object AppBlockerPrefs {
7
7
  const val PREFS_NAME = "expo_app_blocker_prefs"
8
8
  const val KEY_BLOCKED_PACKAGES = "blocked_packages"
9
+ private const val KEY_OVERLAY_TITLE = "overlay_title"
9
10
  private const val KEY_OVERLAY_TEXT = "overlay_text"
11
+ private const val KEY_OVERLAY_BG_COLOR = "overlay_bg_color"
12
+ private const val KEY_OVERLAY_TITLE_COLOR = "overlay_title_color"
13
+ private const val KEY_OVERLAY_TEXT_COLOR = "overlay_text_color"
10
14
  private const val KEY_NOTIFICATION_TITLE = "notification_title"
11
15
  private const val KEY_NOTIFICATION_TEXT = "notification_text"
12
16
 
@@ -24,20 +28,40 @@ object AppBlockerPrefs {
24
28
 
25
29
  fun setAndroidConfig(
26
30
  context: Context,
31
+ overlayTitle: String?,
27
32
  overlayText: String?,
33
+ overlayBackgroundColor: String?,
34
+ overlayTitleColor: String?,
35
+ overlayTextColor: String?,
28
36
  notificationTitle: String?,
29
37
  notificationText: String?,
30
38
  ) {
31
39
  get(context).edit()
40
+ .putString(KEY_OVERLAY_TITLE, overlayTitle)
32
41
  .putString(KEY_OVERLAY_TEXT, overlayText)
42
+ .putString(KEY_OVERLAY_BG_COLOR, overlayBackgroundColor)
43
+ .putString(KEY_OVERLAY_TITLE_COLOR, overlayTitleColor)
44
+ .putString(KEY_OVERLAY_TEXT_COLOR, overlayTextColor)
33
45
  .putString(KEY_NOTIFICATION_TITLE, notificationTitle)
34
46
  .putString(KEY_NOTIFICATION_TEXT, notificationText)
35
47
  .apply()
36
48
  }
37
49
 
50
+ fun getOverlayTitle(context: Context): String =
51
+ get(context).getString(KEY_OVERLAY_TITLE, null) ?: "App Blocked"
52
+
38
53
  fun getOverlayText(context: Context): String =
39
54
  get(context).getString(KEY_OVERLAY_TEXT, null) ?: "{appName} is blocked."
40
55
 
56
+ fun getOverlayBackgroundColor(context: Context): String =
57
+ get(context).getString(KEY_OVERLAY_BG_COLOR, null) ?: "#FFFFFF"
58
+
59
+ fun getOverlayTitleColor(context: Context): String =
60
+ get(context).getString(KEY_OVERLAY_TITLE_COLOR, null) ?: "#111111"
61
+
62
+ fun getOverlayTextColor(context: Context): String =
63
+ get(context).getString(KEY_OVERLAY_TEXT_COLOR, null) ?: "#737373"
64
+
41
65
  fun getNotificationTitle(context: Context): String =
42
66
  get(context).getString(KEY_NOTIFICATION_TITLE, null) ?: "App Blocked"
43
67
 
@@ -68,7 +68,11 @@ class ExpoAppBlockerModule : Module() {
68
68
  Function("setAndroidConfig") { config: Map<String, Any?> ->
69
69
  AppBlockerPrefs.setAndroidConfig(
70
70
  context,
71
+ overlayTitle = config["overlayTitle"] as? String,
71
72
  overlayText = config["overlayText"] as? String,
73
+ overlayBackgroundColor = config["overlayBackgroundColor"] as? String,
74
+ overlayTitleColor = config["overlayTitleColor"] as? String,
75
+ overlayTextColor = config["overlayTextColor"] as? String,
72
76
  notificationTitle = config["notificationTitle"] as? String,
73
77
  notificationText = config["notificationText"] as? String,
74
78
  )
@@ -2,6 +2,7 @@ package expo.modules.appblocker
2
2
 
3
3
  import android.content.Context
4
4
  import android.content.Intent
5
+ import android.graphics.BitmapFactory
5
6
  import android.graphics.Color
6
7
  import android.graphics.PixelFormat
7
8
  import android.graphics.Typeface
@@ -12,6 +13,7 @@ import android.util.TypedValue
12
13
  import android.view.Gravity
13
14
  import android.view.View
14
15
  import android.view.WindowManager
16
+ import android.widget.ImageView
15
17
  import android.widget.LinearLayout
16
18
  import android.widget.TextView
17
19
 
@@ -117,18 +119,51 @@ class OverlayManager(private val context: Context) {
117
119
  val density = context.resources.displayMetrics.density
118
120
  fun dp(value: Int) = (value * density).toInt()
119
121
 
122
+ val overlayTitle = AppBlockerPrefs.getOverlayTitle(context)
123
+ .replace("{appName}", appName)
120
124
  val overlayText = AppBlockerPrefs.getOverlayText(context)
121
125
  .replace("{appName}", appName)
126
+ val backgroundColor = parseColorOrDefault(
127
+ AppBlockerPrefs.getOverlayBackgroundColor(context),
128
+ Color.WHITE,
129
+ )
130
+ val titleColor = parseColorOrDefault(
131
+ AppBlockerPrefs.getOverlayTitleColor(context),
132
+ Color.parseColor("#111111"),
133
+ )
134
+ val textColor = parseColorOrDefault(
135
+ AppBlockerPrefs.getOverlayTextColor(context),
136
+ Color.parseColor("#737373"),
137
+ )
122
138
 
123
139
  return LinearLayout(context).apply {
124
140
  orientation = LinearLayout.VERTICAL
125
141
  gravity = Gravity.CENTER
126
- setBackgroundColor(Color.WHITE)
142
+ setBackgroundColor(backgroundColor)
127
143
  setPadding(dp(32), dp(32), dp(32), dp(32))
128
144
 
145
+ // Optional brand icon — drawable named `expo_app_blocker_overlay_icon`
146
+ // is copied by the config plugin from `pluginConfig.android.overlay.icon`.
147
+ // Skip silently if missing so apps that don't ship one still get a clean overlay.
148
+ val iconResId = context.resources.getIdentifier(
149
+ "expo_app_blocker_overlay_icon",
150
+ "drawable",
151
+ context.packageName,
152
+ )
153
+ if (iconResId != 0) {
154
+ addView(ImageView(context).apply {
155
+ val bitmap = BitmapFactory.decodeResource(context.resources, iconResId)
156
+ if (bitmap != null) setImageBitmap(bitmap)
157
+ val size = dp(96)
158
+ layoutParams = LinearLayout.LayoutParams(size, size).apply {
159
+ bottomMargin = dp(20)
160
+ }
161
+ })
162
+ }
163
+
129
164
  addView(TextView(context).apply {
130
- text = "App Blocked"
131
- setTextColor(Color.parseColor("#111111"))
165
+ text = overlayTitle
166
+ setTextColor(titleColor)
132
167
  setTextSize(TypedValue.COMPLEX_UNIT_SP, 24f)
133
168
  setTypeface(typeface, Typeface.BOLD)
134
169
  gravity = Gravity.CENTER
@@ -137,13 +172,19 @@ class OverlayManager(private val context: Context) {
137
172
 
138
173
  addView(TextView(context).apply {
139
174
  text = overlayText
140
- setTextColor(Color.parseColor("#737373"))
175
+ setTextColor(textColor)
141
176
  setTextSize(TypedValue.COMPLEX_UNIT_SP, 16f)
142
177
  gravity = Gravity.CENTER
143
178
  })
144
179
  }
145
180
  }
146
181
 
182
+ private fun parseColorOrDefault(hex: String, fallback: Int): Int = try {
183
+ Color.parseColor(hex)
184
+ } catch (_: IllegalArgumentException) {
185
+ fallback
186
+ }
187
+
147
188
  private fun buildLayoutParams(): WindowManager.LayoutParams {
148
189
  @Suppress("DEPRECATION")
149
190
  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.45",
3
+ "version": "0.1.47",
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",
@@ -144,6 +144,39 @@ function withAppBlockerAndroid(config, pluginConfig) {
144
144
  ]);
145
145
  }
146
146
 
147
+ // Copy the overlay icon (PNG) to `res/drawable/expo_app_blocker_overlay_icon.png`
148
+ // so `OverlayManager.kt` can resolve it via Resources.getIdentifier(...).
149
+ // The icon is rendered above the title in the SYSTEM_ALERT_WINDOW overlay.
150
+ // Path is resolved relative to the project root for consistency with the
151
+ // top-level `icon` config field.
152
+ const overlayIconRel = pluginConfig?.android?.overlay?.icon;
153
+ if (overlayIconRel) {
154
+ config = withDangerousMod(config, [
155
+ "android",
156
+ (config) => {
157
+ const platformRoot = config.modRequest.platformProjectRoot;
158
+ const projectRoot = config.modRequest.projectRoot;
159
+ const drawableDir = path.join(platformRoot, "app", "src", "main", "res", "drawable");
160
+ const iconSrc = path.isAbsolute(overlayIconRel)
161
+ ? overlayIconRel
162
+ : path.join(projectRoot, overlayIconRel);
163
+
164
+ if (!fs.existsSync(iconSrc)) {
165
+ throw new Error(
166
+ `[expo-app-blocker] android.overlay.icon points to a missing file: ${iconSrc}`,
167
+ );
168
+ }
169
+
170
+ if (!fs.existsSync(drawableDir)) {
171
+ fs.mkdirSync(drawableDir, { recursive: true });
172
+ }
173
+
174
+ fs.copyFileSync(iconSrc, path.join(drawableDir, "expo_app_blocker_overlay_icon.png"));
175
+ return config;
176
+ },
177
+ ]);
178
+ }
179
+
147
180
  return config;
148
181
  }
149
182
 
@@ -149,8 +149,16 @@ export interface ShieldConfig {
149
149
  }
150
150
 
151
151
  export interface AndroidConfig {
152
- /** Text shown on the blocking overlay. Use {appName} as placeholder. Default: "{appName} is blocked." */
152
+ /** Bold title rendered on the blocking overlay. Use {appName} as placeholder. Default: "App Blocked" */
153
+ overlayTitle?: string;
154
+ /** Body text shown under the overlay title. Use {appName} as placeholder. Default: "{appName} is blocked." */
153
155
  overlayText?: string;
156
+ /** Hex color (e.g. "#f6f6f6") for the overlay background. Default: "#FFFFFF". */
157
+ overlayBackgroundColor?: string;
158
+ /** Hex color (e.g. "#111111") for the overlay title text. Default: "#111111". */
159
+ overlayTitleColor?: string;
160
+ /** Hex color (e.g. "#737373") for the overlay body text. Default: "#737373". */
161
+ overlayTextColor?: string;
154
162
  /** Notification title when app is blocked. Use {appName} as placeholder. Default: "App Blocked" */
155
163
  notificationTitle?: string;
156
164
  /** Notification text when app is blocked. Use {appName} as placeholder. */