diff --git a/ios/OptionsSidekick/OptionsSidekick/Networking/APIClient.swift b/ios/OptionsSidekick/OptionsSidekick/Networking/APIClient.swift index 11005d4..b7d78fa 100644 --- a/ios/OptionsSidekick/OptionsSidekick/Networking/APIClient.swift +++ b/ios/OptionsSidekick/OptionsSidekick/Networking/APIClient.swift @@ -19,7 +19,6 @@ final class APIClient { d.dateDecodingStrategy = .custom { decoder in let container = try decoder.singleValueContainer() let str = try container.decode(String.self) - // Try ISO8601 with fractional seconds first, then without let formatters: [ISO8601DateFormatter] = [ { let f = ISO8601DateFormatter() @@ -35,11 +34,12 @@ final class APIClient { for fmt in formatters { if let date = fmt.date(from: str) { return date } } - // Try plain date (YYYY-MM-DD) for date-only fields decoded as Date let df = DateFormatter() df.dateFormat = "yyyy-MM-dd" if let date = df.date(from: str) { return date } - throw DecodingError.dataCorrupted(.init(codingPath: decoder.codingPath, debugDescription: "Cannot parse date: \(str)")) + throw DecodingError.dataCorrupted( + .init(codingPath: decoder.codingPath, + debugDescription: "Cannot parse date: \(str)")) } return d }() @@ -54,7 +54,7 @@ final class APIClient { func request( _ endpoint: Endpoint, - body: (some Encodable)? = Optional.none + body: (any Encodable)? = nil ) async throws -> T { guard let token = LocalStore.shared.deviceToken else { throw APIError.noDeviceToken @@ -66,7 +66,7 @@ final class APIClient { } /// Version that doesn't return a body (e.g. DELETE 204) - func requestVoid(_ endpoint: Endpoint, body: (some Encodable)? = Optional.none) async throws { + func requestVoid(_ endpoint: Endpoint, body: (any Encodable)? = nil) async throws { guard let token = LocalStore.shared.deviceToken else { throw APIError.noDeviceToken } @@ -76,7 +76,10 @@ final class APIClient { } /// For device registration (no token yet) - func requestNoAuth(_ endpoint: Endpoint, body: (some Encodable)? = Optional.none) async throws -> T { + func requestNoAuth( + _ endpoint: Endpoint, + body: (any Encodable)? = nil + ) async throws -> T { let req = try buildRequest(endpoint, deviceToken: nil, body: body) let (data, response) = try await session.data(for: req) try validateResponse(response, data: data) @@ -85,7 +88,11 @@ final class APIClient { // ─── Helpers ────────────────────────────────────────────────────────────── - private func buildRequest(_ endpoint: Endpoint, deviceToken: String?, body: (some Encodable)?) throws -> URLRequest { + private func buildRequest( + _ endpoint: Endpoint, + deviceToken: String?, + body: (any Encodable)? + ) throws -> URLRequest { guard let url = URL(string: Constants.apiBaseURL + endpoint.path) else { throw APIError.invalidURL } @@ -96,11 +103,19 @@ final class APIClient { request.setValue(token, forHTTPHeaderField: "X-Device-Token") } if let body { - request.httpBody = try encoder.encode(body) + request.httpBody = try encodeAny(body) } return request } + /// Opens the `any Encodable` existential so JSONEncoder can encode it. + private func encodeAny(_ value: any Encodable) throws -> Data { + func encode(_ v: T) throws -> Data { + try encoder.encode(v) + } + return try encode(value) + } + private func validateResponse(_ response: URLResponse, data: Data) throws { guard let httpResponse = response as? HTTPURLResponse else { throw APIError.invalidResponse @@ -121,8 +136,6 @@ final class APIClient { // ─── Supporting types ────────────────────────────────────────────────────────── -private struct EmptyBody: Encodable {} - enum APIError: LocalizedError { case noDeviceToken case invalidURL diff --git a/ios/OptionsSidekick/OptionsSidekick/Notifications/NotificationPermissions.swift b/ios/OptionsSidekick/OptionsSidekick/Notifications/NotificationPermissions.swift index 64649b8..4e74408 100644 --- a/ios/OptionsSidekick/OptionsSidekick/Notifications/NotificationPermissions.swift +++ b/ios/OptionsSidekick/OptionsSidekick/Notifications/NotificationPermissions.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit import UserNotifications @MainActor diff --git a/ios/OptionsSidekick/OptionsSidekick/OptionsSidekick/OptionsSidekick.xcodeproj/project.xcworkspace/xcuserdata/claw.xcuserdatad/UserInterfaceState.xcuserstate b/ios/OptionsSidekick/OptionsSidekick/OptionsSidekick/OptionsSidekick.xcodeproj/project.xcworkspace/xcuserdata/claw.xcuserdatad/UserInterfaceState.xcuserstate index 0dd3a61..53dd5d6 100644 Binary files a/ios/OptionsSidekick/OptionsSidekick/OptionsSidekick/OptionsSidekick.xcodeproj/project.xcworkspace/xcuserdata/claw.xcuserdatad/UserInterfaceState.xcuserstate and b/ios/OptionsSidekick/OptionsSidekick/OptionsSidekick/OptionsSidekick.xcodeproj/project.xcworkspace/xcuserdata/claw.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/ios/OptionsSidekick/OptionsSidekick/OptionsSidekick/OptionsSidekick/AppDelegate.swift b/ios/OptionsSidekick/OptionsSidekick/OptionsSidekick/OptionsSidekick/AppDelegate.swift index 788c0e1..af54bf0 100644 --- a/ios/OptionsSidekick/OptionsSidekick/OptionsSidekick/OptionsSidekick/AppDelegate.swift +++ b/ios/OptionsSidekick/OptionsSidekick/OptionsSidekick/OptionsSidekick/AppDelegate.swift @@ -42,9 +42,7 @@ final class AppDelegate: NSObject, UIApplicationDelegate { apns_token: token, device_name: UIDevice.current.name ) - // Temporarily set the token so APIClient has it, but use requestNoAuth - // since this is the registration call itself - let old = LocalStore.shared.deviceToken + // Ensure token is stored before the request so APIClient can use it LocalStore.shared.deviceToken = token let response: DeviceResponse = try await APIClient.shared.requestNoAuth( .registerDevice, diff --git a/ios/OptionsSidekick/OptionsSidekick/OptionsSidekick/OptionsSidekick/Networking/APIClient.swift b/ios/OptionsSidekick/OptionsSidekick/OptionsSidekick/OptionsSidekick/Networking/APIClient.swift index 11005d4..b7d78fa 100644 --- a/ios/OptionsSidekick/OptionsSidekick/OptionsSidekick/OptionsSidekick/Networking/APIClient.swift +++ b/ios/OptionsSidekick/OptionsSidekick/OptionsSidekick/OptionsSidekick/Networking/APIClient.swift @@ -19,7 +19,6 @@ final class APIClient { d.dateDecodingStrategy = .custom { decoder in let container = try decoder.singleValueContainer() let str = try container.decode(String.self) - // Try ISO8601 with fractional seconds first, then without let formatters: [ISO8601DateFormatter] = [ { let f = ISO8601DateFormatter() @@ -35,11 +34,12 @@ final class APIClient { for fmt in formatters { if let date = fmt.date(from: str) { return date } } - // Try plain date (YYYY-MM-DD) for date-only fields decoded as Date let df = DateFormatter() df.dateFormat = "yyyy-MM-dd" if let date = df.date(from: str) { return date } - throw DecodingError.dataCorrupted(.init(codingPath: decoder.codingPath, debugDescription: "Cannot parse date: \(str)")) + throw DecodingError.dataCorrupted( + .init(codingPath: decoder.codingPath, + debugDescription: "Cannot parse date: \(str)")) } return d }() @@ -54,7 +54,7 @@ final class APIClient { func request( _ endpoint: Endpoint, - body: (some Encodable)? = Optional.none + body: (any Encodable)? = nil ) async throws -> T { guard let token = LocalStore.shared.deviceToken else { throw APIError.noDeviceToken @@ -66,7 +66,7 @@ final class APIClient { } /// Version that doesn't return a body (e.g. DELETE 204) - func requestVoid(_ endpoint: Endpoint, body: (some Encodable)? = Optional.none) async throws { + func requestVoid(_ endpoint: Endpoint, body: (any Encodable)? = nil) async throws { guard let token = LocalStore.shared.deviceToken else { throw APIError.noDeviceToken } @@ -76,7 +76,10 @@ final class APIClient { } /// For device registration (no token yet) - func requestNoAuth(_ endpoint: Endpoint, body: (some Encodable)? = Optional.none) async throws -> T { + func requestNoAuth( + _ endpoint: Endpoint, + body: (any Encodable)? = nil + ) async throws -> T { let req = try buildRequest(endpoint, deviceToken: nil, body: body) let (data, response) = try await session.data(for: req) try validateResponse(response, data: data) @@ -85,7 +88,11 @@ final class APIClient { // ─── Helpers ────────────────────────────────────────────────────────────── - private func buildRequest(_ endpoint: Endpoint, deviceToken: String?, body: (some Encodable)?) throws -> URLRequest { + private func buildRequest( + _ endpoint: Endpoint, + deviceToken: String?, + body: (any Encodable)? + ) throws -> URLRequest { guard let url = URL(string: Constants.apiBaseURL + endpoint.path) else { throw APIError.invalidURL } @@ -96,11 +103,19 @@ final class APIClient { request.setValue(token, forHTTPHeaderField: "X-Device-Token") } if let body { - request.httpBody = try encoder.encode(body) + request.httpBody = try encodeAny(body) } return request } + /// Opens the `any Encodable` existential so JSONEncoder can encode it. + private func encodeAny(_ value: any Encodable) throws -> Data { + func encode(_ v: T) throws -> Data { + try encoder.encode(v) + } + return try encode(value) + } + private func validateResponse(_ response: URLResponse, data: Data) throws { guard let httpResponse = response as? HTTPURLResponse else { throw APIError.invalidResponse @@ -121,8 +136,6 @@ final class APIClient { // ─── Supporting types ────────────────────────────────────────────────────────── -private struct EmptyBody: Encodable {} - enum APIError: LocalizedError { case noDeviceToken case invalidURL diff --git a/ios/OptionsSidekick/OptionsSidekick/OptionsSidekick/OptionsSidekick/Notifications/NotificationHandler.swift b/ios/OptionsSidekick/OptionsSidekick/OptionsSidekick/OptionsSidekick/Notifications/NotificationHandler.swift index b534bed..9e335ce 100644 --- a/ios/OptionsSidekick/OptionsSidekick/OptionsSidekick/OptionsSidekick/Notifications/NotificationHandler.swift +++ b/ios/OptionsSidekick/OptionsSidekick/OptionsSidekick/OptionsSidekick/Notifications/NotificationHandler.swift @@ -1,4 +1,5 @@ import Foundation +import Combine import UserNotifications /// Handles incoming push notifications — both foreground and background tap. diff --git a/ios/OptionsSidekick/OptionsSidekick/OptionsSidekick/OptionsSidekick/Notifications/NotificationPermissions.swift b/ios/OptionsSidekick/OptionsSidekick/OptionsSidekick/OptionsSidekick/Notifications/NotificationPermissions.swift index 64649b8..4e74408 100644 --- a/ios/OptionsSidekick/OptionsSidekick/OptionsSidekick/OptionsSidekick/Notifications/NotificationPermissions.swift +++ b/ios/OptionsSidekick/OptionsSidekick/OptionsSidekick/OptionsSidekick/Notifications/NotificationPermissions.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit import UserNotifications @MainActor diff --git a/ios/OptionsSidekick/OptionsSidekick/OptionsSidekick/OptionsSidekick/ViewModels/AlertsViewModel.swift b/ios/OptionsSidekick/OptionsSidekick/OptionsSidekick/OptionsSidekick/ViewModels/AlertsViewModel.swift index 4e3924c..0a2b8b3 100644 --- a/ios/OptionsSidekick/OptionsSidekick/OptionsSidekick/OptionsSidekick/ViewModels/AlertsViewModel.swift +++ b/ios/OptionsSidekick/OptionsSidekick/OptionsSidekick/OptionsSidekick/ViewModels/AlertsViewModel.swift @@ -1,4 +1,5 @@ import Foundation +import Combine @MainActor final class AlertsViewModel: ObservableObject { @@ -14,7 +15,7 @@ final class AlertsViewModel: ObservableObject { do { alerts = try await APIClient.shared.request( .getAlerts(unreadOnly: unreadOnly), - body: Optional.none + body: nil ) LocalStore.shared.unreadAlertCount = unreadCount } catch { @@ -27,7 +28,7 @@ final class AlertsViewModel: ObservableObject { do { let updated: AppAlert = try await APIClient.shared.request( .acknowledgeAlert(alert.id), - body: Optional.none + body: nil ) if let idx = alerts.firstIndex(where: { $0.id == alert.id }) { alerts[idx] = updated diff --git a/ios/OptionsSidekick/OptionsSidekick/OptionsSidekick/OptionsSidekick/ViewModels/DashboardViewModel.swift b/ios/OptionsSidekick/OptionsSidekick/OptionsSidekick/OptionsSidekick/ViewModels/DashboardViewModel.swift index 20f6762..e16d805 100644 --- a/ios/OptionsSidekick/OptionsSidekick/OptionsSidekick/OptionsSidekick/ViewModels/DashboardViewModel.swift +++ b/ios/OptionsSidekick/OptionsSidekick/OptionsSidekick/OptionsSidekick/ViewModels/DashboardViewModel.swift @@ -45,18 +45,18 @@ final class DashboardViewModel: ObservableObject { // ─── Private loaders ────────────────────────────────────────────────────── private func loadStocks() async -> [PortfolioPosition] { - (try? await APIClient.shared.request(.getPortfolio, body: Optional.none)) ?? [] + (try? await APIClient.shared.request(.getPortfolio, body: nil)) ?? [] } private func loadOptions() async -> [OptionPosition] { - (try? await APIClient.shared.request(.getPositions(status: "open"), body: Optional.none)) ?? [] + (try? await APIClient.shared.request(.getPositions(status: "open"), body: nil)) ?? [] } private func loadRecommendations() async -> [Recommendation] { - (try? await APIClient.shared.request(.getRecommendations(timeHorizon: nil), body: Optional.none)) ?? [] + (try? await APIClient.shared.request(.getRecommendations(timeHorizon: nil), body: nil)) ?? [] } private func loadAlerts() async -> [AppAlert] { - (try? await APIClient.shared.request(.getAlerts(unreadOnly: true), body: Optional.none)) ?? [] + (try? await APIClient.shared.request(.getAlerts(unreadOnly: true), body: nil)) ?? [] } } diff --git a/ios/OptionsSidekick/OptionsSidekick/OptionsSidekick/OptionsSidekick/ViewModels/PortfolioViewModel.swift b/ios/OptionsSidekick/OptionsSidekick/OptionsSidekick/OptionsSidekick/ViewModels/PortfolioViewModel.swift index 1b8ae0e..3f0dc54 100644 --- a/ios/OptionsSidekick/OptionsSidekick/OptionsSidekick/OptionsSidekick/ViewModels/PortfolioViewModel.swift +++ b/ios/OptionsSidekick/OptionsSidekick/OptionsSidekick/OptionsSidekick/ViewModels/PortfolioViewModel.swift @@ -1,4 +1,5 @@ import Foundation +import Combine @MainActor final class PortfolioViewModel: ObservableObject { @@ -10,7 +11,7 @@ final class PortfolioViewModel: ObservableObject { isLoading = true error = nil do { - positions = try await APIClient.shared.request(.getPortfolio, body: Optional.none) + positions = try await APIClient.shared.request(.getPortfolio, body: nil) } catch { self.error = error.localizedDescription } @@ -35,7 +36,7 @@ final class PortfolioViewModel: ObservableObject { } func add(ticker: String, shares: Int, costBasis: Double?) async { - var updated = positions + let updated = positions struct AddBody: Encodable { let ticker: String let shares: Int @@ -57,7 +58,7 @@ final class PortfolioViewModel: ObservableObject { isLoading = true error = nil do { - try await APIClient.shared.requestVoid(.deleteTicker(ticker), body: Optional.none) + try await APIClient.shared.requestVoid(.deleteTicker(ticker), body: nil) positions.removeAll { $0.ticker == ticker } } catch { self.error = error.localizedDescription diff --git a/ios/OptionsSidekick/OptionsSidekick/OptionsSidekick/OptionsSidekick/ViewModels/PositionsViewModel.swift b/ios/OptionsSidekick/OptionsSidekick/OptionsSidekick/OptionsSidekick/ViewModels/PositionsViewModel.swift index d8c1106..cf29434 100644 --- a/ios/OptionsSidekick/OptionsSidekick/OptionsSidekick/OptionsSidekick/ViewModels/PositionsViewModel.swift +++ b/ios/OptionsSidekick/OptionsSidekick/OptionsSidekick/OptionsSidekick/ViewModels/PositionsViewModel.swift @@ -1,4 +1,5 @@ import Foundation +import Combine @MainActor final class PositionsViewModel: ObservableObject { @@ -15,7 +16,7 @@ final class PositionsViewModel: ObservableObject { do { positions = try await APIClient.shared.request( .getPositions(status: nil), - body: Optional.none + body: nil ) } catch { self.error = error.localizedDescription diff --git a/ios/OptionsSidekick/OptionsSidekick/OptionsSidekick/OptionsSidekick/ViewModels/RecommendationsViewModel.swift b/ios/OptionsSidekick/OptionsSidekick/OptionsSidekick/OptionsSidekick/ViewModels/RecommendationsViewModel.swift index fb26253..b533fe0 100644 --- a/ios/OptionsSidekick/OptionsSidekick/OptionsSidekick/OptionsSidekick/ViewModels/RecommendationsViewModel.swift +++ b/ios/OptionsSidekick/OptionsSidekick/OptionsSidekick/OptionsSidekick/ViewModels/RecommendationsViewModel.swift @@ -1,4 +1,5 @@ import Foundation +import Combine @MainActor final class RecommendationsViewModel: ObservableObject { @@ -21,7 +22,7 @@ final class RecommendationsViewModel: ObservableObject { do { recommendations = try await APIClient.shared.request( .getRecommendations(timeHorizon: nil), - body: Optional.none + body: nil ) } catch { self.error = error.localizedDescription @@ -35,7 +36,7 @@ final class RecommendationsViewModel: ObservableObject { do { recommendations = try await APIClient.shared.request( .refreshRecommendations, - body: Optional.none + body: nil ) } catch { self.error = error.localizedDescription @@ -47,7 +48,7 @@ final class RecommendationsViewModel: ObservableObject { do { return try await APIClient.shared.request( .getRecommendation(ticker: ticker, strategy: selectedStrategy, timeHorizon: selectedHorizon), - body: Optional.none + body: nil ) } catch { self.error = error.localizedDescription diff --git a/ios/OptionsSidekick/OptionsSidekick/OptionsSidekick/OptionsSidekick/Views/Positions/OpenPositionsView.swift b/ios/OptionsSidekick/OptionsSidekick/OptionsSidekick/OptionsSidekick/Views/Positions/OpenPositionsView.swift index c32c121..095fa5a 100644 --- a/ios/OptionsSidekick/OptionsSidekick/OptionsSidekick/OptionsSidekick/Views/Positions/OpenPositionsView.swift +++ b/ios/OptionsSidekick/OptionsSidekick/OptionsSidekick/OptionsSidekick/Views/Positions/OpenPositionsView.swift @@ -22,7 +22,7 @@ struct OpenPositionsView: View { if !vm.openPositions.isEmpty { Section("Open") { ForEach(vm.openPositions) { position in - NavigationLink(destination: PositionDetailView(position: position, vm: vm)) { + NavigationLink(destination: PositionDetailView(position: position, parentVM: vm)) { LoggedPositionRow(position: position) } } diff --git a/ios/OptionsSidekick/OptionsSidekick/OptionsSidekick/OptionsSidekick/Views/Positions/PositionDetailView.swift b/ios/OptionsSidekick/OptionsSidekick/OptionsSidekick/OptionsSidekick/Views/Positions/PositionDetailView.swift index 58e7de5..9f71824 100644 --- a/ios/OptionsSidekick/OptionsSidekick/OptionsSidekick/OptionsSidekick/Views/Positions/PositionDetailView.swift +++ b/ios/OptionsSidekick/OptionsSidekick/OptionsSidekick/OptionsSidekick/Views/Positions/PositionDetailView.swift @@ -2,17 +2,17 @@ import SwiftUI struct PositionDetailView: View { let position: OptionPosition - @ObservedObject var vm: PositionsViewModel + /// Pass in from parent (OpenPositionsView) so close/roll updates the parent list. + /// If nil, a local vm is used (e.g. when navigating from DashboardView). + var parentVM: PositionsViewModel? = nil + @StateObject private var localVM = PositionsViewModel() @State private var signals: SignalSnapshot? = nil @State private var isLoadingSignals = false @State private var showCloseConfirm = false @State private var showRollConfirm = false @Environment(\.dismiss) var dismiss - init(position: OptionPosition, vm: PositionsViewModel = PositionsViewModel()) { - self.position = position - self.vm = vm - } + private var vm: PositionsViewModel { parentVM ?? localVM } var body: some View { ScrollView { @@ -146,7 +146,7 @@ struct PositionDetailView: View { do { signals = try await APIClient.shared.request( .getSignals(position.ticker), - body: Optional.none + body: nil ) } catch { // Non-critical — just don't show signals diff --git a/ios/OptionsSidekick/OptionsSidekick/ViewModels/AlertsViewModel.swift b/ios/OptionsSidekick/OptionsSidekick/ViewModels/AlertsViewModel.swift index c8178a7..0a2b8b3 100644 --- a/ios/OptionsSidekick/OptionsSidekick/ViewModels/AlertsViewModel.swift +++ b/ios/OptionsSidekick/OptionsSidekick/ViewModels/AlertsViewModel.swift @@ -15,7 +15,7 @@ final class AlertsViewModel: ObservableObject { do { alerts = try await APIClient.shared.request( .getAlerts(unreadOnly: unreadOnly), - body: Optional.none + body: nil ) LocalStore.shared.unreadAlertCount = unreadCount } catch { @@ -28,7 +28,7 @@ final class AlertsViewModel: ObservableObject { do { let updated: AppAlert = try await APIClient.shared.request( .acknowledgeAlert(alert.id), - body: Optional.none + body: nil ) if let idx = alerts.firstIndex(where: { $0.id == alert.id }) { alerts[idx] = updated diff --git a/ios/OptionsSidekick/OptionsSidekick/ViewModels/DashboardViewModel.swift b/ios/OptionsSidekick/OptionsSidekick/ViewModels/DashboardViewModel.swift index 20f6762..e16d805 100644 --- a/ios/OptionsSidekick/OptionsSidekick/ViewModels/DashboardViewModel.swift +++ b/ios/OptionsSidekick/OptionsSidekick/ViewModels/DashboardViewModel.swift @@ -45,18 +45,18 @@ final class DashboardViewModel: ObservableObject { // ─── Private loaders ────────────────────────────────────────────────────── private func loadStocks() async -> [PortfolioPosition] { - (try? await APIClient.shared.request(.getPortfolio, body: Optional.none)) ?? [] + (try? await APIClient.shared.request(.getPortfolio, body: nil)) ?? [] } private func loadOptions() async -> [OptionPosition] { - (try? await APIClient.shared.request(.getPositions(status: "open"), body: Optional.none)) ?? [] + (try? await APIClient.shared.request(.getPositions(status: "open"), body: nil)) ?? [] } private func loadRecommendations() async -> [Recommendation] { - (try? await APIClient.shared.request(.getRecommendations(timeHorizon: nil), body: Optional.none)) ?? [] + (try? await APIClient.shared.request(.getRecommendations(timeHorizon: nil), body: nil)) ?? [] } private func loadAlerts() async -> [AppAlert] { - (try? await APIClient.shared.request(.getAlerts(unreadOnly: true), body: Optional.none)) ?? [] + (try? await APIClient.shared.request(.getAlerts(unreadOnly: true), body: nil)) ?? [] } } diff --git a/ios/OptionsSidekick/OptionsSidekick/ViewModels/PortfolioViewModel.swift b/ios/OptionsSidekick/OptionsSidekick/ViewModels/PortfolioViewModel.swift index cd0893f..3f0dc54 100644 --- a/ios/OptionsSidekick/OptionsSidekick/ViewModels/PortfolioViewModel.swift +++ b/ios/OptionsSidekick/OptionsSidekick/ViewModels/PortfolioViewModel.swift @@ -11,7 +11,7 @@ final class PortfolioViewModel: ObservableObject { isLoading = true error = nil do { - positions = try await APIClient.shared.request(.getPortfolio, body: Optional.none) + positions = try await APIClient.shared.request(.getPortfolio, body: nil) } catch { self.error = error.localizedDescription } @@ -58,7 +58,7 @@ final class PortfolioViewModel: ObservableObject { isLoading = true error = nil do { - try await APIClient.shared.requestVoid(.deleteTicker(ticker), body: Optional.none) + try await APIClient.shared.requestVoid(.deleteTicker(ticker), body: nil) positions.removeAll { $0.ticker == ticker } } catch { self.error = error.localizedDescription diff --git a/ios/OptionsSidekick/OptionsSidekick/ViewModels/PositionsViewModel.swift b/ios/OptionsSidekick/OptionsSidekick/ViewModels/PositionsViewModel.swift index 32e97bf..cf29434 100644 --- a/ios/OptionsSidekick/OptionsSidekick/ViewModels/PositionsViewModel.swift +++ b/ios/OptionsSidekick/OptionsSidekick/ViewModels/PositionsViewModel.swift @@ -16,7 +16,7 @@ final class PositionsViewModel: ObservableObject { do { positions = try await APIClient.shared.request( .getPositions(status: nil), - body: Optional.none + body: nil ) } catch { self.error = error.localizedDescription diff --git a/ios/OptionsSidekick/OptionsSidekick/ViewModels/RecommendationsViewModel.swift b/ios/OptionsSidekick/OptionsSidekick/ViewModels/RecommendationsViewModel.swift index c1ae4b3..b533fe0 100644 --- a/ios/OptionsSidekick/OptionsSidekick/ViewModels/RecommendationsViewModel.swift +++ b/ios/OptionsSidekick/OptionsSidekick/ViewModels/RecommendationsViewModel.swift @@ -22,7 +22,7 @@ final class RecommendationsViewModel: ObservableObject { do { recommendations = try await APIClient.shared.request( .getRecommendations(timeHorizon: nil), - body: Optional.none + body: nil ) } catch { self.error = error.localizedDescription @@ -36,7 +36,7 @@ final class RecommendationsViewModel: ObservableObject { do { recommendations = try await APIClient.shared.request( .refreshRecommendations, - body: Optional.none + body: nil ) } catch { self.error = error.localizedDescription @@ -48,7 +48,7 @@ final class RecommendationsViewModel: ObservableObject { do { return try await APIClient.shared.request( .getRecommendation(ticker: ticker, strategy: selectedStrategy, timeHorizon: selectedHorizon), - body: Optional.none + body: nil ) } catch { self.error = error.localizedDescription diff --git a/ios/OptionsSidekick/OptionsSidekick/Views/Positions/PositionDetailView.swift b/ios/OptionsSidekick/OptionsSidekick/Views/Positions/PositionDetailView.swift index 6e2de66..9f71824 100644 --- a/ios/OptionsSidekick/OptionsSidekick/Views/Positions/PositionDetailView.swift +++ b/ios/OptionsSidekick/OptionsSidekick/Views/Positions/PositionDetailView.swift @@ -146,7 +146,7 @@ struct PositionDetailView: View { do { signals = try await APIClient.shared.request( .getSignals(position.ticker), - body: Optional.none + body: nil ) } catch { // Non-critical — just don't show signals