Files
Options-SideKick/ios/OptionsSidekick/OptionsSidekick/Views/Alerts/AlertsView.swift
olsch01 b7d4e900cc Initial implementation of Options Sidekick
Full-stack iOS options trading assistant:
- Python FastAPI backend with SQLite, APScheduler (15-min position monitor),
  APNs push notifications, and yfinance market data integration
- Signal engine: IV Rank (rolling HV proxy), SMA-50/200, swing-based
  support/resistance, earnings detection, signal strength scoring and
  noise-resistant SHA hash for change detection
- Recommendation engine: covered call and cash-secured put strike/expiry
  selection across 0DTE, 1DTE, weekly, and monthly horizons
- REST API: /devices, /portfolio, /recommendations, /positions, /signals, /alerts
- iOS SwiftUI app (iOS 17+): dashboard, recommendations, trades, portfolio,
  and alerts tabs with push notification deep-linking
- Unit + integration tests for signal engine and API layer

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 14:38:25 -04:00

88 lines
3.0 KiB
Swift

import SwiftUI
struct AlertsView: View {
@StateObject private var vm = AlertsViewModel()
var body: some View {
NavigationStack {
Group {
if vm.isLoading && vm.alerts.isEmpty {
LoadingView(message: "Loading alerts...")
} else if vm.alerts.isEmpty {
EmptyStateView(
icon: "bell.slash",
title: "No alerts",
subtitle: "Alerts appear here when signal changes on your open positions."
)
} else {
List(vm.alerts) { alert in
AlertRowView(alert: alert) {
Task { await vm.acknowledge(alert) }
}
}
.listStyle(.insetGrouped)
.refreshable { await vm.load() }
}
}
.navigationTitle("Alerts")
.toolbar {
if vm.unreadCount > 0 {
ToolbarItem(placement: .navigationBarTrailing) {
Button("Mark All Read") {
Task { await vm.acknowledgeAll() }
}
.font(.caption)
}
}
}
}
.task { await vm.load() }
}
}
// Row
struct AlertRowView: View {
let alert: AppAlert
let onAcknowledge: () -> Void
var body: some View {
HStack(alignment: .top, spacing: 10) {
// Unread indicator
Circle()
.fill(alert.acknowledged ? Color.clear : Constants.Color.accent)
.frame(width: 8, height: 8)
.padding(.top, 5)
VStack(alignment: .leading, spacing: 4) {
HStack(spacing: 6) {
Text(alert.ticker)
.font(.headline)
AlertTypeBadge(alertType: alert.alertType)
}
Text(alert.message)
.font(.subheadline)
.foregroundStyle(.primary)
.fixedSize(horizontal: false, vertical: true)
Text(RelativeDateTimeFormatter().localizedString(for: alert.sentAt, relativeTo: Date()))
.font(.caption)
.foregroundStyle(.tertiary)
}
Spacer()
if !alert.acknowledged {
Button {
onAcknowledge()
} label: {
Image(systemName: "checkmark.circle")
.foregroundStyle(Constants.Color.accent)
}
.buttonStyle(.plain)
}
}
.padding(.vertical, 4)
.opacity(alert.acknowledged ? 0.6 : 1.0)
}
}