@@ -37,7 +37,7 @@ struct ContentView: View {
3737 DemoButton (
3838 icon: " list.bullet.rectangle " ,
3939 title: " Pokemon List " ,
40- description: " Browse Pokemon with caching and infinite loading "
40+ description: " Infinite scrolling Pokemon list with automatic pagination "
4141 )
4242 }
4343
@@ -118,29 +118,108 @@ struct PokemonListView: View {
118118
119119 var body : some View {
120120 WithPerceptionTracking {
121- UseQuery (
122- queryKey: " pokemon-list " ,
123- queryFn: { _ in try await PokemonAPI . fetchPokemonList ( limit: 50 ) } ,
124- staleTime: 5 // 5 minutes before considered stale
121+ UseInfiniteQuery (
122+ queryKey: " pokemon-infinite-list " ,
123+ queryFn: { _, pageParam in
124+ try await PokemonAPI . fetchPokemonPage ( offset: pageParam ?? 0 )
125+ } ,
126+ getNextPageParam: { pages in
127+ // Calculate next offset based on current pages
128+ let currentTotal = pages. reduce ( 0 ) { total, page in total + page. results. count }
129+ let lastPage = pages. last
130+
131+ // If we have next URL or haven't reached the total count, continue pagination
132+ if let lastPage, lastPage. next != nil {
133+ return currentTotal
134+ }
135+ return nil // No more pages
136+ } ,
137+ initialPageParam: 0 ,
138+ staleTime: 5 * 60 // 5 minutes before considered stale
125139 ) { result in
126- if result. isLoading {
140+ if result. isLoading, result. data? . pages. isEmpty != false {
141+ // Initial loading state
127142 VStack ( spacing: 16 ) {
128143 ProgressView ( )
129144 . scaleEffect ( 1.2 )
130145 Text ( " Loading Pokemon... " )
131146 . foregroundColor ( . secondary)
132147 }
133148 . frame ( maxWidth: . infinity, maxHeight: . infinity)
134- } else if let error = result. error {
149+ } else if let error = result. error, result. data? . pages. isEmpty != false {
150+ // Error state when no data is loaded
135151 ErrorView ( error: error) {
136152 Task {
137153 _ = try ? await result. refetch ( )
138154 }
139155 }
140- } else if let pokemonList = result. data {
141- List ( pokemonList. results) { pokemon in
142- NavigationLink ( destination: PokemonDetailView ( pokemonId: pokemon. pokemonId) ) {
143- PokemonListRow ( pokemon: pokemon)
156+ } else if let infiniteData = result. data {
157+ // Show the list with infinite scrolling
158+ ScrollView {
159+ LazyVStack ( spacing: 0 ) {
160+ // Render all Pokemon from all pages
161+ ForEach ( infiniteData. pages. indices, id: \. self) { pageIndex in
162+ let page = infiniteData. pages [ pageIndex]
163+ ForEach ( page. results) { pokemon in
164+ NavigationLink ( destination: PokemonDetailView ( pokemonId: pokemon. pokemonId) ) {
165+ PokemonListRow ( pokemon: pokemon)
166+ . padding ( . horizontal)
167+ . padding ( . vertical, 8 )
168+ }
169+ . buttonStyle ( PlainButtonStyle ( ) )
170+
171+ // Add divider except for last item
172+ if pokemon. id != page. results. last? . id || pageIndex != infiniteData. pages
173+ . count - 1 {
174+ Divider ( )
175+ . padding ( . horizontal)
176+ }
177+ }
178+ }
179+
180+ // Load more section
181+ if result. hasNextPage {
182+ VStack ( spacing: 12 ) {
183+ if result. isFetchingNextPage {
184+ HStack ( spacing: 8 ) {
185+ ProgressView ( )
186+ . scaleEffect ( 0.8 )
187+ Text ( " Loading more Pokemon... " )
188+ . foregroundColor ( . secondary)
189+ . font ( . subheadline)
190+ }
191+ . padding ( )
192+ } else {
193+ Button ( " Load More Pokemon " ) {
194+ Task {
195+ _ = await result. fetchNextPage ( )
196+ }
197+ }
198+ . buttonStyle ( . bordered)
199+ . padding ( )
200+ }
201+ }
202+ . frame ( maxWidth: . infinity)
203+ . onAppear {
204+ // Auto-load when this view appears (infinite scrolling)
205+ if !result. isFetchingNextPage {
206+ Task {
207+ _ = await result. fetchNextPage ( )
208+ }
209+ }
210+ }
211+ } else {
212+ // End of list indicator
213+ VStack ( spacing: 8 ) {
214+ Text ( " 🎉 " )
215+ . font ( . title)
216+ Text ( " You've seen all available Pokemon! " )
217+ . font ( . subheadline)
218+ . foregroundColor ( . secondary)
219+ }
220+ . padding ( )
221+ . frame ( maxWidth: . infinity)
222+ }
144223 }
145224 }
146225 . refreshable {
0 commit comments