- Add Apple Sign-In backend (JWKS verification, account linking, session management) - Implement push notification deep linking with NotificationDeepLinkRouter - Add jailbreak detection, runtime integrity monitoring, secure enclave service - Implement OAuth social login, token refresh, and secure logout flows - Add image caching (memory/disk), optimizer, upload queue, async semaphore - Implement notification analytics, type preferences, and category setup - Expand UI test suite with UITestBase, accessibility, auth flow, performance tests - Add CI pipeline for iOS UI tests (3 device sizes) and performance benchmarks - Restructure Xcode project to manual groups with KordantWidgets target - Add SwiftLint, Swift Collections/Algorithms/GoogleSignIn dependencies - Update project.yml for XcodeGen with new targets and configurations
164 lines
5.0 KiB
Swift
164 lines
5.0 KiB
Swift
import SwiftUI
|
|
import WidgetKit
|
|
|
|
// MARK: - Widget Bundle
|
|
|
|
@main
|
|
struct KordantWidgets: WidgetBundle {
|
|
var body: some Widget {
|
|
ThreatScoreWidget() // systemSmall
|
|
AlertSummaryWidget() // systemMedium
|
|
FullDashboardWidget() // systemLarge
|
|
}
|
|
}
|
|
|
|
// MARK: - Timeline Provider
|
|
|
|
struct KordantWidgetProvider: IntentTimelineProvider {
|
|
typealias Entry = WidgetEntry
|
|
typealias Intent = KordantWidgetConfigurationIntent
|
|
|
|
func placeholder(in context: Context) -> WidgetEntry {
|
|
WidgetEntry(
|
|
date: Date(),
|
|
widgetData: .placeholder,
|
|
severityFilter: .all,
|
|
isPlaceholder: true
|
|
)
|
|
}
|
|
|
|
func getSnapshot(
|
|
for configuration: KordantWidgetConfigurationIntent,
|
|
in context: Context,
|
|
completion: @escaping (WidgetEntry) -> Void
|
|
) {
|
|
Task {
|
|
let data = WidgetDataManager.shared.load() ?? .placeholder
|
|
let entry = WidgetEntry(
|
|
date: Date(),
|
|
widgetData: data,
|
|
severityFilter: configuration.severityFilter,
|
|
isPlaceholder: context.isPreview
|
|
)
|
|
completion(entry)
|
|
}
|
|
}
|
|
|
|
func getTimeline(
|
|
for configuration: KordantWidgetConfigurationIntent,
|
|
in context: Context,
|
|
completion: @escaping (Timeline<WidgetEntry>) -> Void
|
|
) {
|
|
Task {
|
|
let data = WidgetDataManager.shared.load() ?? .unavailable
|
|
let entry = WidgetEntry(
|
|
date: Date(),
|
|
widgetData: data,
|
|
severityFilter: configuration.severityFilter,
|
|
isPlaceholder: false
|
|
)
|
|
|
|
// Widgets must refresh at most every 15 minutes per system policy.
|
|
let nextRefresh = Calendar.current.date(
|
|
byAdding: .minute,
|
|
value: 15,
|
|
to: Date()
|
|
) ?? Date().addingTimeInterval(900)
|
|
|
|
// If data is unavailable, retry sooner (5 minutes) to pick up
|
|
// initial data after the user opens the app.
|
|
let refreshDate = data == .unavailable
|
|
? Date().addingTimeInterval(300)
|
|
: nextRefresh
|
|
|
|
let timeline = Timeline(entries: [entry], policy: .after(refreshDate))
|
|
completion(timeline)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - System Small: Threat Score Gauge
|
|
|
|
struct ThreatScoreWidget: Widget {
|
|
let kind: String = "com.frenocorp.kordant.widget.threatScore"
|
|
|
|
var body: some WidgetConfiguration {
|
|
IntentConfiguration(
|
|
kind: kind,
|
|
intent: KordantWidgetConfigurationIntent.self,
|
|
provider: KordantWidgetProvider()
|
|
) { entry in
|
|
KordantWidgetsEntryView(entry: entry)
|
|
.containerBackground(.widgetBackground, for: .widget)
|
|
}
|
|
.configurationDisplayName("Threat Score")
|
|
.description("Your current Kordant threat score at a glance.")
|
|
.supportedFamilies([.systemSmall])
|
|
.contentMarginsDisabled()
|
|
}
|
|
}
|
|
|
|
// MARK: - System Medium: Threat Score + Recent Alerts
|
|
|
|
struct AlertSummaryWidget: Widget {
|
|
let kind: String = "com.frenocorp.kordant.widget.alertSummary"
|
|
|
|
var body: some WidgetConfiguration {
|
|
IntentConfiguration(
|
|
kind: kind,
|
|
intent: KordantWidgetConfigurationIntent.self,
|
|
provider: KordantWidgetProvider()
|
|
) { entry in
|
|
KordantWidgetsEntryView(entry: entry)
|
|
.containerBackground(.widgetBackground, for: .widget)
|
|
}
|
|
.configurationDisplayName("Alert Summary")
|
|
.description("Your threat score with up to 2 recent alerts.")
|
|
.supportedFamilies([.systemMedium])
|
|
.contentMarginsDisabled()
|
|
}
|
|
}
|
|
|
|
// MARK: - System Large: Full Dashboard
|
|
|
|
struct FullDashboardWidget: Widget {
|
|
let kind: String = "com.frenocorp.kordant.widget.fullDashboard"
|
|
|
|
var body: some WidgetConfiguration {
|
|
IntentConfiguration(
|
|
kind: kind,
|
|
intent: KordantWidgetConfigurationIntent.self,
|
|
provider: KordantWidgetProvider()
|
|
) { entry in
|
|
KordantWidgetsEntryView(entry: entry)
|
|
.containerBackground(.widgetBackground, for: .widget)
|
|
}
|
|
.configurationDisplayName("Security Dashboard")
|
|
.description("Full dashboard with threat score, alerts, stats, and quick actions.")
|
|
.supportedFamilies([.systemLarge])
|
|
.contentMarginsDisabled()
|
|
}
|
|
}
|
|
|
|
// MARK: - Entry View Router
|
|
|
|
struct KordantWidgetsEntryView: View {
|
|
let entry: WidgetEntry
|
|
@Environment(\.widgetFamily) var family
|
|
|
|
var body: some View {
|
|
Group {
|
|
switch family {
|
|
case .systemSmall:
|
|
SmallWidgetView(entry: entry)
|
|
case .systemMedium:
|
|
MediumWidgetView(entry: entry)
|
|
case .systemLarge:
|
|
LargeWidgetView(entry: entry)
|
|
@unknown default:
|
|
SmallWidgetView(entry: entry)
|
|
}
|
|
}
|
|
}
|
|
}
|