66 "github.com/0xjuanma/golazo/internal/api"
77 "github.com/0xjuanma/golazo/internal/fotmob"
88 "github.com/0xjuanma/golazo/internal/ui"
9+ "github.com/charmbracelet/bubbles/list"
910 "github.com/charmbracelet/bubbles/spinner"
1011 tea "github.com/charmbracelet/bubbletea"
1112)
@@ -57,6 +58,10 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
5758 case pollDisplayCompleteMsg :
5859 return m .handlePollDisplayComplete ()
5960
61+ case list.FilterMatchesMsg :
62+ // Route filter matches message to the appropriate list based on current view
63+ return m .handleFilterMatches (msg )
64+
6065 default :
6166 // Fallback handler for ui.TickMsg type assertion
6267 if _ , ok := msg .(ui.TickMsg ); ok {
@@ -198,6 +203,23 @@ func (m model) handleKeyPress(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
198203 case "q" , "ctrl+c" :
199204 return m , tea .Quit
200205 case "esc" :
206+ // Check if any list is in filtering mode - if so, let the list handle Esc
207+ // to cancel the filter instead of navigating back
208+ isFiltering := false
209+ switch m .currentView {
210+ case viewLiveMatches :
211+ isFiltering = m .liveMatchesList .FilterState () == list .Filtering ||
212+ m .liveMatchesList .FilterState () == list .FilterApplied
213+ case viewStats :
214+ isFiltering = m .statsMatchesList .FilterState () == list .Filtering ||
215+ m .statsMatchesList .FilterState () == list .FilterApplied
216+ }
217+
218+ if isFiltering {
219+ // Let the view-specific handler pass Esc to the list to cancel filter
220+ break
221+ }
222+
201223 if m .currentView != viewMain {
202224 return m .resetToMainView ()
203225 }
@@ -211,6 +233,8 @@ func (m model) handleKeyPress(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
211233 return m .handleLiveMatchesSelection (msg )
212234 case viewStats :
213235 return m .handleStatsSelection (msg )
236+ case viewSettings :
237+ return m .handleSettingsViewKeys (msg )
214238 }
215239
216240 return m , nil
@@ -233,43 +257,106 @@ func (m model) resetToMainView() (tea.Model, tea.Cmd) {
233257
234258// handleLiveMatchesSelection handles list navigation in live matches view.
235259func (m model ) handleLiveMatchesSelection (msg tea.KeyMsg ) (tea.Model , tea.Cmd ) {
260+ // Capture selected item BEFORE Update (critical for filter mode - selection changes after filter clears)
261+ var preUpdateMatchID int
262+ if preItem := m .liveMatchesList .SelectedItem (); preItem != nil {
263+ if item , ok := preItem .(ui.MatchListItem ); ok {
264+ preUpdateMatchID = item .Match .ID
265+ }
266+ }
267+
236268 var listCmd tea.Cmd
237269 m .liveMatchesList , listCmd = m .liveMatchesList .Update (msg )
238270
239- if selectedItem := m .liveMatchesList .SelectedItem (); selectedItem != nil {
240- if item , ok := selectedItem .(ui.MatchListItem ); ok {
241- for i , match := range m .matches {
242- if match .ID == item .Match .ID && i != m .selected {
243- m .selected = i
244- return m .loadMatchDetails (m .matches [m .selected ].ID )
245- }
271+ // Get currently displayed match ID
272+ currentMatchID := 0
273+ if m .matchDetails != nil {
274+ currentMatchID = m .matchDetails .ID
275+ }
276+
277+ // Check post-update selection
278+ var postUpdateMatchID int
279+ if postItem := m .liveMatchesList .SelectedItem (); postItem != nil {
280+ if item , ok := postItem .(ui.MatchListItem ); ok {
281+ postUpdateMatchID = item .Match .ID
282+ }
283+ }
284+
285+ // Use pre-update selection if it was valid and different from current
286+ // This handles the filter case where Enter clears the filter
287+ targetMatchID := postUpdateMatchID
288+ if msg .String () == "enter" && preUpdateMatchID != 0 {
289+ targetMatchID = preUpdateMatchID
290+ }
291+
292+ // Load match details if selection changed
293+ if targetMatchID != 0 && targetMatchID != currentMatchID {
294+ for i , match := range m .matches {
295+ if match .ID == targetMatchID {
296+ m .selected = i
297+ break
246298 }
247299 }
300+ return m .loadMatchDetails (targetMatchID )
248301 }
249302
250303 return m , listCmd
251304}
252305
253306// handleStatsSelection handles list navigation and date range changes in stats view.
254307func (m model ) handleStatsSelection (msg tea.KeyMsg ) (tea.Model , tea.Cmd ) {
255- // Handle date range navigation
256- if msg .String () == "h" || msg .String () == "left" || msg .String () == "l" || msg .String () == "right" {
257- return m .handleStatsViewKeys (msg )
308+ // Check if list is in filtering mode - if so, let list handle ALL keys
309+ isFiltering := m .statsMatchesList .FilterState () == list .Filtering
310+
311+ // Only handle date range navigation when NOT filtering
312+ if ! isFiltering {
313+ if msg .String () == "h" || msg .String () == "left" || msg .String () == "l" || msg .String () == "right" {
314+ return m .handleStatsViewKeys (msg )
315+ }
316+ }
317+
318+ // Capture selected item BEFORE Update (critical for filter mode - selection changes after filter clears)
319+ var preUpdateMatchID int
320+ if preItem := m .statsMatchesList .SelectedItem (); preItem != nil {
321+ if item , ok := preItem .(ui.MatchListItem ); ok {
322+ preUpdateMatchID = item .Match .ID
323+ }
258324 }
259325
260326 // Handle list navigation
261327 var listCmd tea.Cmd
262328 m .statsMatchesList , listCmd = m .statsMatchesList .Update (msg )
263329
264- if selectedItem := m .statsMatchesList .SelectedItem (); selectedItem != nil {
265- if item , ok := selectedItem .(ui.MatchListItem ); ok {
266- for i , match := range m .matches {
267- if match .ID == item .Match .ID && i != m .selected {
268- m .selected = i
269- return m .loadStatsMatchDetails (m .matches [m .selected ].ID )
270- }
330+ // Get currently displayed match ID
331+ currentMatchID := 0
332+ if m .matchDetails != nil {
333+ currentMatchID = m .matchDetails .ID
334+ }
335+
336+ // Check post-update selection
337+ var postUpdateMatchID int
338+ if postItem := m .statsMatchesList .SelectedItem (); postItem != nil {
339+ if item , ok := postItem .(ui.MatchListItem ); ok {
340+ postUpdateMatchID = item .Match .ID
341+ }
342+ }
343+
344+ // Use pre-update selection if it was valid and different from current
345+ // This handles the filter case where Enter clears the filter
346+ targetMatchID := postUpdateMatchID
347+ if msg .String () == "enter" && preUpdateMatchID != 0 {
348+ targetMatchID = preUpdateMatchID
349+ }
350+
351+ // Load match details if selection changed
352+ if targetMatchID != 0 && targetMatchID != currentMatchID {
353+ for i , match := range m .matches {
354+ if match .ID == targetMatchID {
355+ m .selected = i
356+ break
271357 }
272358 }
359+ return m .loadStatsMatchDetails (targetMatchID )
273360 }
274361
275362 return m , listCmd
@@ -738,6 +825,28 @@ func (m model) handlePollDisplayComplete() (tea.Model, tea.Cmd) {
738825 return m , nil
739826}
740827
828+ // handleFilterMatches routes filter matches messages to the appropriate list.
829+ // This is required for the bubbles list filter to work - it fires async matching
830+ // and sends results via FilterMatchesMsg which must be routed back to the list.
831+ func (m model ) handleFilterMatches (msg list.FilterMatchesMsg ) (tea.Model , tea.Cmd ) {
832+ var cmd tea.Cmd
833+
834+ switch m .currentView {
835+ case viewLiveMatches :
836+ m .liveMatchesList , cmd = m .liveMatchesList .Update (msg )
837+ case viewStats :
838+ m .statsMatchesList , cmd = m .statsMatchesList .Update (msg )
839+ // Also update upcoming list in case it's being filtered
840+ var upCmd tea.Cmd
841+ m .upcomingMatchesList , upCmd = m .upcomingMatchesList .Update (msg )
842+ if upCmd != nil {
843+ cmd = tea .Batch (cmd , upCmd )
844+ }
845+ }
846+
847+ return m , cmd
848+ }
849+
741850// max returns the larger of two integers.
742851func max (a , b int ) int {
743852 if a > b {
0 commit comments