// // AppWidgetLiveActivity.swift // AppWidget // // Created by Cloud Solutions on 15/12/2025. // import ActivityKit import WidgetKit import SwiftUI struct LiveActivitiesAppAttributes: ActivityAttributes, Identifiable { public typealias LiveDeliveryData = ContentState // don't forget to add this line, otherwise, live activity will not display it. public struct ContentState: Codable, Hashable { } var id = UUID() } let sharedDefault = UserDefaults(suiteName: "group.alhabib.patientApp")! struct AppWidgetAttributes: ActivityAttributes { public struct ContentState: Codable, Hashable { // Dynamic stateful properties about your activity go here! var emoji: String } // Fixed non-changing properties about your activity go here! var name: String } extension Color { static let redColorText = Color(hex: "#ED1C2B") static let yellowColor = Color(hex: "#FFAF15") static let successColor = Color(hex: "#18C273") static let inactiveColor = Color(hex: "#ED1C2B") static let textColor = Color(hex: "#A2A2A2") static let colorWhite = Color(hex: "#FFFFFF") // Helper initializer for hex colors init(hex: String) { let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted) var int: UInt64 = 0 Scanner(string: hex).scanHexInt64(&int) let a, r, g, b: UInt64 switch hex.count { case 3: // RGB (12-bit) (a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17) case 6: // RGB (24-bit) (a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF) case 8: // ARGB (32-bit) (a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF) default: (a, r, g, b) = (255, 0, 0, 0) } self.init( .sRGB, red: Double(r) / 255, green: Double(g) / 255, blue: Double(b) / 255, opacity: Double(a) / 255 ) } } // MARK: - Widget Configuration based on nextStepBackground struct WidgetColorConfig { let nextStepBackground: Color let nextStepTextColor: Color let containerBorderColor: Color static func config(for value: Int) -> WidgetColorConfig { switch value { case 0: return WidgetColorConfig( nextStepBackground: .inactiveColor, nextStepTextColor: .textColor, containerBorderColor: .yellowColor ) case 1: return WidgetColorConfig( nextStepBackground: .redColorText, nextStepTextColor: .colorWhite, containerBorderColor: .redColorText ) case 2: return WidgetColorConfig( nextStepBackground: .successColor, nextStepTextColor: .colorWhite, containerBorderColor: .successColor ) default: return WidgetColorConfig( nextStepBackground: .inactiveColor, nextStepTextColor: .textColor, containerBorderColor: .yellowColor ) } } } // MARK: - Widget Data Model struct NotificationWidgetData { let currentTicket: String let currentlyServingHeader: String let currentlyServing: String let status: String let nextStep: String let imageName: String let colorConfig: WidgetColorConfig } // MARK: - Main Widget View struct NotificationWidgetView: View { private let data: NotificationWidgetData init(data: NotificationWidgetData) { self.data = data } var body: some View { VStack(alignment: .leading, spacing: 0) { // Top section with current ticket and image HStack(alignment: .top, spacing: 0) { // Current ticket value Text(data.currentTicket) .font(.custom("Poppins-Medium", size: 28)) .fontWeight(.bold) .foregroundColor(.black) .padding(.trailing, 10) .frame(maxWidth: .infinity, alignment: .leading) // Image Image(systemName: data.imageName) .resizable() .scaledToFit() .frame(width: 24, height: 24) .foregroundColor(.gray) } // Currently Serving Header Text(data.currentlyServingHeader) .font(.custom("Poppins-Medium", size: 14)) .fontWeight(.bold) .foregroundColor(.black) .padding(.trailing, 12) .padding(.bottom, 10) // Currently Serving section with status HStack(alignment: .center, spacing: 0) { // Currently serving text Text(data.currentlyServing) .font(.custom("Poppins-Medium", size: 16)) .fontWeight(.bold) .foregroundColor(.black) .frame(maxWidth: .infinity, alignment: .leading) // Status badge Text(data.status) .font(.custom("Poppins-Medium", size: 12)) .foregroundColor(data.colorConfig.nextStepTextColor) .padding(.horizontal, 12) .padding(.vertical, 8) .background( RoundedRectangle(cornerRadius: 8) .fill(Color.inactiveColor.opacity(0.2)) ) } // Next Step badge Text(data.nextStep) .font(.custom("Poppins-Medium", size: 12)) .foregroundColor(data.colorConfig.nextStepTextColor) .padding(.horizontal, 12) .padding(.vertical, 8) .background( RoundedRectangle(cornerRadius: 8) .fill(data.colorConfig.nextStepBackground) ) .padding(.top, 10) } .padding(12) .background( // 1. Use a Material for the glass/frosted effect .thinMaterial, // Define the shape for the material and the stroke in: RoundedRectangle(cornerRadius: 12, style: .continuous) ) .overlay( // 2. Add the stroke (border) around the shape RoundedRectangle(cornerRadius: 12, style: .continuous) .stroke(data.colorConfig.containerBorderColor, lineWidth: 5) ) } } // MARK: - Live Activity Widget struct AppWidgetLiveActivity: Widget { var body: some WidgetConfiguration { ActivityConfiguration(for: LiveActivitiesAppAttributes.self) { context in // Extract parameters from Flutter let title = sharedDefault.string(forKey: context.attributes.prefixedKey("title")) ?? "test" let contentText = sharedDefault.string(forKey: context.attributes.prefixedKey("contentText")) ?? "test" let currentTicket = sharedDefault.string(forKey: context.attributes.prefixedKey("currentTicket")) ?? "test" let status = sharedDefault.string(forKey: context.attributes.prefixedKey("status")) ?? "test" let currentlyServing = sharedDefault.string(forKey: context.attributes.prefixedKey("currentlyServing")) ?? "test" let nextStep = sharedDefault.string(forKey: context.attributes.prefixedKey("nextStep")) ?? "test" let nextStepBackground = sharedDefault.integer(forKey: context.attributes.prefixedKey("nextStepBackground")) // Get color configuration based on nextStepBackground let colorConfig = WidgetColorConfig.config(for: nextStepBackground) // Create widget data let widgetData = NotificationWidgetData( currentTicket: currentTicket, currentlyServingHeader: "Currently Serving:", currentlyServing: currentlyServing, status: status, nextStep: nextStep, imageName: "bell.fill", colorConfig: colorConfig ) // Lock screen/banner UI NotificationWidgetView(data: widgetData) .activityBackgroundTint(Color.clear) .activitySystemActionForegroundColor(Color.black) } dynamicIsland: { context in let currentTicket = sharedDefault.string(forKey: context.attributes.prefixedKey("currentTicket")) ?? "" let status = sharedDefault.string(forKey: context.attributes.prefixedKey("status")) ?? "" let currentlyServing = sharedDefault.string(forKey: context.attributes.prefixedKey("currentlyServing")) ?? "" let nextStep = sharedDefault.string(forKey: context.attributes.prefixedKey("nextStep")) ?? "" let nextStepBackground = sharedDefault.integer(forKey: context.attributes.prefixedKey("nextStepBackground")) let colorConfig = WidgetColorConfig.config(for: nextStepBackground) return DynamicIsland { // Expanded UI DynamicIslandExpandedRegion(.leading) { VStack(alignment: .leading) { Text(currentTicket) .font(.system(size: 24, weight: .bold)) .foregroundColor(.primary) } } DynamicIslandExpandedRegion(.trailing) { Image(systemName: "bell.fill") .foregroundColor(.gray) } DynamicIslandExpandedRegion(.center) { VStack(spacing: 4) { Text("Currently Serving:") .font(.caption2) .foregroundColor(.secondary) Text(currentlyServing) .font(.caption) .fontWeight(.semibold) } } DynamicIslandExpandedRegion(.bottom) { HStack { Text(status) .font(.caption2) .padding(.horizontal, 8) .padding(.vertical, 4) .background( RoundedRectangle(cornerRadius: 6) .fill(colorConfig.nextStepBackground.opacity(0.2)) ) Spacer() Text(nextStep) .font(.caption2) .foregroundColor(colorConfig.nextStepTextColor) .padding(.horizontal, 8) .padding(.vertical, 4) .background( RoundedRectangle(cornerRadius: 6) .fill(colorConfig.nextStepBackground) ) } .padding(.horizontal) } } compactLeading: { // Compact leading view Text(currentTicket) .font(.caption2) .fontWeight(.bold) } compactTrailing: { // Compact trailing view Image(systemName: "bell.fill") .foregroundColor(colorConfig.containerBorderColor) } minimal: { // Minimal view Image(systemName: "bell.fill") .foregroundColor(colorConfig.containerBorderColor) } .widgetURL(URL(string: "http://www.apple.com")) .keylineTint(colorConfig.containerBorderColor) } } } extension LiveActivitiesAppAttributes { func prefixedKey(_ key: String) -> String { return "\(id)_\(key)" } } // MARK: - Preview for Testing struct NotificationWidgetView_Previews: PreviewProvider { static var previews: some View { Group { // Preview with nextStepBackground = 0 (Yellow border) NotificationWidgetView( data: NotificationWidgetData( currentTicket: "42", currentlyServingHeader: "Currently Serving:", currentlyServing: "Customer #15", status: "Pending", nextStep: "Next: Prepare Order", imageName: "bell.fill", colorConfig: .config(for: 0) ) ) .previewDisplayName("Yellow Border (0)") // Preview with nextStepBackground = 1 (Red border) NotificationWidgetView( data: NotificationWidgetData( currentTicket: "89", currentlyServingHeader: "Currently Serving:", currentlyServing: "Customer #67", status: "Active", nextStep: "Next: Quality Check", imageName: "bell.fill", colorConfig: .config(for: 1) ) ) .previewDisplayName("Red Border (1)") // Preview with nextStepBackground = 2 (Green border) NotificationWidgetView( data: NotificationWidgetData( currentTicket: "125", currentlyServingHeader: "Currently Serving:", currentlyServing: "Customer #98", status: "Completed", nextStep: "Next: Ready for Pickup", imageName: "bell.fill", colorConfig: .config(for: 2) ) ) .previewDisplayName("Green Border (2)") } .padding() .previewLayout(.sizeThatFits) } }