expo-app-blocker 0.1.7 → 0.1.9
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/.github/workflows/publish.yml +10 -3
- package/expo-module.config.json +1 -1
- package/ios/ExpoAppBlockerPickerModule.swift +117 -0
- package/package.json +1 -1
- package/src/ExpoAppBlocker.types.ts +7 -0
- package/src/index.ts +35 -0
- package/targets/ShieldConfiguration/ShieldConfigurationExtension.swift +1 -1
|
@@ -7,7 +7,6 @@ on:
|
|
|
7
7
|
jobs:
|
|
8
8
|
publish:
|
|
9
9
|
runs-on: ubuntu-latest
|
|
10
|
-
# Skip version bump commits to prevent loops
|
|
11
10
|
if: "!startsWith(github.event.head_commit.message, 'v')"
|
|
12
11
|
|
|
13
12
|
steps:
|
|
@@ -30,8 +29,16 @@ jobs:
|
|
|
30
29
|
echo "bump=patch" >> $GITHUB_OUTPUT
|
|
31
30
|
fi
|
|
32
31
|
|
|
33
|
-
- name:
|
|
34
|
-
run:
|
|
32
|
+
- name: Sync version from npm and bump
|
|
33
|
+
run: |
|
|
34
|
+
# Get latest version from npm, fallback to package.json
|
|
35
|
+
NPM_VERSION=$(npm view expo-app-blocker version 2>/dev/null || echo "0.0.0")
|
|
36
|
+
echo "Current npm version: $NPM_VERSION"
|
|
37
|
+
# Set package.json to npm version first
|
|
38
|
+
npm version $NPM_VERSION --no-git-tag-version --allow-same-version
|
|
39
|
+
# Then bump
|
|
40
|
+
npm version ${{ steps.version.outputs.bump }} --no-git-tag-version
|
|
41
|
+
echo "New version: $(node -p 'require("./package.json").version')"
|
|
35
42
|
|
|
36
43
|
- name: Publish to npm
|
|
37
44
|
run: npm publish --access public
|
package/expo-module.config.json
CHANGED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import ExpoModulesCore
|
|
2
|
+
import FamilyControls
|
|
3
|
+
import ManagedSettings
|
|
4
|
+
import SwiftUI
|
|
5
|
+
|
|
6
|
+
public class ExpoAppBlockerPickerModule: Module {
|
|
7
|
+
public func definition() -> ModuleDefinition {
|
|
8
|
+
Name("ExpoAppBlockerPicker")
|
|
9
|
+
|
|
10
|
+
View(FamilyActivityPickerNativeView.self) {
|
|
11
|
+
Events("onSelectionChange")
|
|
12
|
+
|
|
13
|
+
Prop("initialSelection") { (view: FamilyActivityPickerNativeView, selectionBase64: String) in
|
|
14
|
+
guard !selectionBase64.isEmpty,
|
|
15
|
+
let data = Data(base64Encoded: selectionBase64),
|
|
16
|
+
let selection = try? JSONDecoder().decode(FamilyActivitySelection.self, from: data)
|
|
17
|
+
else { return }
|
|
18
|
+
view.setInitialSelection(selection)
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// MARK: - ViewModel
|
|
25
|
+
|
|
26
|
+
class FamilyActivityPickerViewModel: ObservableObject {
|
|
27
|
+
@Published var selection = FamilyActivitySelection()
|
|
28
|
+
var didSetInitial = false
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// MARK: - Native View (ExpoView wrapper)
|
|
32
|
+
|
|
33
|
+
class FamilyActivityPickerNativeView: ExpoView {
|
|
34
|
+
let onSelectionChange = EventDispatcher()
|
|
35
|
+
let viewModel = FamilyActivityPickerViewModel()
|
|
36
|
+
private var hostingController: UIHostingController<InlinePickerContentView>?
|
|
37
|
+
|
|
38
|
+
required init(appContext: AppContext? = nil) {
|
|
39
|
+
super.init(appContext: appContext)
|
|
40
|
+
clipsToBounds = true
|
|
41
|
+
|
|
42
|
+
let contentView = InlinePickerContentView(viewModel: viewModel) { [weak self] selection in
|
|
43
|
+
self?.handleSelectionChange(selection)
|
|
44
|
+
}
|
|
45
|
+
let hc = UIHostingController(rootView: contentView)
|
|
46
|
+
hc.view.backgroundColor = .clear
|
|
47
|
+
addSubview(hc.view)
|
|
48
|
+
hostingController = hc
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
override func layoutSubviews() {
|
|
52
|
+
super.layoutSubviews()
|
|
53
|
+
hostingController?.view.frame = bounds
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
func setInitialSelection(_ selection: FamilyActivitySelection) {
|
|
57
|
+
guard !viewModel.didSetInitial else { return }
|
|
58
|
+
viewModel.didSetInitial = true
|
|
59
|
+
viewModel.selection = selection
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
private func handleSelectionChange(_ selection: FamilyActivitySelection) {
|
|
63
|
+
var appItems: [[String: Any]] = []
|
|
64
|
+
for token in selection.applicationTokens {
|
|
65
|
+
if let data = try? JSONEncoder().encode(token) {
|
|
66
|
+
appItems.append([
|
|
67
|
+
"type": "app",
|
|
68
|
+
"token": data.base64EncodedString()
|
|
69
|
+
])
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
var categoryItems: [[String: Any]] = []
|
|
74
|
+
for token in selection.categoryTokens {
|
|
75
|
+
if let data = try? JSONEncoder().encode(token) {
|
|
76
|
+
categoryItems.append([
|
|
77
|
+
"type": "category",
|
|
78
|
+
"token": data.base64EncodedString()
|
|
79
|
+
])
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
var selectionBase64 = ""
|
|
84
|
+
if let selectionData = try? JSONEncoder().encode(selection) {
|
|
85
|
+
selectionBase64 = selectionData.base64EncodedString()
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
let items = appItems + categoryItems
|
|
89
|
+
let summary: [String: Any] = [
|
|
90
|
+
"type": "summary",
|
|
91
|
+
"totalApps": selection.applicationTokens.count,
|
|
92
|
+
"totalCategories": selection.categoryTokens.count,
|
|
93
|
+
"selectionData": selectionBase64
|
|
94
|
+
]
|
|
95
|
+
|
|
96
|
+
onSelectionChange([
|
|
97
|
+
"items": items + [summary],
|
|
98
|
+
"totalApps": selection.applicationTokens.count,
|
|
99
|
+
"totalCategories": selection.categoryTokens.count,
|
|
100
|
+
"selectionData": selectionBase64
|
|
101
|
+
])
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// MARK: - SwiftUI Content View with inline FamilyActivityPicker
|
|
106
|
+
|
|
107
|
+
struct InlinePickerContentView: View {
|
|
108
|
+
@ObservedObject var viewModel: FamilyActivityPickerViewModel
|
|
109
|
+
var onSelectionChange: (FamilyActivitySelection) -> Void
|
|
110
|
+
|
|
111
|
+
var body: some View {
|
|
112
|
+
FamilyActivityPicker(selection: $viewModel.selection)
|
|
113
|
+
.onChange(of: viewModel.selection) { newSelection in
|
|
114
|
+
onSelectionChange(newSelection)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-app-blocker",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
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",
|
|
@@ -61,6 +61,13 @@ export interface RelockResult {
|
|
|
61
61
|
locked: boolean;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
export interface FamilyActivityPickerSelectionEvent {
|
|
65
|
+
items: IOSBlockedItem[];
|
|
66
|
+
totalApps: number;
|
|
67
|
+
totalCategories: number;
|
|
68
|
+
selectionData: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
64
71
|
// ──────────────────────────────────────────────────────────────────────────────
|
|
65
72
|
// Plugin configuration types
|
|
66
73
|
// ──────────────────────────────────────────────────────────────────────────────
|
package/src/index.ts
CHANGED
|
@@ -15,6 +15,7 @@ import type {
|
|
|
15
15
|
IOSBlockConfiguration,
|
|
16
16
|
TemporaryUnlockResult,
|
|
17
17
|
RelockResult,
|
|
18
|
+
FamilyActivityPickerSelectionEvent,
|
|
18
19
|
} from "./ExpoAppBlocker.types";
|
|
19
20
|
|
|
20
21
|
export type {
|
|
@@ -28,6 +29,7 @@ export type {
|
|
|
28
29
|
RelockResult,
|
|
29
30
|
ShieldConfig,
|
|
30
31
|
PluginConfig,
|
|
32
|
+
FamilyActivityPickerSelectionEvent,
|
|
31
33
|
} from "./ExpoAppBlocker.types";
|
|
32
34
|
|
|
33
35
|
// ──────────────────────────────────────────────────────────────────────────────
|
|
@@ -223,3 +225,36 @@ export function BlockedAppsNativeList({
|
|
|
223
225
|
style: [{ minHeight: 50 }, style],
|
|
224
226
|
});
|
|
225
227
|
}
|
|
228
|
+
|
|
229
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
230
|
+
// iOS Native View: inline FamilyActivityPicker (embedded in your UI)
|
|
231
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
232
|
+
|
|
233
|
+
let NativePickerView: any = null;
|
|
234
|
+
if (Platform.OS === "ios") {
|
|
235
|
+
try {
|
|
236
|
+
NativePickerView = requireNativeViewManager("ExpoAppBlockerPicker");
|
|
237
|
+
} catch {}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export function FamilyActivityPickerView({
|
|
241
|
+
initialSelection,
|
|
242
|
+
onSelectionChange,
|
|
243
|
+
style,
|
|
244
|
+
}: {
|
|
245
|
+
/** Base64-encoded FamilyActivitySelection (from a previous picker result's selectionData) */
|
|
246
|
+
initialSelection?: string;
|
|
247
|
+
/** Called when the user changes selection in the picker */
|
|
248
|
+
onSelectionChange?: (event: FamilyActivityPickerSelectionEvent) => void;
|
|
249
|
+
style?: any;
|
|
250
|
+
}) {
|
|
251
|
+
if (!NativePickerView || Platform.OS !== "ios") return null;
|
|
252
|
+
|
|
253
|
+
return React.createElement(NativePickerView, {
|
|
254
|
+
initialSelection: initialSelection || "",
|
|
255
|
+
onSelectionChange: onSelectionChange
|
|
256
|
+
? (e: any) => onSelectionChange(e.nativeEvent)
|
|
257
|
+
: undefined,
|
|
258
|
+
style: [{ minHeight: 400 }, style],
|
|
259
|
+
});
|
|
260
|
+
}
|
|
@@ -59,7 +59,7 @@ class ShieldConfigurationExtension: ShieldConfigurationDataSource {
|
|
|
59
59
|
let hasSecondary = !shieldSecondaryButtonLabel.isEmpty && shieldSecondaryButtonLabel != "none"
|
|
60
60
|
|
|
61
61
|
return ShieldConfiguration(
|
|
62
|
-
backgroundBlurStyle:
|
|
62
|
+
backgroundBlurStyle: shieldBlurStyle,
|
|
63
63
|
backgroundColor: shieldBackgroundColor,
|
|
64
64
|
icon: mascotIcon,
|
|
65
65
|
title: ShieldConfiguration.Label(text: shieldTitle, color: shieldTitleColor),
|