diff --git a/Jarvis/Views/Home/SignalFeedView.swift b/Jarvis/Views/Home/SignalFeedView.swift index 1561b0a..d456ef2 100644 --- a/Jarvis/Views/Home/SignalFeedView.swift +++ b/Jarvis/Views/Home/SignalFeedView.swift @@ -60,15 +60,15 @@ struct SignalFeedView: View { private var isDigest: Bool { true } /// Front-page digest: Top Stories + up to 3 per category section, deduped. - private var digest: (top: [StorySummary], sections: [(NewsSection, [StorySummary])]) { + private func makeDigest(sections: [NewsSection]) -> (top: [StorySummary], sections: [(NewsSection, [StorySummary])]) { let unread = filteredStories.filter { !readStoryIds.contains($0.id) } let top = Array(unread.prefix(5)) var pool = Array(unread.dropFirst(5)) var out: [(NewsSection, [StorySummary])] = [] - for section in NewsSection.sections { + for section in sections { var picked: [StorySummary] = [], rest: [StorySummary] = [] for s in pool { - if picked.count < 3 && s.inSection(section) { picked.append(s) } + if picked.count < 4 && s.inSection(section) { picked.append(s) } else { rest.append(s) } } pool = rest @@ -136,8 +136,8 @@ struct SignalFeedView: View { .task(id: selectedPill) { await store.setPill(selectedPill) var pages = 0 - if selectedPill == .all { - // Front page: pull enough stories to fill the sections. + // All and Tech both use multi-section layouts that need enough stories. + if selectedPill == .all || selectedPill == .tech { while store.stories.count < 100 && store.hasMore && pages < 5 { await store.loadMore(); pages += 1 } @@ -262,10 +262,12 @@ struct SignalFeedView: View { /// Card layout for every pill. "All" gets full multi-section front page; /// topic pills get a hero card + flat list within that topic. @ViewBuilder private var digestContent: some View { - let d = digest - let headerLabel = selectedPill == .all - ? "TOP STORIES" - : selectedPill.label.uppercased() + let activeSections = selectedPill == .all + ? NewsSection.sections + : (selectedPill.subSections ?? []) + let d = makeDigest(sections: activeSections) + let headerLabel = selectedPill == .all ? "TOP STORIES" : selectedPill.label.uppercased() + sectionHeader(headerLabel, pill: nil) if let lead = d.top.first { mainRow(lead, hero: true) @@ -273,11 +275,10 @@ struct SignalFeedView: View { } else { emptyState.plainBlackRow() } - if selectedPill == .all { - ForEach(d.sections, id: \.0.id) { section, stories in - sectionHeader(section.label.uppercased(), pill: section.pill) - ForEach(stories) { mainRow($0) } - } + // "All" and pills with sub-sections (Tech) get a sectioned breakdown. + ForEach(d.sections, id: \.0.id) { section, stories in + sectionHeader(section.label.uppercased(), pill: section.pill) + ForEach(stories) { mainRow($0) } } readShelf } diff --git a/Jarvis/Views/Shared/Theme.swift b/Jarvis/Views/Shared/Theme.swift index 93a5e81..b0ffc03 100644 --- a/Jarvis/Views/Shared/Theme.swift +++ b/Jarvis/Views/Shared/Theme.swift @@ -216,10 +216,7 @@ enum StoryPill: String, CaseIterable, Identifiable { case all = "All" case f1 = "F1" case sport = "Sport" - case ai = "AI" - case cloud = "Cloud" - case homelab = "HomeLab" - case tech = "Tech" + case tech = "Tech" // covers AI, Cloud, HomeLab — use sub-sections inside case uganda = "Uganda" case southAfrica = "South Africa" case canada = "Canada" @@ -233,18 +230,27 @@ enum StoryPill: String, CaseIterable, Identifiable { case .all: return [] case .f1: return ["formula-1"] case .sport: return ["sports", "esports"] - case .ai: return ["artificial-intelligence", "machine-learning", "robotics"] - case .cloud: return ["cloud-computing"] - case .homelab: return ["homelab"] - case .tech: return ["technology", "programming-and-software-development", - "cybersecurity", "security", "privacy-and-data-protection", - "web-design-and-ui-ux", "wordpress-and-web-development"] + case .tech: return [ + "technology", "programming-and-software-development", + "cybersecurity", "security", "privacy-and-data-protection", + "web-design-and-ui-ux", "wordpress-and-web-development", + "artificial-intelligence", "machine-learning", "robotics", + "cloud-computing", "homelab", + ] case .uganda: return ["uganda"] case .southAfrica: return ["south-africa"] case .canada: return ["canada"] case .unitedStates: return ["united-states"] } } + + /// Sub-sections to show inside the card layout for this pill (nil = flat). + var subSections: [NewsSection]? { + switch self { + case .tech: return NewsSection.techSubSections + default: return nil + } + } } extension StorySummary { @@ -289,17 +295,26 @@ struct NewsSection: Identifiable { let pill: StoryPill? static let sections: [NewsSection] = [ - .init(id: "us", label: "US", slugs: ["united-states"], pill: .unitedStates), - .init(id: "world", label: "World", slugs: ["world-news", "news", "news-and-current-affairs"], pill: nil), - .init(id: "politics", label: "Politics", slugs: ["politics", "human-rights", "investigative"], pill: nil), - .init(id: "business", label: "Business", slugs: ["business", "finance", "fintech", "cryptocurrency", "blockchain", "entrepreneur", "startups", "real-estate"], pill: nil), - .init(id: "ai", label: "AI", slugs: ["artificial-intelligence", "machine-learning", "robotics"], pill: .ai), - .init(id: "technology", label: "Technology", slugs: ["technology", "programming-and-software-development", "cybersecurity", "security", "web-design-and-ui-ux", "wordpress-and-web-development", "privacy-and-data-protection", "cloud-computing"], pill: .tech), - .init(id: "africa", label: "Africa", slugs: ["africa", "east-africa", "south-africa", "uganda"], pill: nil), - .init(id: "sport", label: "Sport", slugs: ["sports", "esports", "formula-1"], pill: .sport), - .init(id: "science", label: "Science", slugs: ["science", "astronomy", "environment", "green-technology", "sustainability"], pill: nil), - .init(id: "entertainment", label: "Entertainment", slugs: ["entertainment", "music", "pop-culture", "video-game", "arts-and-culture", "books-and-literature"], pill: nil), - .init(id: "health", label: "Health", slugs: ["health", "health-and-wellness", "mental-health"], pill: nil), - .init(id: "lifestyle", label: "Lifestyle", slugs: ["lifestyle", "food-and-drink", "travel", "gardening", "outdoor-and-hiking", "parenting-and-family", "interior-design-and-home-decor", "photography", "self-improvement-and-personal-development", "education", "e-learning"], pill: nil), + .init(id: "us", label: "US", slugs: ["united-states"], pill: .unitedStates), + .init(id: "world", label: "World", slugs: ["world-news", "news", "news-and-current-affairs"], pill: nil), + .init(id: "politics", label: "Politics", slugs: ["politics", "human-rights", "investigative"], pill: nil), + .init(id: "business", label: "Business", slugs: ["business", "finance", "fintech", "cryptocurrency", "blockchain", "entrepreneur", "startups", "real-estate"], pill: nil), + .init(id: "tech", label: "Tech", slugs: ["technology", "programming-and-software-development", "cybersecurity", "security", "web-design-and-ui-ux", "wordpress-and-web-development", "privacy-and-data-protection", "cloud-computing", "artificial-intelligence", "machine-learning", "robotics", "homelab"], pill: .tech), + .init(id: "africa", label: "Africa", slugs: ["africa", "east-africa", "south-africa", "uganda"], pill: nil), + .init(id: "sport", label: "Sport", slugs: ["sports", "esports", "formula-1"], pill: .sport), + .init(id: "science", label: "Science", slugs: ["science", "astronomy", "environment", "green-technology", "sustainability"], pill: nil), + .init(id: "entertainment", label: "Entertainment",slugs: ["entertainment", "music", "pop-culture", "video-game", "arts-and-culture", "books-and-literature"], pill: nil), + .init(id: "health", label: "Health", slugs: ["health", "health-and-wellness", "mental-health"], pill: nil), + .init(id: "lifestyle", label: "Lifestyle", slugs: ["lifestyle", "food-and-drink", "travel", "gardening", "outdoor-and-hiking", "parenting-and-family", "interior-design-and-home-decor", "photography", "self-improvement-and-personal-development", "education", "e-learning"], pill: nil), + ] + + /// Sub-sections shown inside the Tech pill view. + static let techSubSections: [NewsSection] = [ + .init(id: "t-ai", label: "AI", slugs: ["artificial-intelligence", "machine-learning", "robotics"], pill: nil), + .init(id: "t-cloud", label: "Cloud", slugs: ["cloud-computing"], pill: nil), + .init(id: "t-homelab", label: "HomeLab", slugs: ["homelab"], pill: nil), + .init(id: "t-security", label: "Security", slugs: ["cybersecurity", "security", "privacy-and-data-protection"], pill: nil), + .init(id: "t-dev", label: "Dev", slugs: ["programming-and-software-development", "web-design-and-ui-ux", "wordpress-and-web-development"], pill: nil), + .init(id: "t-tech", label: "Technology", slugs: ["technology"], pill: nil), ] }