- Models: TrainingPlan, Race, FamilyPlan, BeginnerMode, CommunityEvent - Services: 5 service layers with protocol-based architecture - ViewModels: 5 view models with @MainActor ObservableObject pattern - Views: 10 SwiftUI views for all Phase 3 features - Updated README with full Phase 3 documentation Co-Authored-By: Paperclip <noreply@paperclip.ing>
244 lines
5.9 KiB
Swift
244 lines
5.9 KiB
Swift
import Foundation
|
|
import SwiftUI
|
|
|
|
// MARK: - Community Event
|
|
|
|
struct CommunityEvent: Identifiable, Equatable, Codable {
|
|
let id: String
|
|
let title: String
|
|
let description: String
|
|
let eventType: EventType
|
|
let location: String
|
|
let latitude: Double
|
|
let longitude: Double
|
|
let startDate: Date
|
|
let endDate: Date
|
|
let distanceKm: Double?
|
|
let organizerId: String
|
|
let organizerName: String
|
|
let maxParticipants: Int?
|
|
let participantCount: Int
|
|
let imageUrl: String?
|
|
let difficulty: Difficulty?
|
|
var rsvpStatus: RSVPStatus
|
|
let createdAt: Date
|
|
|
|
enum CodingKeys: String, CodingKey {
|
|
case id, title, description, eventType, location, latitude, longitude, startDate, endDate, distanceKm, organizerId, organizerName, maxParticipants, participantCount, imageUrl, difficulty, rsvpStatus, createdAt
|
|
}
|
|
|
|
init(
|
|
id: String,
|
|
title: String,
|
|
description: String,
|
|
eventType: EventType,
|
|
location: String,
|
|
latitude: Double,
|
|
longitude: Double,
|
|
startDate: Date,
|
|
endDate: Date,
|
|
distanceKm: Double?,
|
|
organizerId: String,
|
|
organizerName: String,
|
|
maxParticipants: Int?,
|
|
participantCount: Int,
|
|
imageUrl: String?,
|
|
difficulty: Difficulty?,
|
|
rsvpStatus: RSVPStatus,
|
|
createdAt: Date
|
|
) {
|
|
self.id = id
|
|
self.title = title
|
|
self.description = description
|
|
self.eventType = eventType
|
|
self.location = location
|
|
self.latitude = latitude
|
|
self.longitude = longitude
|
|
self.startDate = startDate
|
|
self.endDate = endDate
|
|
self.distanceKm = distanceKm
|
|
self.organizerId = organizerId
|
|
self.organizerName = organizerName
|
|
self.maxParticipants = maxParticipants
|
|
self.participantCount = participantCount
|
|
self.imageUrl = imageUrl
|
|
self.difficulty = difficulty
|
|
self.rsvpStatus = rsvpStatus
|
|
self.createdAt = createdAt
|
|
}
|
|
|
|
static func == (lhs: CommunityEvent, rhs: CommunityEvent) -> Bool {
|
|
lhs.id == rhs.id && lhs.rsvpStatus == rhs.rsvpStatus
|
|
}
|
|
|
|
var isUpcoming: Bool {
|
|
startDate > Date()
|
|
}
|
|
|
|
var isOngoing: Bool {
|
|
Date() >= startDate && Date() <= endDate
|
|
}
|
|
|
|
var isPast: Bool {
|
|
endDate < Date()
|
|
}
|
|
|
|
var availableSpots: Int? {
|
|
guard let max = maxParticipants else { return nil }
|
|
return max - participantCount
|
|
}
|
|
}
|
|
|
|
// MARK: - Event Type
|
|
|
|
enum EventType: String, CaseIterable, Codable {
|
|
case groupRun
|
|
case race
|
|
case workshop
|
|
case socialGather
|
|
case charityEvent
|
|
case trainingCamp
|
|
|
|
var displayName: String {
|
|
switch self {
|
|
case .groupRun: return "Group Run"
|
|
case .race: return "Race"
|
|
case .workshop: return "Workshop"
|
|
case .socialGather: return "Social"
|
|
case .charityEvent: return "Charity"
|
|
case .trainingCamp: return "Training Camp"
|
|
}
|
|
}
|
|
|
|
var icon: String {
|
|
switch self {
|
|
case .groupRun: return "person.3.fill"
|
|
case .race: return "flag.fill"
|
|
case .workshop: return "lightbulb.fill"
|
|
case .socialGather: return "cup.and.saucer.fill"
|
|
case .charityEvent: return "heart.fill"
|
|
case .trainingCamp: return "figure.run"
|
|
}
|
|
}
|
|
|
|
var color: Color {
|
|
switch self {
|
|
case .groupRun: return .blue
|
|
case .race: return .orange
|
|
case .workshop: return .purple
|
|
case .socialGather: return .green
|
|
case .charityEvent: return .red
|
|
case .trainingCamp: return .indigo
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - RSVP Status
|
|
|
|
enum RSVPStatus: String, CaseIterable, Codable {
|
|
case going
|
|
case maybe
|
|
case notGoing
|
|
case pending
|
|
}
|
|
|
|
// MARK: - Event Participant
|
|
|
|
struct EventParticipant: Identifiable, Codable {
|
|
let id: String
|
|
let name: String
|
|
let avatarUrl: String?
|
|
let rsvpStatus: RSVPStatus
|
|
let joinedAt: Date
|
|
}
|
|
|
|
// MARK: - Create Event Request
|
|
|
|
struct CreateEventRequest: Encodable {
|
|
let title: String
|
|
let description: String
|
|
let eventType: EventType
|
|
let location: String
|
|
let latitude: Double
|
|
let longitude: Double
|
|
let startDate: Date
|
|
let endDate: Date
|
|
let distanceKm: Double?
|
|
let maxParticipants: Int?
|
|
let difficulty: Difficulty?
|
|
}
|
|
|
|
// MARK: - Update Event Request
|
|
|
|
struct UpdateEventRequest: Encodable {
|
|
var title: String?
|
|
var description: String?
|
|
var eventType: EventType?
|
|
var location: String?
|
|
var latitude: Double?
|
|
var longitude: Double?
|
|
var startDate: Date?
|
|
var endDate: Date?
|
|
var distanceKm: Double?
|
|
var maxParticipants: Int?
|
|
}
|
|
|
|
// MARK: - Event Filter
|
|
|
|
struct EventFilter: Encodable {
|
|
var eventType: EventType?
|
|
var startDate: Date?
|
|
var endDate: Date?
|
|
var location: String?
|
|
var radiusKm: Double?
|
|
var rsvpStatus: RSVPStatus?
|
|
var limit: Int
|
|
var offset: Int
|
|
|
|
init(
|
|
eventType: EventType? = nil,
|
|
startDate: Date? = nil,
|
|
endDate: Date? = nil,
|
|
location: String? = nil,
|
|
radiusKm: Double? = nil,
|
|
rsvpStatus: RSVPStatus? = nil,
|
|
limit: Int = 20,
|
|
offset: Int = 0
|
|
) {
|
|
self.eventType = eventType
|
|
self.startDate = startDate
|
|
self.endDate = endDate
|
|
self.location = location
|
|
self.radiusKm = radiusKm
|
|
self.rsvpStatus = rsvpStatus
|
|
self.limit = limit
|
|
self.offset = offset
|
|
}
|
|
}
|
|
|
|
// MARK: - API Response Types
|
|
|
|
struct EventListResponse: Decodable {
|
|
let events: [CommunityEvent]
|
|
let hasMore: Bool
|
|
}
|
|
|
|
struct EventDetailResponse: Decodable {
|
|
let event: CommunityEvent
|
|
let participants: [EventParticipant]
|
|
}
|
|
|
|
struct CreateEventResponse: Decodable {
|
|
let event: CommunityEvent
|
|
}
|
|
|
|
struct UpdateEventResponse: Decodable {
|
|
let event: CommunityEvent
|
|
}
|
|
|
|
struct RSVPResponse: Decodable {
|
|
let success: Bool
|
|
let eventId: String
|
|
let status: RSVPStatus
|
|
}
|