feat(ui): replace loading indicators with Apple-style LoadingSpinner
- Add LoadingSpinner component with small/medium/large sizes using system gray color - Add LoadingPlaceholder for skeleton loading states - Add LoadingSheet for full-screen blocking overlays - Replace ThemedSpinner/ThemedSpinnerCompact across all views - Remove deprecated loading components from AnimatedComponents.swift - Delete LoadingTextGenerator.swift - Fix PhotoImportView layout to fill full width Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,99 +0,0 @@
|
||||
//
|
||||
// LoadingTextGenerator.swift
|
||||
// SportsTime
|
||||
//
|
||||
// Generates unique loading messages using Apple Foundation Models.
|
||||
// Falls back to predefined messages on unsupported devices.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
#if canImport(FoundationModels)
|
||||
import FoundationModels
|
||||
#endif
|
||||
|
||||
actor LoadingTextGenerator {
|
||||
|
||||
static let shared = LoadingTextGenerator()
|
||||
|
||||
private static let fallbackMessages = [
|
||||
"Hang tight, we're finding the best routes...",
|
||||
"Scanning stadiums across the country...",
|
||||
"Building your dream road trip...",
|
||||
"Calculating the perfect game day schedule...",
|
||||
"Finding the best matchups for you...",
|
||||
"Mapping out your adventure...",
|
||||
"Checking stadium schedules...",
|
||||
"Putting together some epic trips...",
|
||||
"Hold on, great trips incoming...",
|
||||
"Crunching the numbers on routes...",
|
||||
"Almost there, planning magic happening...",
|
||||
"Finding games you'll love..."
|
||||
]
|
||||
|
||||
private var usedMessages: Set<String> = []
|
||||
|
||||
/// Generates a unique loading message.
|
||||
/// Uses Foundation Models if available, falls back to predefined messages.
|
||||
func generateMessage() async -> String {
|
||||
#if canImport(FoundationModels)
|
||||
// Try Foundation Models first
|
||||
if let message = await generateWithFoundationModels() {
|
||||
return message
|
||||
}
|
||||
#endif
|
||||
|
||||
// Fall back to predefined messages
|
||||
return getNextFallbackMessage()
|
||||
}
|
||||
|
||||
#if canImport(FoundationModels)
|
||||
private func generateWithFoundationModels() async -> String? {
|
||||
// Check availability
|
||||
guard case .available = SystemLanguageModel.default.availability else {
|
||||
return nil
|
||||
}
|
||||
|
||||
do {
|
||||
let session = LanguageModelSession(instructions: """
|
||||
Generate a short, friendly loading message for a sports road trip planning app.
|
||||
The message should be casual, fun, and 8-12 words.
|
||||
Don't use emojis. Don't start with "We're" or "We are".
|
||||
Examples: "Hang tight, finding the best routes for you...",
|
||||
"Calculating the perfect game day adventure...",
|
||||
"Almost there, great trips are brewing..."
|
||||
"""
|
||||
)
|
||||
|
||||
let response = try await session.respond(to: "Generate one loading message")
|
||||
let message = response.content.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
|
||||
// Validate message isn't empty and is reasonable length
|
||||
guard message.count >= 10 && message.count <= 80 else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return message
|
||||
} catch {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
private func getNextFallbackMessage() -> String {
|
||||
// Reset if we've used all messages
|
||||
if usedMessages.count >= Self.fallbackMessages.count {
|
||||
usedMessages.removeAll()
|
||||
}
|
||||
|
||||
// Pick a random unused message
|
||||
let availableMessages = Self.fallbackMessages.filter { !usedMessages.contains($0) }
|
||||
let message = availableMessages.randomElement() ?? Self.fallbackMessages[0]
|
||||
usedMessages.insert(message)
|
||||
return message
|
||||
}
|
||||
|
||||
/// Reset used messages (for testing or new session)
|
||||
func reset() {
|
||||
usedMessages.removeAll()
|
||||
}
|
||||
}
|
||||
@@ -48,7 +48,6 @@ final class SuggestedTripsGenerator {
|
||||
// MARK: - Dependencies
|
||||
|
||||
private let dataProvider = AppDataProvider.shared
|
||||
private let loadingTextGenerator = LoadingTextGenerator.shared
|
||||
|
||||
// MARK: - Grouped Trips
|
||||
|
||||
@@ -78,8 +77,8 @@ final class SuggestedTripsGenerator {
|
||||
error = nil
|
||||
suggestedTrips = []
|
||||
|
||||
// Start with a loading message
|
||||
loadingMessage = await loadingTextGenerator.generateMessage()
|
||||
// Set loading message
|
||||
loadingMessage = "Finding the best routes..."
|
||||
|
||||
// Ensure data is loaded
|
||||
if dataProvider.teams.isEmpty {
|
||||
@@ -141,7 +140,6 @@ final class SuggestedTripsGenerator {
|
||||
}
|
||||
|
||||
func refreshTrips() async {
|
||||
await loadingTextGenerator.reset()
|
||||
await generateTrips()
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user