1+ import Foundation
2+ import SwiftUI
3+ import WidgetKit
4+ import Combine
5+
6+ /// Manager class for handling all widget-related operations in the demo app
7+ @available ( iOS 16 . 0 , * )
8+ @MainActor
9+ public class WidgetKitManager : ObservableObject {
10+
11+ // MARK: - Published Properties
12+
13+ @Published public var availableWidgets : [ WidgetModel ] = [ ]
14+ @Published public var activeWidgets : [ WidgetModel ] = [ ]
15+ @Published public var enableHomeScreen : Bool = true
16+ @Published public var enableLockScreen : Bool = true
17+ @Published public var enableLiveActivities : Bool = true
18+ @Published public var enableDynamicIsland : Bool = true
19+ @Published public var isConfigured : Bool = false
20+
21+ // MARK: - Private Properties
22+
23+ private var configuration = WidgetConfiguration ( )
24+ private var cancellables = Set < AnyCancellable > ( )
25+
26+ // MARK: - Initialization
27+
28+ public init ( ) {
29+ loadAvailableWidgets ( )
30+ setupObservers ( )
31+ }
32+
33+ // MARK: - Public Methods
34+
35+ /// Configures the widget manager with custom settings
36+ public func configure( _ block: ( inout WidgetConfiguration ) -> Void ) {
37+ block ( & configuration)
38+ isConfigured = true
39+ applyConfiguration ( )
40+ }
41+
42+ /// Creates a new widget with specified parameters
43+ public func createWidget( type: WidgetType , size: WidgetSize , content: AnyView ) async throws -> WidgetModel {
44+ let widget = WidgetModel (
45+ id: UUID ( ) . uuidString,
46+ type: type,
47+ size: size,
48+ title: " New Widget " ,
49+ subtitle: " Custom widget " ,
50+ icon: " square.grid.2x2 " ,
51+ isActive: false ,
52+ lastUpdated: Date ( )
53+ )
54+
55+ availableWidgets. append ( widget)
56+
57+ // Request widget timeline reload
58+ WidgetCenter . shared. reloadAllTimelines ( )
59+
60+ return widget
61+ }
62+
63+ /// Activates a widget
64+ public func activateWidget( _ widget: WidgetModel ) async throws {
65+ guard let index = availableWidgets. firstIndex ( where: { $0. id == widget. id } ) else {
66+ throw WidgetError . widgetNotFound
67+ }
68+
69+ availableWidgets [ index] . isActive = true
70+ activeWidgets. append ( availableWidgets [ index] )
71+
72+ // Update widget timeline
73+ WidgetCenter . shared. reloadTimelines ( ofKind: widget. kind)
74+ }
75+
76+ /// Deactivates a widget
77+ public func deactivateWidget( _ widget: WidgetModel ) async throws {
78+ guard let availableIndex = availableWidgets. firstIndex ( where: { $0. id == widget. id } ) else {
79+ throw WidgetError . widgetNotFound
80+ }
81+
82+ availableWidgets [ availableIndex] . isActive = false
83+ activeWidgets. removeAll { $0. id == widget. id }
84+
85+ // Update widget timeline
86+ WidgetCenter . shared. reloadTimelines ( ofKind: widget. kind)
87+ }
88+
89+ /// Updates widget content
90+ public func updateWidget( _ widget: WidgetModel , with data: WidgetData ) async throws {
91+ guard let index = availableWidgets. firstIndex ( where: { $0. id == widget. id } ) else {
92+ throw WidgetError . widgetNotFound
93+ }
94+
95+ availableWidgets [ index] . lastUpdated = Date ( )
96+
97+ // Store widget data for timeline provider
98+ UserDefaults . standard. set ( try ? JSONEncoder ( ) . encode ( data) , forKey: " widget_data_ \( widget. id) " )
99+
100+ // Reload widget timeline
101+ WidgetCenter . shared. reloadTimelines ( ofKind: widget. kind)
102+ }
103+
104+ /// Refreshes all widgets
105+ public func refreshAllWidgets( ) async {
106+ WidgetCenter . shared. reloadAllTimelines ( )
107+
108+ for index in availableWidgets. indices {
109+ availableWidgets [ index] . lastUpdated = Date ( )
110+ }
111+ }
112+
113+ /// Gets widget statistics
114+ public func getWidgetStatistics( ) -> WidgetStatistics {
115+ return WidgetStatistics (
116+ totalWidgets: availableWidgets. count,
117+ activeWidgets: activeWidgets. count,
118+ homeScreenWidgets: availableWidgets. filter { $0. type == . homeScreen } . count,
119+ lockScreenWidgets: availableWidgets. filter { $0. type == . lockScreen } . count,
120+ liveActivities: availableWidgets. filter { $0. type == . liveActivity } . count
121+ )
122+ }
123+
124+ // MARK: - Private Methods
125+
126+ private func loadAvailableWidgets( ) {
127+ // Load sample widgets for demo
128+ availableWidgets = [
129+ WidgetModel (
130+ id: " weather " ,
131+ type: . homeScreen,
132+ size: . medium,
133+ title: " Weather Widget " ,
134+ subtitle: " Current weather conditions " ,
135+ icon: " cloud.sun.fill " ,
136+ isActive: true ,
137+ lastUpdated: Date ( )
138+ ) ,
139+ WidgetModel (
140+ id: " calendar " ,
141+ type: . homeScreen,
142+ size: . small,
143+ title: " Calendar Widget " ,
144+ subtitle: " Upcoming events " ,
145+ icon: " calendar " ,
146+ isActive: true ,
147+ lastUpdated: Date ( )
148+ ) ,
149+ WidgetModel (
150+ id: " fitness " ,
151+ type: . lockScreen,
152+ size: . small,
153+ title: " Fitness Widget " ,
154+ subtitle: " Activity rings " ,
155+ icon: " figure.walk " ,
156+ isActive: false ,
157+ lastUpdated: Date ( )
158+ ) ,
159+ WidgetModel (
160+ id: " stocks " ,
161+ type: . homeScreen,
162+ size: . large,
163+ title: " Stocks Widget " ,
164+ subtitle: " Market overview " ,
165+ icon: " chart.line.uptrend.xyaxis " ,
166+ isActive: false ,
167+ lastUpdated: Date ( )
168+ ) ,
169+ WidgetModel (
170+ id: " music " ,
171+ type: . lockScreen,
172+ size: . small,
173+ title: " Music Widget " ,
174+ subtitle: " Now playing " ,
175+ icon: " music.note " ,
176+ isActive: true ,
177+ lastUpdated: Date ( )
178+ ) ,
179+ WidgetModel (
180+ id: " news " ,
181+ type: . homeScreen,
182+ size: . medium,
183+ title: " News Widget " ,
184+ subtitle: " Top headlines " ,
185+ icon: " newspaper " ,
186+ isActive: false ,
187+ lastUpdated: Date ( )
188+ ) ,
189+ WidgetModel (
190+ id: " battery " ,
191+ type: . lockScreen,
192+ size: . small,
193+ title: " Battery Widget " ,
194+ subtitle: " Device battery status " ,
195+ icon: " battery.75 " ,
196+ isActive: true ,
197+ lastUpdated: Date ( )
198+ ) ,
199+ WidgetModel (
200+ id: " reminders " ,
201+ type: . homeScreen,
202+ size: . small,
203+ title: " Reminders Widget " ,
204+ subtitle: " Today's tasks " ,
205+ icon: " checklist " ,
206+ isActive: false ,
207+ lastUpdated: Date ( )
208+ )
209+ ]
210+
211+ // Filter active widgets
212+ activeWidgets = availableWidgets. filter { $0. isActive }
213+ }
214+
215+ private func setupObservers( ) {
216+ // Observe widget configuration changes
217+ NotificationCenter . default. publisher ( for: . widgetConfigurationChanged)
218+ . sink { [ weak self] _ in
219+ Task { @MainActor in
220+ await self ? . refreshAllWidgets ( )
221+ }
222+ }
223+ . store ( in: & cancellables)
224+ }
225+
226+ private func applyConfiguration( ) {
227+ enableHomeScreen = configuration. enableHomeScreenWidgets
228+ enableLockScreen = configuration. enableLockScreenWidgets
229+ enableLiveActivities = configuration. enableLiveActivities
230+ enableDynamicIsland = configuration. enableDynamicIsland
231+ }
232+ }
233+
234+ // MARK: - Supporting Types
235+
236+ @available ( iOS 16 . 0 , * )
237+ public struct WidgetModel : Identifiable , Codable {
238+ public let id : String
239+ public let type : WidgetType
240+ public let size : WidgetSize
241+ public let title : String
242+ public let subtitle : String
243+ public let icon : String
244+ public var isActive : Bool
245+ public var lastUpdated : Date
246+
247+ public var kind : String {
248+ return " com.widgetkit.demo. \( id) "
249+ }
250+ }
251+
252+ public enum WidgetType : String , Codable , CaseIterable {
253+ case homeScreen = " home_screen "
254+ case lockScreen = " lock_screen "
255+ case standBy = " stand_by "
256+ case liveActivity = " live_activity "
257+ case dynamicIsland = " dynamic_island "
258+
259+ public var displayName : String {
260+ switch self {
261+ case . homeScreen: return " Home Screen "
262+ case . lockScreen: return " Lock Screen "
263+ case . standBy: return " StandBy "
264+ case . liveActivity: return " Live Activity "
265+ case . dynamicIsland: return " Dynamic Island "
266+ }
267+ }
268+ }
269+
270+ public enum WidgetSize : String , Codable , CaseIterable {
271+ case small = " small "
272+ case medium = " medium "
273+ case large = " large "
274+ case extraLarge = " extra_large "
275+
276+ public var displayName : String {
277+ switch self {
278+ case . small: return " Small "
279+ case . medium: return " Medium "
280+ case . large: return " Large "
281+ case . extraLarge: return " Extra Large "
282+ }
283+ }
284+
285+ public var dimensions : CGSize {
286+ switch self {
287+ case . small: return CGSize ( width: 155 , height: 155 )
288+ case . medium: return CGSize ( width: 329 , height: 155 )
289+ case . large: return CGSize ( width: 329 , height: 345 )
290+ case . extraLarge: return CGSize ( width: 329 , height: 382 )
291+ }
292+ }
293+ }
294+
295+ public struct WidgetConfiguration {
296+ public var enableHomeScreenWidgets : Bool = true
297+ public var enableLockScreenWidgets : Bool = true
298+ public var enableLiveActivities : Bool = true
299+ public var enableDynamicIsland : Bool = true
300+ public var refreshInterval : TimeInterval = 300 // 5 minutes
301+ public var enableBackgroundRefresh : Bool = true
302+ public var enableInteractions : Bool = true
303+ public var enableDeepLinking : Bool = true
304+ }
305+
306+ public struct WidgetData : Codable {
307+ public let content : [ String : Any ]
308+ public let timestamp : Date
309+
310+ enum CodingKeys : String , CodingKey {
311+ case content
312+ case timestamp
313+ }
314+
315+ public init ( content: [ String : Any ] , timestamp: Date = Date ( ) ) {
316+ self . content = content
317+ self . timestamp = timestamp
318+ }
319+
320+ public init ( from decoder: Decoder ) throws {
321+ let container = try decoder. container ( keyedBy: CodingKeys . self)
322+ timestamp = try container. decode ( Date . self, forKey: . timestamp)
323+
324+ // Decode content as Data and convert to dictionary
325+ if let data = try ? container. decode ( Data . self, forKey: . content) ,
326+ let dict = try ? JSONSerialization . jsonObject ( with: data) as? [ String : Any ] {
327+ content = dict
328+ } else {
329+ content = [ : ]
330+ }
331+ }
332+
333+ public func encode( to encoder: Encoder ) throws {
334+ var container = encoder. container ( keyedBy: CodingKeys . self)
335+ try container. encode ( timestamp, forKey: . timestamp)
336+
337+ // Encode content dictionary as Data
338+ if let data = try ? JSONSerialization . data ( withJSONObject: content) {
339+ try container. encode ( data, forKey: . content)
340+ }
341+ }
342+ }
343+
344+ public struct WidgetStatistics {
345+ public let totalWidgets : Int
346+ public let activeWidgets : Int
347+ public let homeScreenWidgets : Int
348+ public let lockScreenWidgets : Int
349+ public let liveActivities : Int
350+ }
351+
352+ public enum WidgetError : LocalizedError {
353+ case widgetNotFound
354+ case invalidConfiguration
355+ case activationFailed
356+ case updateFailed
357+
358+ public var errorDescription : String ? {
359+ switch self {
360+ case . widgetNotFound:
361+ return " Widget not found "
362+ case . invalidConfiguration:
363+ return " Invalid widget configuration "
364+ case . activationFailed:
365+ return " Failed to activate widget "
366+ case . updateFailed:
367+ return " Failed to update widget "
368+ }
369+ }
370+ }
371+
372+ // MARK: - Notifications
373+
374+ extension Notification . Name {
375+ static let widgetConfigurationChanged = Notification . Name ( " widgetConfigurationChanged " )
376+ static let widgetActivated = Notification . Name ( " widgetActivated " )
377+ static let widgetDeactivated = Notification . Name ( " widgetDeactivated " )
378+ static let widgetUpdated = Notification . Name ( " widgetUpdated " )
379+ }
0 commit comments