feat(polls): implement group trip polling MVP
Add complete group trip polling feature allowing users to share trips
with friends for voting using Borda count scoring.
New components:
- TripPoll and PollVote domain models with share codes and rankings
- LocalTripPoll and LocalPollVote SwiftData models for persistence
- CKTripPoll and CKPollVote CloudKit record wrappers
- PollService actor for CloudKit CRUD operations and subscriptions
- PollCreation/Detail/Voting views and view models
- Deep link handling for sportstime://poll/{code} URLs
- Debug Pro status override toggle in Settings
Integration:
- HomeView shows polls section in My Trips
- SportsTimeApp registers SwiftData models and handles deep links
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
//
|
||||
// PollCreationViewModel.swift
|
||||
// SportsTime
|
||||
//
|
||||
// ViewModel for creating trip polls
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
@Observable
|
||||
@MainActor
|
||||
final class PollCreationViewModel {
|
||||
var title: String = ""
|
||||
var selectedTripIds: Set<UUID> = []
|
||||
var isLoading = false
|
||||
var error: PollError?
|
||||
var createdPoll: TripPoll?
|
||||
|
||||
private let pollService = PollService.shared
|
||||
|
||||
var canCreate: Bool {
|
||||
!title.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
|
||||
&& selectedTripIds.count >= 2
|
||||
}
|
||||
|
||||
var validationMessage: String? {
|
||||
if title.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
||||
return "Enter a title for your poll"
|
||||
}
|
||||
if selectedTripIds.count < 2 {
|
||||
return "Select at least 2 trips to create a poll"
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func createPoll(trips: [Trip]) async {
|
||||
guard canCreate else { return }
|
||||
|
||||
isLoading = true
|
||||
error = nil
|
||||
|
||||
do {
|
||||
let userId = try await pollService.getCurrentUserRecordID()
|
||||
let selectedTrips = trips.filter { selectedTripIds.contains($0.id) }
|
||||
|
||||
let poll = TripPoll(
|
||||
title: title.trimmingCharacters(in: .whitespacesAndNewlines),
|
||||
ownerId: userId,
|
||||
tripSnapshots: selectedTrips
|
||||
)
|
||||
|
||||
createdPoll = try await pollService.createPoll(poll)
|
||||
} catch let pollError as PollError {
|
||||
error = pollError
|
||||
} catch {
|
||||
self.error = .unknown(error)
|
||||
}
|
||||
|
||||
isLoading = false
|
||||
}
|
||||
|
||||
func toggleTrip(_ tripId: UUID) {
|
||||
if selectedTripIds.contains(tripId) {
|
||||
selectedTripIds.remove(tripId)
|
||||
} else {
|
||||
selectedTripIds.insert(tripId)
|
||||
}
|
||||
}
|
||||
|
||||
func reset() {
|
||||
title = ""
|
||||
selectedTripIds = []
|
||||
error = nil
|
||||
createdPoll = nil
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user