expo-app-blocker 0.1.32 → 0.1.34

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.
@@ -22,11 +22,10 @@ public class ExpoAppBlockerPickerModule: Module {
22
22
  view.setTheme(theme)
23
23
  }
24
24
 
25
- // Increment this value to programmatically clear the picker's selection
26
- // without remounting. Works because FamilyActivityPicker is driven by a
27
- // @Binding — setting the backing @Published var resets the SwiftUI view.
25
+ // Increment to clear selection in-process. Applied only after the initial
26
+ // React prop snapshot so mounting with `{ clearTrigger: 0 }` does not wipe state.
28
27
  Prop("clearTrigger") { (view: FamilyActivityPickerNativeView, trigger: Int) in
29
- view.clearSelection()
28
+ view.applyClearTrigger(trigger)
30
29
  }
31
30
  }
32
31
  }
@@ -37,7 +36,6 @@ public class ExpoAppBlockerPickerModule: Module {
37
36
  class FamilyActivityPickerViewModel: ObservableObject {
38
37
  @Published var selection = FamilyActivitySelection()
39
38
  @Published var colorScheme: ColorScheme? = nil
40
- @Published var clearCounter: Int = 0
41
39
  var didSetInitial = false
42
40
  }
43
41
 
@@ -47,6 +45,9 @@ class FamilyActivityPickerNativeView: ExpoView {
47
45
  let onSelectionChange = EventDispatcher()
48
46
  let viewModel = FamilyActivityPickerViewModel()
49
47
  private var hostingController: UIHostingController<InlinePickerContentView>?
48
+ /// First `clearTrigger` snapshot from React establishes baseline — do not treat as clear.
49
+ private var didSyncClearTriggerFromReact = false
50
+ private var lastAppliedClearTrigger: Int = 0
50
51
 
51
52
  required init(appContext: AppContext? = nil) {
52
53
  super.init(appContext: appContext)
@@ -72,10 +73,25 @@ class FamilyActivityPickerNativeView: ExpoView {
72
73
  viewModel.selection = selection
73
74
  }
74
75
 
75
- func clearSelection() {
76
- viewModel.selection = FamilyActivitySelection()
76
+ /// Increments-only: first snapshot records baseline without clearing; higher values clear UI.
77
+ func applyClearTrigger(_ trigger: Int) {
78
+ if !didSyncClearTriggerFromReact {
79
+ didSyncClearTriggerFromReact = true
80
+ lastAppliedClearTrigger = trigger
81
+ return
82
+ }
83
+ guard trigger > lastAppliedClearTrigger else { return }
84
+ lastAppliedClearTrigger = trigger
85
+ clearSelectionWithoutRemount()
86
+ }
87
+
88
+ private func clearSelectionWithoutRemount() {
89
+ var transaction = Transaction()
90
+ transaction.disablesAnimations = true
91
+ withTransaction(transaction) {
92
+ viewModel.selection = FamilyActivitySelection()
93
+ }
77
94
  viewModel.didSetInitial = false
78
- viewModel.clearCounter += 1
79
95
  }
80
96
 
81
97
  func setTheme(_ theme: String) {
@@ -116,15 +132,11 @@ class FamilyActivityPickerNativeView: ExpoView {
116
132
  }
117
133
 
118
134
  let items = appItems + categoryItems
119
- let summary: [String: Any] = [
120
- "type": "summary",
121
- "totalApps": selection.applicationTokens.count,
122
- "totalCategories": selection.categoryTokens.count,
123
- "selectionData": selectionBase64
124
- ]
125
135
 
136
+ // Do not append a synthetic "summary" row to `items` — JS counts `items.length` for UI and
137
+ // `totalApps` / `totalCategories` / `selectionData` already carry the same metadata.
126
138
  onSelectionChange([
127
- "items": items + [summary],
139
+ "items": items,
128
140
  "totalApps": selection.applicationTokens.count,
129
141
  "totalCategories": selection.categoryTokens.count,
130
142
  "selectionData": selectionBase64
@@ -139,11 +151,9 @@ struct InlinePickerContentView: View {
139
151
  var onSelectionChange: (FamilyActivitySelection) -> Void
140
152
 
141
153
  var body: some View {
142
- // .id(clearCounter) forces SwiftUI to destroy and recreate FamilyActivityPicker
143
- // when clearSelection() is called. The picker does not respond to external
144
- // binding mutations after initial presentation — recreation is the only reset path.
154
+ // Prefer binding-only resets: recreating FamilyActivityPicker (e.g. via `.id`) causes a visible flash.
155
+ // If a future iOS/SDK build stops syncing empty selections here, reconsider a targeted redraw.
145
156
  let picker = FamilyActivityPicker(selection: $viewModel.selection)
146
- .id(viewModel.clearCounter)
147
157
  .onChange(of: viewModel.selection) { newSelection in
148
158
  onSelectionChange(newSelection)
149
159
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-app-blocker",
3
- "version": "0.1.32",
3
+ "version": "0.1.34",
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",
package/src/index.ts CHANGED
@@ -250,6 +250,7 @@ export function FamilyActivityPickerView({
250
250
  onSelectionChange,
251
251
  theme,
252
252
  style,
253
+ clearTrigger,
253
254
  }: FamilyActivityPickerViewProps) {
254
255
  if (!NativePickerView || Platform.OS !== "ios") return null;
255
256
 
@@ -257,8 +258,16 @@ export function FamilyActivityPickerView({
257
258
  initialSelection: initialSelection || "",
258
259
  theme: theme || "system",
259
260
  onSelectionChange: onSelectionChange
260
- ? (e: any) => onSelectionChange(e.nativeEvent)
261
+ ? (e: any) => {
262
+ const ne = e.nativeEvent;
263
+ const items = (ne.items ?? []).filter(
264
+ (item: { type?: string }) =>
265
+ item?.type === "app" || item?.type === "category",
266
+ );
267
+ onSelectionChange({ ...ne, items });
268
+ }
261
269
  : undefined,
270
+ ...(clearTrigger !== undefined ? { clearTrigger } : {}),
262
271
  style: [{ minHeight: 400 }, style],
263
272
  });
264
273
  }