import SwiftUI struct EventsListView: View { @Environment(ChurchDataService.self) private var dataService @State private var searchText = "" @State private var selectedCategory = "All" @State private var showingFilters = false private let categories = ["All", "Service", "Ministry", "Social", "Other"] private var grayBackgroundColor: Color { #if os(iOS) Color(.systemGray6) #else Color.gray.opacity(0.2) #endif } private var systemBackgroundColor: Color { #if os(iOS) Color(.systemBackground) #else Color.black #endif } var filteredEvents: [ChurchEvent] { var events = dataService.events // Filter by search text events = SearchUtils.searchEvents(events, searchText: searchText) // Filter by category if selectedCategory != "All" { events = events.filter { $0.category.lowercased() == selectedCategory.lowercased() } } return events } var body: some View { VStack(spacing: 0) { // Search and Filter Bar VStack(spacing: 12) { // Search Bar HStack { Image(systemName: "magnifyingglass") .foregroundColor(.secondary) TextField("Search events...", text: $searchText) .textFieldStyle(PlainTextFieldStyle()) if !searchText.isEmpty { Button(action: { searchText = "" }) { Image(systemName: "xmark.circle.fill") .foregroundColor(.secondary) } } } .padding(.horizontal, 16) .padding(.vertical, 10) .background(grayBackgroundColor) .cornerRadius(10) // Category Filter ScrollView(.horizontal, showsIndicators: false) { HStack(spacing: 8) { ForEach(categories, id: \.self) { category in Button(action: { selectedCategory = category }) { Text(category) .font(.subheadline) .fontWeight(.medium) .padding(.horizontal, 16) .padding(.vertical, 8) .background(selectedCategory == category ? Color.blue : grayBackgroundColor) .foregroundColor(selectedCategory == category ? .white : .primary) .cornerRadius(20) } } } .padding(.horizontal, 20) } } .padding(.horizontal, 20) .padding(.top, 8) .padding(.bottom, 16) .background(systemBackgroundColor) // Events List if dataService.isLoading { Spacer() ProgressView("Loading events...") Spacer() } else if filteredEvents.isEmpty { Spacer() VStack(spacing: 16) { Image(systemName: "calendar.badge.exclamationmark") .font(.system(size: 50)) .foregroundColor(.secondary) Text("No events found") .font(.headline) .foregroundColor(.secondary) if !searchText.isEmpty || selectedCategory != "All" { Text("Try adjusting your search or filters") .font(.subheadline) .foregroundColor(.secondary) .multilineTextAlignment(.center) } } .padding() Spacer() } else { ScrollView { LazyVStack(spacing: 16) { ForEach(filteredEvents) { event in NavigationLink(destination: EventDetailViewWrapper(event: event) .environment(dataService)) { EventListCard(event: event) } .buttonStyle(.plain) } } .padding(.horizontal, 20) .padding(.bottom, 100) } .refreshable { await dataService.loadAllEvents() } } } .navigationTitle("Events") #if os(iOS) .navigationBarTitleDisplayMode(.large) #endif .task { await dataService.loadAllEvents() } } } // MARK: - Event List Card Component struct EventListCard: View { let event: ChurchEvent @Environment(\.horizontalSizeClass) private var horizontalSizeClass private var systemBackgroundColor: Color { #if os(iOS) Color(.systemBackground) #else Color.black #endif } var body: some View { VStack(alignment: .leading, spacing: 0) { // Hero Image or Gradient Group { if let imageUrl = event.image, !imageUrl.isEmpty { CachedAsyncImage(url: URL(string: imageUrl)) { image in image .resizable() .aspectRatio(contentMode: .fill) } placeholder: { gradientBackground } } else { gradientBackground } } .frame(height: horizontalSizeClass == .regular ? 180 : 140) .clipped() .overlay( // Category Badge VStack { HStack { if !event.category.isEmpty { Text(event.category.uppercased()) .font(.caption2) .fontWeight(.bold) .foregroundColor(.white) .padding(.horizontal, 8) .padding(.vertical, 4) .background(.ultraThinMaterial) .cornerRadius(6) } Spacer() if event.isFeatured { Image(systemName: "star.fill") .foregroundColor(.yellow) .font(.caption) } } Spacer() } .padding(12) ) // Event Details VStack(alignment: .leading, spacing: 8) { Text(event.title) .font(.headline) .fontWeight(.semibold) .lineLimit(2) .multilineTextAlignment(.leading) // Date and Time HStack(spacing: 4) { Image(systemName: "calendar") .foregroundColor(.blue) .font(.caption) if event.isMultiDay { // Multi-day: show formatted time which contains "Aug 30 - Aug 31 at 6 PM" Text(event.formattedTime) .font(.subheadline) .foregroundColor(.secondary) } else { // Single day: show date and time separately Text(event.formattedDateRange) .font(.subheadline) .foregroundColor(.secondary) Text("•") .foregroundColor(.secondary) .font(.caption) Text(event.formattedTime) .font(.subheadline) .foregroundColor(.secondary) } } // Location if !event.location.isEmpty { HStack(spacing: 4) { Image(systemName: "location") .foregroundColor(.blue) .font(.caption) Text(event.location) .font(.subheadline) .foregroundColor(.secondary) .lineLimit(1) } } // Recurring indicator if let recurringType = event.recurringType, !recurringType.isEmpty { HStack(spacing: 4) { Image(systemName: "repeat") .foregroundColor(.orange) .font(.caption) Text(recurringType.capitalized) .font(.caption) .foregroundColor(.orange) .fontWeight(.medium) } } } .padding(16) } .background(systemBackgroundColor) .cornerRadius(12) .shadow(color: .black.opacity(0.1), radius: 4, x: 0, y: 2) .contentShape(RoundedRectangle(cornerRadius: 12)) } private var gradientBackground: some View { LinearGradient( colors: [ Color.blue.opacity(0.7), Color.purple.opacity(0.6) ], startPoint: .topLeading, endPoint: .bottomTrailing ) } } #Preview { NavigationStack { EventsListView() .environment(ChurchDataService.shared) } }