import SwiftUI struct FeedItemCard: View { let item: FeedItem @Environment(\.horizontalSizeClass) private var horizontalSizeClass var body: some View { Group { switch item.type { case .sermon(let sermon): SermonCard(sermon: sermon, style: .feed) case .event(let event): EventFeedCard(event: event) case .bulletin(let bulletin): BulletinFeedCard(bulletin: bulletin) case .verse(let verse): VerseFeedCard(verse: verse) } } .containerRelativeFrame(.horizontal) { width, _ in horizontalSizeClass == .regular ? min(width * 0.9, 600) : width * 0.95 } } } // MARK: - Event Feed Card struct EventFeedCard: View { let event: ChurchEvent @Environment(\.horizontalSizeClass) private var horizontalSizeClass var body: some View { NavigationLink { EventDetailViewWrapper(event: event) } label: { HStack(spacing: 16) { // Date indicator VStack(spacing: 4) { Text(event.dayOfMonth) .font(.system(size: 18, weight: .bold)) .foregroundColor(Color(hex: "fb8b23")) Text(event.monthAbbreviation) .font(.system(size: 10, weight: .semibold)) .foregroundColor(.secondary) .textCase(.uppercase) } .frame(width: 50) .padding(.vertical, 8) .background(Color(hex: "fb8b23").opacity(Double(0.1)), in: RoundedRectangle(cornerRadius: 8)) // Event content VStack(alignment: .leading, spacing: 8) { Text(event.title) .font(.system(size: 16, weight: .semibold)) .lineLimit(2) if !event.description.isEmpty { Text(event.description.stripHtml()) .font(.system(size: 14)) .foregroundColor(.secondary) .lineLimit(2) } HStack(spacing: 8) { Image(systemName: "clock.fill") .font(.caption) .foregroundColor(Color(hex: "fb8b23")) Text(event.timeString) .font(.system(size: 13, weight: .medium)) .foregroundColor(.secondary) if !event.location.isEmpty { Image(systemName: "location.fill") .font(.caption) .foregroundColor(Color(hex: "fb8b23")) Text(event.location) .font(.system(size: 13, weight: .medium)) .foregroundColor(.secondary) .lineLimit(1) } } } Spacer() // Event image (if available) if let imageUrl = event.thumbnail ?? event.image { AsyncImage(url: URL(string: imageUrl)) { image in image .resizable() .aspectRatio(contentMode: .fill) } placeholder: { RoundedRectangle(cornerRadius: 8) .fill(.secondary.opacity(0.3)) } .frame(width: 60, height: 60) .clipShape(RoundedRectangle(cornerRadius: 8)) } } .padding(16) .background(.regularMaterial, in: RoundedRectangle(cornerRadius: 16)) .shadow(color: .black.opacity(0.1), radius: 8, x: 0, y: 4) } .buttonStyle(.plain) } private func dayFromDate(_ dateString: String) -> String { let formatter = ISO8601DateFormatter() if let date = formatter.date(from: dateString) { let dayFormatter = DateFormatter() dayFormatter.dateFormat = "d" return dayFormatter.string(from: date) } return "?" } private func monthFromDate(_ dateString: String) -> String { let formatter = ISO8601DateFormatter() if let date = formatter.date(from: dateString) { let monthFormatter = DateFormatter() monthFormatter.dateFormat = "MMM" return monthFormatter.string(from: date).uppercased() } return "???" } } // MARK: - Bulletin Feed Card struct BulletinFeedCard: View { let bulletin: ChurchBulletin var body: some View { NavigationLink { BulletinDetailView(bulletin: bulletin) } label: { HStack(spacing: 16) { // PDF icon Image(systemName: "doc.text.fill") .font(.system(size: 32)) .foregroundStyle(Color(hex: "fb8b23")) .frame(width: 50) // Bulletin content VStack(alignment: .leading, spacing: 8) { Text(bulletin.title) .font(.headline) .fontWeight(.semibold) .lineLimit(2) .multilineTextAlignment(.leading) Text(bulletin.formattedDate) .font(.subheadline) .foregroundStyle(.secondary) Label("Church Bulletin", systemImage: "newspaper.fill") .font(.caption) .padding(.horizontal, 8) .padding(.vertical, 4) .background(.green.opacity(0.1), in: Capsule()) .foregroundStyle(.green) } Spacer() Image(systemName: "chevron.right") .font(.caption) .foregroundStyle(.secondary) } .padding(16) .background(.regularMaterial, in: RoundedRectangle(cornerRadius: 16)) .shadow(color: .black.opacity(0.1), radius: 8, x: 0, y: 4) } .buttonStyle(.plain) } } // MARK: - Bible Verse Feed Card struct VerseFeedCard: View { let verse: BibleVerse var body: some View { VStack(alignment: .leading, spacing: 16) { HStack { Image(systemName: "book.fill") .foregroundStyle(Color(hex: "fb8b23")) Text("Verse of the Day") .font(.caption) .fontWeight(.medium) .foregroundStyle(.secondary) Spacer() } Text("\"\(verse.text)\"") .font(.body) .fontWeight(.medium) .italic() .lineLimit(4) .multilineTextAlignment(.leading) HStack { Text(verse.reference) .font(.caption) .fontWeight(.semibold) .foregroundStyle(Color(hex: "fb8b23")) if let version = verse.version { Text(version) .font(.caption) .foregroundStyle(.secondary) } Spacer() Button { // Share verse } label: { Image(systemName: "square.and.arrow.up") .font(.caption) .foregroundStyle(.secondary) } } } .padding(16) .background( LinearGradient( colors: [Color(hex: "fb8b23").opacity(0.1), Color(hex: "fb8b23").opacity(0.05)], startPoint: .topLeading, endPoint: .bottomTrailing ), in: RoundedRectangle(cornerRadius: 16) ) .overlay( RoundedRectangle(cornerRadius: 16) .stroke(Color(hex: "fb8b23").opacity(0.2), lineWidth: 1) ) } } // MARK: - Color Extension extension Color { init(hex: String) { let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted) var int: UInt64 = 0 Scanner(string: hex).scanHexInt64(&int) let a, r, g, b: UInt64 switch hex.count { case 3: // RGB (12-bit) (a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17) case 6: // RGB (24-bit) (a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF) case 8: // ARGB (32-bit) (a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF) default: (a, r, g, b) = (1, 1, 1, 0) } self.init( .sRGB, red: Double(r) / 255, green: Double(g) / 255, blue: Double(b) / 255, opacity: Double(a) / 255 ) } } #Preview { VStack(spacing: 20) { FeedItemCard(item: FeedItem( type: .sermon(Sermon.sampleSermon()), timestamp: Date() )) FeedItemCard(item: FeedItem( type: .event(ChurchEvent.sampleEvent()), timestamp: Date() )) } .padding() }