From 0fc057fd7d9ea705a113c5af8b6f11c458caec9f Mon Sep 17 00:00:00 2001 From: tahaalam Date: Wed, 17 Dec 2025 15:28:59 +0300 Subject: [PATCH] notification shown with the custom input --- ios/AppWidget/AppWidgetLiveActivity.swift | 356 +++++++++++++++++++--- 1 file changed, 318 insertions(+), 38 deletions(-) diff --git a/ios/AppWidget/AppWidgetLiveActivity.swift b/ios/AppWidget/AppWidgetLiveActivity.swift index 1e6ff4c..d558748 100644 --- a/ios/AppWidget/AppWidgetLiveActivity.swift +++ b/ios/AppWidget/AppWidgetLiveActivity.swift @@ -15,13 +15,9 @@ struct LiveActivitiesAppAttributes: ActivityAttributes, Identifiable { var id = UUID() } -extension LiveActivitiesAppAttributes { - func prefixedKey(_ key: String) -> String { - return "\(id)_\(key)" - } -} -let sharedDefault = UserDefaults(suiteName: "group.alhabib.pateientApp")! + +let sharedDefault = UserDefaults(suiteName: "group.alhabib.patientApp")! struct AppWidgetAttributes: ActivityAttributes { public struct ContentState: Codable, Hashable { // Dynamic stateful properties about your activity go here! @@ -32,56 +28,340 @@ struct AppWidgetAttributes: ActivityAttributes { 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 - // Lock screen/banner UI goes here - VStack { - Text("Hello patient") - } - .activityBackgroundTint(Color.cyan) - .activitySystemActionForegroundColor(Color.black) + // 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 - DynamicIsland { - // Expanded UI goes here. Compose the expanded UI through - // various regions, like leading/trailing/center/bottom + + 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) { - Text("Leading") + VStack(alignment: .leading) { + Text(currentTicket) + .font(.system(size: 24, weight: .bold)) + .foregroundColor(.primary) + } } + DynamicIslandExpandedRegion(.trailing) { - Text("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: { - Text("Leading") + // Compact leading view + Text(currentTicket) + .font(.caption2) + .fontWeight(.bold) } compactTrailing: { - Text("trailing") + // Compact trailing view + Image(systemName: "bell.fill") + .foregroundColor(colorConfig.containerBorderColor) } minimal: { - Text("minimal") + // Minimal view + Image(systemName: "bell.fill") + .foregroundColor(colorConfig.containerBorderColor) } .widgetURL(URL(string: "http://www.apple.com")) - .keylineTint(Color.red) + .keylineTint(colorConfig.containerBorderColor) + } } } -} - -extension AppWidgetAttributes { - fileprivate static var preview: AppWidgetAttributes { - AppWidgetAttributes(name: "World") - } +extension LiveActivitiesAppAttributes { + func prefixedKey(_ key: String) -> String { + return "\(id)_\(key)" + } } - -extension AppWidgetAttributes.ContentState { - fileprivate static var smiley: AppWidgetAttributes.ContentState { - AppWidgetAttributes.ContentState(emoji: "😀") - } - - fileprivate static var starEyes: AppWidgetAttributes.ContentState { - AppWidgetAttributes.ContentState(emoji: "🤩") - } +// 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) + } } -