Initial implementation of Fantasy Hockey watchOS app
Implemented complete TCA architecture for iOS and watchOS targets: - Authentication flow (Sign in with Apple + Yahoo OAuth) - OAuth token management with iCloud Key-Value Storage - Yahoo Fantasy Sports API client with async/await - Watch Connectivity for iPhone ↔ Watch data sync - Complete UI for both iPhone and Watch platforms Core features: - Matchup score display - Category breakdown with win/loss/tie indicators - Roster status tracking - Manual refresh functionality - Persistent data caching on Watch Technical stack: - The Composable Architecture for state management - Swift Concurrency (async/await, actors) - WatchConnectivity framework - Sign in with Apple - OAuth 2.0 authentication flow 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
//
|
||||
// WatchConnectivityClient.swift
|
||||
// FantasyWatch Watch App
|
||||
//
|
||||
// Created by Claude Code
|
||||
//
|
||||
|
||||
import ComposableArchitecture
|
||||
import Foundation
|
||||
import WatchConnectivity
|
||||
|
||||
@DependencyClient
|
||||
struct WatchConnectivityClient {
|
||||
var matchupUpdates: @Sendable () -> AsyncStream<(Matchup, RosterStatus, Date)>
|
||||
var requestRefresh: @Sendable () async throws -> Void
|
||||
}
|
||||
|
||||
extension WatchConnectivityClient: DependencyKey {
|
||||
static let liveValue: WatchConnectivityClient = {
|
||||
let manager = WatchConnectivityManager()
|
||||
|
||||
return Self(
|
||||
matchupUpdates: {
|
||||
manager.matchupUpdatesStream()
|
||||
},
|
||||
requestRefresh: {
|
||||
try await manager.requestRefresh()
|
||||
}
|
||||
)
|
||||
}()
|
||||
|
||||
static let testValue = Self(
|
||||
matchupUpdates: {
|
||||
AsyncStream { continuation in
|
||||
continuation.yield((
|
||||
Matchup(
|
||||
week: 1,
|
||||
status: "midevent",
|
||||
userTeam: TeamScore(teamKey: "test.l.123.t.1", teamName: "Test Team", wins: 5, losses: 3, ties: 1),
|
||||
opponentTeam: TeamScore(teamKey: "test.l.123.t.2", teamName: "Opponent", wins: 4, losses: 4, ties: 1),
|
||||
categories: []
|
||||
),
|
||||
RosterStatus(activeCount: 10, benchedCount: 5, injuredReserve: 2),
|
||||
Date()
|
||||
))
|
||||
}
|
||||
},
|
||||
requestRefresh: {}
|
||||
)
|
||||
}
|
||||
|
||||
extension DependencyValues {
|
||||
var watchConnectivityClient: WatchConnectivityClient {
|
||||
get { self[WatchConnectivityClient.self] }
|
||||
set { self[WatchConnectivityClient.self] = newValue }
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Watch Connectivity Manager
|
||||
|
||||
final class WatchConnectivityManager: NSObject, WCSessionDelegate {
|
||||
private var session: WCSession?
|
||||
private var updatesContinuation: AsyncStream<(Matchup, RosterStatus, Date)>.Continuation?
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
|
||||
guard WCSession.isSupported() else { return }
|
||||
|
||||
session = WCSession.default
|
||||
session?.delegate = self
|
||||
session?.activate()
|
||||
}
|
||||
|
||||
func matchupUpdatesStream() -> AsyncStream<(Matchup, RosterStatus, Date)> {
|
||||
AsyncStream { continuation in
|
||||
self.updatesContinuation = continuation
|
||||
|
||||
if let context = session?.receivedApplicationContext,
|
||||
let data = context["payload"] as? Data,
|
||||
let payload = try? JSONDecoder().decode(WatchPayload.self, from: data) {
|
||||
continuation.yield((payload.matchup, payload.roster, payload.timestamp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func requestRefresh() async throws {
|
||||
guard let session = session, session.activationState == .activated else {
|
||||
throw NetworkError.networkFailure(
|
||||
NSError(domain: "WatchConnectivity", code: -1, userInfo: [NSLocalizedDescriptionKey: "Watch session not active"])
|
||||
)
|
||||
}
|
||||
|
||||
let message = ["type": WatchMessage.refreshRequest.rawValue]
|
||||
session.sendMessage(message, replyHandler: nil) { error in
|
||||
print("Failed to send refresh request: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - WCSessionDelegate
|
||||
|
||||
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
|
||||
if let error = error {
|
||||
print("Watch session activation failed: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
|
||||
func session(_ session: WCSession, didReceiveApplicationContext applicationContext: [String : Any]) {
|
||||
guard let data = applicationContext["payload"] as? Data,
|
||||
let payload = try? JSONDecoder().decode(WatchPayload.self, from: data) else {
|
||||
return
|
||||
}
|
||||
|
||||
updatesContinuation?.yield((payload.matchup, payload.roster, payload.timestamp))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user