Full rename ahead of App Store submission: project, targets, bundle identifiers (dev.bchen.Severed -> dev.bchen.OpenAppLock), entitlements file, app entry point, user-facing onboarding strings, test helpers, and docs. Verified: all 62 unit tests pass.
91 lines
3.0 KiB
Swift
91 lines
3.0 KiB
Swift
//
|
|
// AppSelectionSheet.swift
|
|
// OpenAppLock
|
|
//
|
|
|
|
import FamilyControls
|
|
import SwiftUI
|
|
|
|
/// App/category/website selection wrapping Apple's `FamilyActivityPicker`,
|
|
/// presented as a plain sheet with a segmented Block / Allow Only control.
|
|
struct AppSelectionSheet: View {
|
|
@Binding var draft: RuleDraft
|
|
@Environment(\.dismiss) private var dismiss
|
|
|
|
@State private var selection: FamilyActivitySelection
|
|
@State private var mode: SelectionMode
|
|
|
|
init(draft: Binding<RuleDraft>) {
|
|
self._draft = draft
|
|
self._selection = State(
|
|
initialValue: AppSelectionCodec.decode(draft.wrappedValue.selectionData)
|
|
)
|
|
self._mode = State(initialValue: draft.wrappedValue.selectionMode)
|
|
}
|
|
|
|
var body: some View {
|
|
NavigationStack {
|
|
VStack(spacing: 0) {
|
|
Picker("Mode", selection: $mode) {
|
|
ForEach(SelectionMode.allCases, id: \.self) { mode in
|
|
Text(mode.displayName).tag(mode)
|
|
}
|
|
}
|
|
.pickerStyle(.segmented)
|
|
.padding(.horizontal)
|
|
.padding(.vertical, 8)
|
|
.accessibilityIdentifier("selectionModePicker")
|
|
|
|
FamilyActivityPicker(selection: $selection)
|
|
}
|
|
.navigationTitle("Selected")
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
.toolbar {
|
|
ToolbarItem(placement: .cancellationAction) {
|
|
Button("Cancel") {
|
|
dismiss()
|
|
}
|
|
.accessibilityIdentifier("selectionBackButton")
|
|
}
|
|
ToolbarItem(placement: .confirmationAction) {
|
|
Button("Done") {
|
|
save()
|
|
}
|
|
.accessibilityIdentifier("confirmSelectionButton")
|
|
}
|
|
}
|
|
.safeAreaInset(edge: .bottom) {
|
|
VStack(spacing: 8) {
|
|
Text(selectionSummary)
|
|
.font(.footnote)
|
|
.foregroundStyle(.secondary)
|
|
.accessibilityIdentifier("selectionCountLabel")
|
|
Button {
|
|
save()
|
|
} label: {
|
|
Text("Save")
|
|
.frame(maxWidth: .infinity)
|
|
}
|
|
.buttonStyle(.borderedProminent)
|
|
.controlSize(.large)
|
|
.accessibilityIdentifier("saveSelectionButton")
|
|
}
|
|
.padding(.horizontal)
|
|
.padding(.bottom, 8)
|
|
}
|
|
}
|
|
}
|
|
|
|
private var selectionSummary: String {
|
|
let count = AppSelectionCodec.count(of: selection)
|
|
return count == 1 ? "1 App Selected" : "\(count) Apps Selected"
|
|
}
|
|
|
|
private func save() {
|
|
draft.selectionData = AppSelectionCodec.encode(selection)
|
|
draft.selectionCount = AppSelectionCodec.count(of: selection)
|
|
draft.selectionMode = mode
|
|
dismiss()
|
|
}
|
|
}
|