1- import Combine
21import Shared
3- import SwiftUI
42import UIKit
53
64// MARK: - Kiosk Mode Extension
@@ -9,221 +7,28 @@ extension WebViewController {
97 /// Setup kiosk mode integration with KioskModeManager
108 /// Call this from viewDidLoad
119 func setupKioskMode( ) {
12- let manager = KioskModeManager . shared
13-
14- // Wire up callbacks from KioskModeManager
15- manager. onNavigate = { [ weak self] path in
16- self ? . navigateToKioskPath ( path)
17- }
18-
19- manager. onRefresh = { [ weak self] in
20- self ? . refresh ( )
21- }
22-
23- manager. onKioskModeChange = { [ weak self] enabled in
24- self ? . updateKioskModeLockdown ( enabled: enabled)
25- }
26-
27- manager. onShowScreensaver = { [ weak self] mode in
28- self ? . showScreensaver ( mode: mode)
29- }
30-
31- manager. onHideScreensaver = { [ weak self] in
32- self ? . hideScreensaver ( )
33- }
34-
35- // Observe kiosk mode and settings changes using Combine (auto-cleanup on dealloc)
36- var cancellables = Set < AnyCancellable > ( )
37-
38- manager. $isKioskModeActive
39- . receive ( on: DispatchQueue . main)
40- . sink { [ weak self] _ in
41- self ? . kioskModeDidChange ( )
42- }
43- . store ( in: & cancellables)
44-
45- manager. $settings
46- . receive ( on: DispatchQueue . main)
47- . sink { [ weak self] _ in
48- self ? . kioskSettingsDidChange ( )
49- }
50- . store ( in: & cancellables)
51-
52- kioskCancellables = cancellables
53-
54- // Setup the screensaver
55- setupScreensaver ( )
56-
57- // Setup secret exit gesture (only when not showing screensaver)
58- setupSecretExitGesture ( )
59-
60- // Apply initial state if already in kiosk mode
61- if manager. isKioskModeActive {
62- updateKioskModeLockdown ( enabled: true )
63- }
64- }
65-
66- // MARK: - Screensaver
67-
68- private func setupScreensaver( ) {
69- let controller = KioskScreensaverViewController ( )
70- screensaverController = controller
71-
72- // Forward the callback for showing settings
73- controller. onShowSettings = { [ weak self] in
74- self ? . showKioskSettings ( )
75- }
76-
77- addChild ( controller)
78- view. addSubview ( controller. view)
79- controller. view. translatesAutoresizingMaskIntoConstraints = false
80-
81- NSLayoutConstraint . activate ( [
82- controller. view. topAnchor. constraint ( equalTo: view. topAnchor) ,
83- controller. view. bottomAnchor. constraint ( equalTo: view. bottomAnchor) ,
84- controller. view. leadingAnchor. constraint ( equalTo: view. leadingAnchor) ,
85- controller. view. trailingAnchor. constraint ( equalTo: view. trailingAnchor) ,
86- ] )
87-
88- controller. didMove ( toParent: self )
89- controller. view. isHidden = true
90- }
91-
92- private func setupSecretExitGesture( ) {
93- let controller = KioskSecretExitGestureViewController ( )
94- secretExitGestureController = controller
95-
96- controller. onShowSettings = { [ weak self] in
97- self ? . showKioskSettings ( )
98- }
99-
100- addChild ( controller)
101- view. addSubview ( controller. view)
102- controller. view. translatesAutoresizingMaskIntoConstraints = false
103-
104- NSLayoutConstraint . activate ( [
105- controller. view. topAnchor. constraint ( equalTo: view. topAnchor) ,
106- controller. view. bottomAnchor. constraint ( equalTo: view. bottomAnchor) ,
107- controller. view. leadingAnchor. constraint ( equalTo: view. leadingAnchor) ,
108- controller. view. trailingAnchor. constraint ( equalTo: view. trailingAnchor) ,
109- ] )
110-
111- controller. didMove ( toParent: self )
112- }
113-
114- private func showScreensaver( mode: ScreensaverMode ) {
115- guard let controller = screensaverController else { return }
116-
117- Current . Log. info ( " Showing screensaver: \( mode. rawValue) " )
118-
119- controller. view. isHidden = false
120- view. bringSubviewToFront ( controller. view)
121- controller. show ( mode: mode)
122- }
123-
124- private func hideScreensaver( ) {
125- guard let controller = screensaverController else { return }
126-
127- Current . Log. info ( " Hiding screensaver " )
128- controller. hide ( )
129-
130- // Delay hiding the view until the animation completes
131- DispatchQueue . main. asyncAfter ( deadline: . now( ) + 0.3 ) {
132- controller. view. isHidden = true
133- }
134- }
135-
136- // MARK: - Navigation
137-
138- private func navigateToKioskPath( _ path: String ) {
139- Current . Log. info ( " Kiosk navigating to: \( path) " )
140-
141- // Use the existing navigateToPath method
142- navigateToPath ( path: path)
143- }
144-
145- // MARK: - UI Lockdown
146-
147- private func updateKioskModeLockdown( enabled: Bool ) {
148- let settings = KioskModeManager . shared. settings
149-
150- // Update iOS system status bar and home indicator visibility
151- // The navigation controller must be updated first so it re-queries its child
152- if let navController = navigationController {
153- navController. setNeedsStatusBarAppearanceUpdate ( )
154- navController. setNeedsUpdateOfHomeIndicatorAutoHidden ( )
155- }
156- setNeedsStatusBarAppearanceUpdate ( )
157- setNeedsUpdateOfHomeIndicatorAutoHidden ( )
158-
159- // Hide/show the custom status bar background view
160- if let statusBarView {
161- let shouldHide = enabled && settings. hideStatusBar
162- statusBarView. isHidden = shouldHide
163- }
10+ let handler = KioskModeHandler ( webViewController: self )
11+ kioskHandler = handler
12+ handler. setup ( )
16413 }
16514
16615 // MARK: - Status Bar & Home Indicator
16716
16817 /// Override in WebViewController to check kiosk mode
16918 var kioskPrefersStatusBarHidden : Bool {
170- let manager = KioskModeManager . shared
171- return manager. isKioskModeActive && manager. settings. hideStatusBar
19+ kioskHandler? . prefersStatusBarHidden ?? false
17220 }
17321
17422 /// Override in WebViewController to check kiosk mode
17523 var kioskPrefersHomeIndicatorAutoHidden : Bool {
176- KioskModeManager . shared. isKioskModeActive
177- }
178-
179- // MARK: - Settings
180-
181- private func showKioskSettings( ) {
182- Current . Log. info ( " Showing kiosk settings " )
183-
184- // Use UINavigationController to avoid SwiftUI NavigationView dismissal bugs on iOS 15
185- // Pass an explicit dismiss closure since SwiftUI's @Environment(\.dismiss) doesn't work
186- // reliably when UIHostingController is embedded in UINavigationController presented via UIKit
187- let settingsView = KioskSettingsView ( onDismiss: { [ weak self] in
188- self ? . dismiss ( animated: true ) { [ weak self] in
189- self ? . refreshStatusBarAppearance ( )
190- }
191- } )
192- let hostingController = UIHostingController ( rootView: settingsView)
193- let navController = UINavigationController ( rootViewController: hostingController)
194- navController. modalPresentationStyle = . pageSheet
195- present ( navController, animated: true )
196- }
197-
198- /// Force a complete status bar appearance refresh after modal dismissal
199- private func refreshStatusBarAppearance( ) {
200- navigationController? . setNeedsStatusBarAppearanceUpdate ( )
201- setNeedsStatusBarAppearanceUpdate ( )
202- navigationController? . setNeedsUpdateOfHomeIndicatorAutoHidden ( )
203- setNeedsUpdateOfHomeIndicatorAutoHidden ( )
204- }
205-
206- // MARK: - Observers
207-
208- private func kioskModeDidChange( ) {
209- let manager = KioskModeManager . shared
210- Current . Log. info ( " Kiosk mode changed: \( manager. isKioskModeActive) " )
211-
212- updateKioskModeLockdown ( enabled: manager. isKioskModeActive)
213- }
214-
215- private func kioskSettingsDidChange( ) {
216- // Re-apply lockdown settings in case they changed
217- let manager = KioskModeManager . shared
218- if manager. isKioskModeActive {
219- updateKioskModeLockdown ( enabled: true )
220- }
24+ kioskHandler? . prefersHomeIndicatorAutoHidden ?? false
22125 }
22226
22327 // MARK: - Touch Handling
22428
22529 /// Call this when user touches the screen to record activity
30+ /// Required because WKWebView consumes touch events before UIKit idle detection
22631 func recordKioskActivity( ) {
227- KioskModeManager . shared . recordActivity ( source : " touch " )
32+ kioskHandler ? . recordActivity ( )
22833 }
22934}
0 commit comments