mirror of
https://github.com/sstent/aicyclingcoach-go.git
synced 2025-12-06 08:01:38 +00:00
152 lines
4.2 KiB
Go
152 lines
4.2 KiB
Go
// internal/tui/app.go
|
|
package tui
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
"github.com/sstent/fitness-tui/internal/config"
|
|
"github.com/sstent/fitness-tui/internal/garmin"
|
|
"github.com/sstent/fitness-tui/internal/storage"
|
|
"github.com/sstent/fitness-tui/internal/tui/screens"
|
|
)
|
|
|
|
type App struct {
|
|
currentModel tea.Model
|
|
activityStorage *storage.ActivityStorage
|
|
garminClient *garmin.Client
|
|
logger garmin.Logger
|
|
activityList *screens.ActivityList // Persistent activity list
|
|
width int // Track window width
|
|
height int // Track window height
|
|
screenStack []tea.Model // Screen navigation stack
|
|
}
|
|
|
|
func NewApp(activityStorage *storage.ActivityStorage, garminClient *garmin.Client, logger garmin.Logger, config *config.Config) *App {
|
|
if logger == nil {
|
|
logger = &garmin.NoopLogger{}
|
|
}
|
|
|
|
// Initialize with a placeholder screen - actual size will be set by WindowSizeMsg
|
|
activityList := screens.NewActivityList(activityStorage, garminClient, config)
|
|
|
|
app := &App{
|
|
currentModel: activityList,
|
|
activityStorage: activityStorage,
|
|
garminClient: garminClient,
|
|
logger: logger,
|
|
activityList: activityList, // Store persistent reference
|
|
screenStack: []tea.Model{activityList},
|
|
width: 80, // Default width
|
|
height: 24, // Default height
|
|
}
|
|
return app
|
|
}
|
|
|
|
func (a *App) Init() tea.Cmd {
|
|
return a.currentModel.Init()
|
|
}
|
|
|
|
func (a *App) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
switch msg := msg.(type) {
|
|
case tea.WindowSizeMsg:
|
|
// Only update if size actually changed
|
|
if a.width != msg.Width || a.height != msg.Height {
|
|
a.width = msg.Width
|
|
a.height = msg.Height
|
|
updatedModel, cmd := a.currentModel.Update(msg)
|
|
a.currentModel = updatedModel
|
|
a.updateStackTop(updatedModel)
|
|
return a, cmd
|
|
}
|
|
return a, nil
|
|
|
|
case tea.KeyMsg:
|
|
switch msg.String() {
|
|
case "ctrl+c":
|
|
// Force quit on Ctrl+C
|
|
return a, tea.Quit
|
|
case "q":
|
|
// Handle quit - go back in stack or quit if at root
|
|
if len(a.screenStack) <= 1 {
|
|
// At root level, quit
|
|
if _, ok := a.currentModel.(*screens.ActivityList); ok {
|
|
return a, tea.Quit
|
|
}
|
|
} else {
|
|
// Go back to previous screen
|
|
return a, a.goBack()
|
|
}
|
|
}
|
|
|
|
case screens.ActivitySelectedMsg:
|
|
a.logger.Debugf("App.Update() - Received ActivitySelectedMsg for: %s", msg.Activity.Name)
|
|
// Create new activity detail screen
|
|
detail := screens.NewActivityDetail(msg.Activity, "", msg.Config, a.logger)
|
|
a.pushScreen(detail)
|
|
return a, detail.Init()
|
|
|
|
case screens.BackToListMsg:
|
|
a.logger.Debugf("App.Update() - Received BackToListMsg")
|
|
return a, a.goBack()
|
|
}
|
|
|
|
// Delegate to the current model
|
|
updatedModel, cmd := a.currentModel.Update(msg)
|
|
a.currentModel = updatedModel
|
|
a.updateStackTop(updatedModel)
|
|
|
|
return a, cmd
|
|
}
|
|
|
|
func (a *App) View() string {
|
|
return a.currentModel.View()
|
|
}
|
|
|
|
func (a *App) Run() error {
|
|
// Use alt screen for better TUI experience
|
|
p := tea.NewProgram(a, tea.WithAltScreen())
|
|
if _, err := p.Run(); err != nil {
|
|
return fmt.Errorf("failed to run application: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// pushScreen adds a new screen to the stack and makes it current
|
|
func (a *App) pushScreen(model tea.Model) {
|
|
a.screenStack = append(a.screenStack, model)
|
|
a.currentModel = model
|
|
}
|
|
|
|
// goBack removes the current screen from stack and returns to previous
|
|
func (a *App) goBack() tea.Cmd {
|
|
if len(a.screenStack) <= 1 {
|
|
// Already at root, can't go back further
|
|
return nil
|
|
}
|
|
|
|
// Remove current screen
|
|
a.screenStack = a.screenStack[:len(a.screenStack)-1]
|
|
|
|
// Set previous screen as current
|
|
a.currentModel = a.screenStack[len(a.screenStack)-1]
|
|
|
|
// Update the model with current window size
|
|
var cmd tea.Cmd
|
|
a.currentModel, cmd = a.currentModel.Update(tea.WindowSizeMsg{Width: a.width, Height: a.height})
|
|
a.updateStackTop(a.currentModel)
|
|
return cmd
|
|
}
|
|
|
|
// updateStackTop updates the top of the stack with the current model
|
|
func (a *App) updateStackTop(model tea.Model) {
|
|
if len(a.screenStack) > 0 {
|
|
a.screenStack[len(a.screenStack)-1] = model
|
|
}
|
|
|
|
// Update activity list reference if needed
|
|
if activityList, ok := model.(*screens.ActivityList); ok {
|
|
a.activityList = activityList
|
|
}
|
|
}
|