feat: complete Tasks 21-28 — backend integration, security hardening, UI tests & CI
- 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
This commit is contained in:
163
iOS/KordantWidgets/KordantWidgets.swift
Normal file
163
iOS/KordantWidgets/KordantWidgets.swift
Normal file
@@ -0,0 +1,163 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user