@@ -115,26 +115,30 @@ struct DemoButton: View {
115115
116116struct PokemonListView : View {
117117 @State private var showDevTools = false
118+ @State private var initialOffset = 0
119+ @State private var showOffsetPicker = false
118120
119121 var body : some View {
120122 WithPerceptionTracking {
121123 UseInfiniteQuery (
122- queryKey: " pokemon-infinite-list " ,
124+ queryKey: " pokemon-infinite-list- \( initialOffset ) " , // Include offset in key for cache separation
123125 queryFn: { _, pageParam in
124- try await PokemonAPI . fetchPokemonPage ( offset: pageParam ?? 0 )
126+ let offset = pageParam ?? initialOffset
127+ return try await PokemonAPI . fetchPokemonPage ( offset: offset)
125128 } ,
126129 getNextPageParam: { pages in
127- // Calculate next offset based on current pages
130+ // Calculate next offset based on current pages and initial offset
128131 let currentTotal = pages. reduce ( 0 ) { total, page in total + page. results. count }
132+ let nextOffset = initialOffset + currentTotal
129133 let lastPage = pages. last
130134
131135 // If we have next URL or haven't reached the total count, continue pagination
132136 if let lastPage, lastPage. next != nil {
133- return currentTotal
137+ return nextOffset
134138 }
135139 return nil // No more pages
136140 } ,
137- initialPageParam: 0 ,
141+ initialPageParam: initialOffset ,
138142 staleTime: 5 * 60 // 5 minutes before considered stale
139143 ) { result in
140144 if result. isLoading, result. data? . pages. isEmpty != false {
@@ -233,6 +237,14 @@ struct PokemonListView: View {
233237 }
234238 . navigationTitle ( " Pokemon " )
235239 . toolbar {
240+ ToolbarItem ( placement: . navigationBarLeading) {
241+ Button ( " Start at # \( initialOffset + 1 ) " ) {
242+ showOffsetPicker = true
243+ }
244+ . font ( . caption)
245+ . buttonStyle ( . bordered)
246+ }
247+
236248 ToolbarItem ( placement: . navigationBarTrailing) {
237249 Button ( " 🛠️ " ) {
238250 showDevTools = true
@@ -242,6 +254,9 @@ struct PokemonListView: View {
242254 . sheet ( isPresented: $showDevTools) {
243255 DevToolsView ( )
244256 }
257+ . sheet ( isPresented: $showOffsetPicker) {
258+ OffsetPickerView ( initialOffset: $initialOffset)
259+ }
245260 }
246261 }
247262}
@@ -798,3 +813,112 @@ struct ErrorView: View {
798813 }
799814 }
800815}
816+
817+ // MARK: - Offset Picker View
818+
819+ struct OffsetPickerView : View {
820+ @Binding var initialOffset : Int
821+ @Environment ( \. dismiss) private var dismiss
822+ @State private var selectedOffset : Int
823+
824+ init ( initialOffset: Binding < Int > ) {
825+ self . _initialOffset = initialOffset
826+ self . _selectedOffset = State ( initialValue: initialOffset. wrappedValue)
827+ }
828+
829+ var body : some View {
830+ NavigationView {
831+ VStack ( spacing: 24 ) {
832+ VStack ( spacing: 16 ) {
833+ Text ( " 🎯 " )
834+ . font ( . system( size: 50 ) )
835+
836+ Text ( " Choose Starting Pokemon " )
837+ . font ( . title2)
838+ . fontWeight ( . bold)
839+
840+ Text (
841+ " Select which Pokemon number to start the list from. This demonstrates how infinite queries can begin from any offset. "
842+ )
843+ . font ( . subheadline)
844+ . foregroundColor ( . secondary)
845+ . multilineTextAlignment ( . center)
846+ . padding ( . horizontal)
847+ }
848+
849+ VStack ( spacing: 20 ) {
850+ // Quick preset buttons
851+ Text ( " Quick Presets " )
852+ . font ( . headline)
853+ . frame ( maxWidth: . infinity, alignment: . leading)
854+
855+ LazyVGrid ( columns: Array ( repeating: GridItem ( . flexible( ) , spacing: 12 ) , count: 3 ) , spacing: 12 ) {
856+ ForEach ( [ 0 , 50 , 100 , 150 , 200 , 250 , 300 , 350 , 400 ] , id: \. self) { offset in
857+ Button ( " Pokemon # \( offset + 1 ) " ) {
858+ selectedOffset = offset
859+ }
860+ . buttonStyle ( . bordered)
861+ . foregroundColor ( selectedOffset == offset ? . blue : . primary)
862+ . fontWeight ( selectedOffset == offset ? . semibold : . regular)
863+ }
864+ }
865+
866+ Divider ( )
867+
868+ // Custom offset input
869+ VStack ( alignment: . leading, spacing: 12 ) {
870+ Text ( " Custom Pokemon Number " )
871+ . font ( . headline)
872+
873+ HStack {
874+ Text ( " Start at Pokemon # " )
875+ TextField ( " 1 " , value: Binding (
876+ get: { selectedOffset + 1 } , // Convert offset to Pokemon number
877+ set: { selectedOffset = max ( 0 , $0 - 1 ) } // Convert Pokemon number to offset
878+ ) , format: . number)
879+ . textFieldStyle ( . roundedBorder)
880+ . keyboardType ( . numberPad)
881+ . frame ( width: 80 )
882+ }
883+
884+ Text ( " Enter a number between 1 and 1000 " )
885+ . font ( . caption)
886+ . foregroundColor ( . secondary)
887+ }
888+ . frame ( maxWidth: . infinity, alignment: . leading)
889+ }
890+
891+ Spacer ( )
892+
893+ VStack ( spacing: 12 ) {
894+ Button ( " Start from Pokemon # \( max ( 1 , selectedOffset + 1 ) ) " ) {
895+ // Ensure valid range (0-999 for API compatibility)
896+ let validOffset = max ( 0 , min ( selectedOffset, 999 ) )
897+ initialOffset = validOffset
898+ dismiss ( )
899+ }
900+ . buttonStyle ( . borderedProminent)
901+ . disabled ( selectedOffset < 0 )
902+
903+ Button ( " Cancel " ) {
904+ dismiss ( )
905+ }
906+ . buttonStyle ( . bordered)
907+ }
908+ }
909+ . padding ( )
910+ . navigationTitle ( " Starting Pokemon " )
911+ . navigationBarTitleDisplayMode ( . inline)
912+ . toolbar {
913+ ToolbarItem ( placement: . navigationBarTrailing) {
914+ Button ( " Done " ) {
915+ let validOffset = max ( 0 , min ( selectedOffset, 999 ) )
916+ initialOffset = validOffset
917+ dismiss ( )
918+ }
919+ }
920+ }
921+ }
922+ . presentationDetents ( [ . medium, . large] )
923+ }
924+ }
0 commit comments