feat: East Africa + Southern Africa pills with regional sub-sections
- East Africa pill: merges Uganda + east-africa tags; Uganda pinned first via sub-sections - Southern Africa pill: covers SA, Namibia, Botswana, Zimbabwe, Zambia, Mozambique; SA/Namibia/Botswana prioritized - StoryStore.setPill now resets lastSyncedAt to bypass 30s rate-limit on filter switch - sectionSupplement background fetch populates sparse regional sections in All digest - SignalFeedView digestContent: flat hero+scroll path for pills without subSections (was showing max 5) - Theme: removed standalone Uganda pill, wired new sub-section layouts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -69,20 +69,21 @@ struct StoryDetailView: View {
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
Color.black.ignoresSafeArea()
|
||||
Palette.background.ignoresSafeArea()
|
||||
ScrollView {
|
||||
VStack(alignment: .leading, spacing: 20) {
|
||||
categoryRow
|
||||
Text(headline)
|
||||
.font(.system(size: 28, weight: .heavy))
|
||||
.foregroundStyle(.white)
|
||||
.font(.system(size: 31, weight: .bold, design: .default))
|
||||
.lineSpacing(2)
|
||||
.foregroundStyle(Palette.primaryText)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
|
||||
if !summary.isEmpty {
|
||||
Text(summary)
|
||||
.font(.system(size: 15, weight: .regular))
|
||||
.foregroundStyle(Color(hex: "9A9A9A"))
|
||||
.lineSpacing(4)
|
||||
.font(.system(size: 18, weight: .regular, design: .default))
|
||||
.foregroundStyle(Palette.secondaryText)
|
||||
.lineSpacing(5)
|
||||
}
|
||||
|
||||
SourceChips(names: sourceNames, total: sourceCount)
|
||||
@@ -123,7 +124,7 @@ struct StoryDetailView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.toolbarBackground(Color.black, for: .navigationBar)
|
||||
.toolbarBackground(Palette.background, for: .navigationBar)
|
||||
.toolbarBackground(.visible, for: .navigationBar)
|
||||
.sheet(isPresented: $showShare) { ShareSheet(items: shareContent) }
|
||||
.task {
|
||||
@@ -195,7 +196,7 @@ struct StoryDetailView: View {
|
||||
.font(.system(size: 11, weight: .bold, design: .monospaced))
|
||||
.kerning(0.8)
|
||||
.foregroundStyle(Color(hex: "888888"))
|
||||
Text("·").foregroundStyle(Color(hex: "444444"))
|
||||
Text("·").foregroundStyle(Palette.mutedText)
|
||||
Text("\(score) signal")
|
||||
.font(.system(size: 11, weight: .bold, design: .monospaced))
|
||||
.foregroundStyle(Signal.scoreColor(score))
|
||||
@@ -228,8 +229,8 @@ struct StoryDetailView: View {
|
||||
.kerning(0.8)
|
||||
.foregroundStyle(titleColor)
|
||||
Text(text)
|
||||
.font(.system(size: 14, weight: .regular))
|
||||
.foregroundStyle(Color(hex: "C8C8C8"))
|
||||
.font(.system(size: 16, weight: .regular, design: .default))
|
||||
.foregroundStyle(Palette.secondaryText)
|
||||
.lineSpacing(3)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
}
|
||||
@@ -245,7 +246,7 @@ struct StoryDetailView: View {
|
||||
Text("COVERAGE TIMELINE")
|
||||
.font(.system(size: 10, weight: .bold, design: .monospaced))
|
||||
.kerning(0.8)
|
||||
.foregroundStyle(Color(hex: "555555"))
|
||||
.foregroundStyle(Palette.tertiaryText)
|
||||
.padding(.bottom, 14)
|
||||
|
||||
if vm.isLoading && timeline.isEmpty {
|
||||
@@ -253,7 +254,7 @@ struct StoryDetailView: View {
|
||||
} else if timeline.isEmpty {
|
||||
Text(vm.error ?? "No coverage available.")
|
||||
.font(.system(size: 13))
|
||||
.foregroundStyle(Color(hex: "555555"))
|
||||
.foregroundStyle(Palette.tertiaryText)
|
||||
} else {
|
||||
ForEach(Array(timeline.enumerated()), id: \.element.id) { index, entry in
|
||||
NavigationLink(value: ArticleRoute(articleId: entry.articleId,
|
||||
@@ -275,11 +276,11 @@ struct StoryDetailView: View {
|
||||
// Node + connector
|
||||
VStack(spacing: 0) {
|
||||
Circle()
|
||||
.fill(isFirst ? Palette.orange : Color(hex: "333333"))
|
||||
.fill(isFirst ? Palette.orange : Palette.mutedText)
|
||||
.frame(width: 11, height: 11)
|
||||
.overlay(Circle().stroke(Color.black, lineWidth: 2))
|
||||
.overlay(Circle().stroke(Palette.background, lineWidth: 2))
|
||||
if !isLast {
|
||||
Rectangle().fill(Color(hex: "222222")).frame(width: 1.5)
|
||||
Rectangle().fill(Palette.hairline).frame(width: 1.5)
|
||||
}
|
||||
}
|
||||
.frame(width: 11)
|
||||
@@ -287,12 +288,12 @@ struct StoryDetailView: View {
|
||||
VStack(alignment: .leading, spacing: 5) {
|
||||
HStack(spacing: 8) {
|
||||
Text(entry.source)
|
||||
.font(.system(size: 12, weight: .bold))
|
||||
.foregroundStyle(isFirst ? Palette.orange : Color(hex: "999999"))
|
||||
.font(.system(size: 14, weight: .semibold, design: .default))
|
||||
.foregroundStyle(isFirst ? Palette.orange : Palette.primaryText)
|
||||
if entry.isBreaking {
|
||||
Text("BREAKING")
|
||||
.font(.system(size: 9, weight: .bold, design: .monospaced))
|
||||
.foregroundStyle(.white)
|
||||
.foregroundStyle(Palette.primaryText)
|
||||
.padding(.horizontal, 5).padding(.vertical, 2)
|
||||
.background(Palette.conflictBorder)
|
||||
.clipShape(Capsule())
|
||||
@@ -300,12 +301,12 @@ struct StoryDetailView: View {
|
||||
Spacer()
|
||||
Text(entry.publishedAt.clockShort)
|
||||
.font(.system(size: 11, weight: .regular, design: .monospaced))
|
||||
.foregroundStyle(Color(hex: "555555"))
|
||||
.foregroundStyle(Palette.tertiaryText)
|
||||
if cached { CachedDot(size: 6) }
|
||||
}
|
||||
Text(entry.headline)
|
||||
.font(.system(size: 15, weight: .regular))
|
||||
.foregroundStyle(Color(hex: "D0D0D0"))
|
||||
.font(.system(size: 16, weight: .regular, design: .default))
|
||||
.foregroundStyle(Palette.secondaryText)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.multilineTextAlignment(.leading)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user