fix: Reader pre-loads on app launch, no more glitchy initial state
- ReaderViewModel starts with isLoading=true (shows spinner, not "No articles") - MainTabView owns ReaderViewModel and pre-fetches in background on launch - Sub-tabs and feed chips hidden during initial load (no tiny squished layout) - VStack fills full screen with frame(maxWidth/maxHeight: .infinity) - WebKit warmer triggers when Reader tab appears - By the time user taps Reader, data is already loaded Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -26,6 +26,7 @@ struct MainTabView: View {
|
|||||||
@State private var selectedTab = 0
|
@State private var selectedTab = 0
|
||||||
@State private var showAssistant = false
|
@State private var showAssistant = false
|
||||||
@State private var confettiTrigger = 0
|
@State private var confettiTrigger = 0
|
||||||
|
@State private var readerVM = ReaderViewModel()
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
@@ -38,12 +39,16 @@ struct MainTabView: View {
|
|||||||
.tabItem { Label("Fitness", systemImage: "flame.fill") }
|
.tabItem { Label("Fitness", systemImage: "flame.fill") }
|
||||||
.tag(1)
|
.tag(1)
|
||||||
|
|
||||||
ReaderTabView()
|
ReaderTabView(vm: readerVM)
|
||||||
.tabItem { Label("Reader", systemImage: "newspaper.fill") }
|
.tabItem { Label("Reader", systemImage: "newspaper.fill") }
|
||||||
.tag(2)
|
.tag(2)
|
||||||
}
|
}
|
||||||
.tint(Color.accentWarm)
|
.tint(Color.accentWarm)
|
||||||
.modifier(TabBarMinimizeModifier())
|
.modifier(TabBarMinimizeModifier())
|
||||||
|
.task {
|
||||||
|
// Pre-fetch reader data in background while user is on Home/Fitness
|
||||||
|
await readerVM.loadInitial()
|
||||||
|
}
|
||||||
|
|
||||||
// Floating buttons
|
// Floating buttons
|
||||||
VStack {
|
VStack {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ final class ReaderViewModel {
|
|||||||
var categories: [ReaderCategory] = []
|
var categories: [ReaderCategory] = []
|
||||||
var counters: ReaderCounters?
|
var counters: ReaderCounters?
|
||||||
var total = 0
|
var total = 0
|
||||||
var isLoading = false
|
var isLoading = true
|
||||||
var isLoadingMore = false
|
var isLoadingMore = false
|
||||||
var isRefreshing = false
|
var isRefreshing = false
|
||||||
var error: String?
|
var error: String?
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct ReaderTabView: View {
|
struct ReaderTabView: View {
|
||||||
@State private var vm = ReaderViewModel()
|
@Bindable var vm: ReaderViewModel
|
||||||
@State private var selectedSubTab = 0
|
@State private var selectedSubTab = 0
|
||||||
@State private var showFeedSheet = false
|
@State private var showFeedSheet = false
|
||||||
@State private var showFeedManagement = false
|
@State private var showFeedManagement = false
|
||||||
@@ -10,7 +10,8 @@ struct ReaderTabView: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
// Sub-tab selector
|
if !vm.isLoading || !vm.entries.isEmpty {
|
||||||
|
// Sub-tab selector — only show after initial load
|
||||||
HStack(spacing: 0) {
|
HStack(spacing: 0) {
|
||||||
ForEach(Array(subTabs.enumerated()), id: \.offset) { index, tab in
|
ForEach(Array(subTabs.enumerated()), id: \.offset) { index, tab in
|
||||||
Button {
|
Button {
|
||||||
@@ -54,6 +55,7 @@ struct ReaderTabView: View {
|
|||||||
.padding(.top, 8)
|
.padding(.top, 8)
|
||||||
|
|
||||||
// Feed filter bar
|
// Feed filter bar
|
||||||
|
if !vm.feeds.isEmpty {
|
||||||
ScrollView(.horizontal, showsIndicators: false) {
|
ScrollView(.horizontal, showsIndicators: false) {
|
||||||
HStack(spacing: 8) {
|
HStack(spacing: 8) {
|
||||||
feedFilterChip("All", isSelected: isAllSelected) {
|
feedFilterChip("All", isSelected: isAllSelected) {
|
||||||
@@ -80,10 +82,13 @@ struct ReaderTabView: View {
|
|||||||
.padding(.horizontal)
|
.padding(.horizontal)
|
||||||
.padding(.vertical, 8)
|
.padding(.vertical, 8)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Entry list
|
// Entry list (shows LoadingView when isLoading)
|
||||||
EntryListView(vm: vm, isCardView: isCardView)
|
EntryListView(vm: vm, isCardView: isCardView)
|
||||||
}
|
}
|
||||||
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
.background(Color.canvas)
|
.background(Color.canvas)
|
||||||
.navigationBarHidden(true)
|
.navigationBarHidden(true)
|
||||||
.toolbar {
|
.toolbar {
|
||||||
@@ -137,9 +142,8 @@ struct ReaderTabView: View {
|
|||||||
FeedManagementSheet(vm: vm)
|
FeedManagementSheet(vm: vm)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.task {
|
.onAppear {
|
||||||
WebKitWarmer.shared.warmUp()
|
WebKitWarmer.shared.warmUp()
|
||||||
await vm.loadInitial()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user