mirror of
https://github.com/sstent/aicyclingcoach-go.git
synced 2026-01-25 16:41:48 +00:00
sync
This commit is contained in:
Binary file not shown.
@@ -14,6 +14,9 @@ type App struct {
|
||||
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
|
||||
}
|
||||
|
||||
func NewApp(activityStorage *storage.ActivityStorage, garminClient *garmin.Client, logger garmin.Logger) *App {
|
||||
@@ -29,6 +32,7 @@ func NewApp(activityStorage *storage.ActivityStorage, garminClient *garmin.Clien
|
||||
activityStorage: activityStorage,
|
||||
garminClient: garminClient,
|
||||
logger: logger,
|
||||
activityList: activityList, // Store persistent reference
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,14 +43,19 @@ func (a *App) Init() tea.Cmd {
|
||||
func (a *App) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
switch msg := msg.(type) {
|
||||
case tea.WindowSizeMsg:
|
||||
// Forward window size to current model
|
||||
// Store window size and forward to current model
|
||||
a.width = msg.Width
|
||||
a.height = msg.Height
|
||||
updatedModel, cmd := a.currentModel.Update(msg)
|
||||
a.currentModel = updatedModel
|
||||
return a, cmd
|
||||
case tea.KeyMsg:
|
||||
switch msg.String() {
|
||||
case "ctrl+c", "q":
|
||||
return a, tea.Quit
|
||||
// Only quit if we're at the top level (activity list)
|
||||
if _, ok := a.currentModel.(*screens.ActivityList); ok {
|
||||
return a, tea.Quit
|
||||
}
|
||||
}
|
||||
case screens.ActivitySelectedMsg:
|
||||
a.logger.Debugf("App.Update() - Received ActivitySelectedMsg for: %s", msg.Activity.Name)
|
||||
@@ -56,15 +65,23 @@ func (a *App) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
return a, detail.Init()
|
||||
case screens.BackToListMsg:
|
||||
a.logger.Debugf("App.Update() - Received BackToListMsg")
|
||||
// Re-initialize the activity list when navigating back
|
||||
activityList := screens.NewActivityList(a.activityStorage, a.garminClient)
|
||||
a.currentModel = activityList
|
||||
return a, activityList.Init()
|
||||
// Return to existing activity list instead of creating new
|
||||
a.currentModel = a.activityList
|
||||
// Send current window size to ensure proper rendering
|
||||
return a, func() tea.Msg {
|
||||
return tea.WindowSizeMsg{Width: a.width, Height: a.height}
|
||||
}
|
||||
}
|
||||
|
||||
// Delegate to the current model
|
||||
updatedModel, cmd := a.currentModel.Update(msg)
|
||||
a.currentModel = updatedModel
|
||||
|
||||
// Update activity list reference if needed
|
||||
if activityList, ok := updatedModel.(*screens.ActivityList); ok {
|
||||
a.activityList = activityList
|
||||
}
|
||||
|
||||
return a, cmd
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ type ActivityDetail struct {
|
||||
hrChart *components.Chart
|
||||
elevationChart *components.Chart
|
||||
logger garmin.Logger
|
||||
ready bool // Tracks if viewport has been initialized
|
||||
}
|
||||
|
||||
type Styles struct {
|
||||
@@ -46,11 +47,11 @@ func NewActivityDetail(activity *models.Activity, analysis string, logger garmin
|
||||
Subtitle: lipgloss.NewStyle().Foreground(lipgloss.Color("8")).MarginTop(1),
|
||||
StatName: lipgloss.NewStyle().Foreground(lipgloss.Color("7")),
|
||||
StatValue: lipgloss.NewStyle().Foreground(lipgloss.Color("15")),
|
||||
Analysis: lipgloss.NewStyle().MarginTop(2),
|
||||
Viewport: lipgloss.NewStyle().Padding(0, 2),
|
||||
Analysis: lipgloss.NewStyle().MarginTop(1),
|
||||
Viewport: lipgloss.NewStyle().Padding(0, 1),
|
||||
}
|
||||
|
||||
vp := viewport.New(0, 0)
|
||||
vp := viewport.New(80, 20) // Default dimensions
|
||||
ad := &ActivityDetail{
|
||||
activity: activity,
|
||||
analysis: analysis,
|
||||
@@ -65,9 +66,10 @@ func NewActivityDetail(activity *models.Activity, analysis string, logger garmin
|
||||
}
|
||||
|
||||
func (m *ActivityDetail) Init() tea.Cmd {
|
||||
// Initialize content immediately instead of waiting for WindowSizeMsg
|
||||
m.setContent()
|
||||
return nil
|
||||
// Request window size to get proper dimensions
|
||||
return tea.Batch(
|
||||
func() tea.Msg { return tea.WindowSizeMsg{Width: 80, Height: 24} },
|
||||
)
|
||||
}
|
||||
|
||||
func (m *ActivityDetail) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
@@ -75,14 +77,12 @@ func (m *ActivityDetail) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
case tea.WindowSizeMsg:
|
||||
m.width = msg.Width
|
||||
m.height = msg.Height
|
||||
m.viewport = viewport.New(msg.Width, msg.Height-4)
|
||||
chartWidth := msg.Width/2 - 4
|
||||
m.hrChart.Width = chartWidth
|
||||
m.elevationChart.Width = chartWidth
|
||||
m.viewport = viewport.New(msg.Width, msg.Height-2)
|
||||
m.ready = true
|
||||
m.setContent()
|
||||
case tea.KeyMsg:
|
||||
switch msg.String() {
|
||||
case "esc":
|
||||
case "esc", "b", "q": // Add 'q' key for quitting/going back
|
||||
return m, func() tea.Msg { return BackToListMsg{} }
|
||||
}
|
||||
}
|
||||
@@ -93,12 +93,18 @@ func (m *ActivityDetail) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
}
|
||||
|
||||
func (m *ActivityDetail) View() string {
|
||||
if !m.ready {
|
||||
return "Loading activity details..."
|
||||
}
|
||||
|
||||
instructions := lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("241")).
|
||||
MarginTop(1).
|
||||
Render("esc back • q quit")
|
||||
|
||||
return fmt.Sprintf("%s\n%s", m.viewport.View(), instructions)
|
||||
return lipgloss.JoinVertical(lipgloss.Left,
|
||||
m.viewport.View(),
|
||||
instructions,
|
||||
)
|
||||
}
|
||||
|
||||
func (m *ActivityDetail) setContent() {
|
||||
@@ -117,8 +123,6 @@ func (m *ActivityDetail) setContent() {
|
||||
m.logger.Debugf("ActivityDetail.setContent() - Metrics: AvgHR=%d, MaxHR=%d, AvgSpeed=%.2f", m.activity.Metrics.AvgHeartRate, m.activity.Metrics.MaxHeartRate, m.activity.Metrics.AvgSpeed)
|
||||
|
||||
// Debug info at top
|
||||
content.WriteString(fmt.Sprintf("DEBUG: Viewport W=%d H=%d, Activity: %s\n", m.width, m.height, m.activity.Name))
|
||||
content.WriteString("\n")
|
||||
|
||||
// Activity Details
|
||||
content.WriteString(m.styles.Title.Render(m.activity.Name))
|
||||
@@ -188,14 +192,22 @@ func (m *ActivityDetail) setContent() {
|
||||
|
||||
// Only show charts if we have data
|
||||
if len(m.activity.Metrics.HeartRateData) > 0 || len(m.activity.Metrics.ElevationData) > 0 {
|
||||
charts := lipgloss.JoinHorizontal(
|
||||
lipgloss.Top,
|
||||
m.hrChart.View(),
|
||||
lipgloss.NewStyle().Width(2).Render(" "),
|
||||
m.elevationChart.View(),
|
||||
)
|
||||
content.WriteString(charts)
|
||||
content.WriteString("\n\n")
|
||||
// Calculate available height for charts (about 1/3 of screen height)
|
||||
chartHeight := max(6, (m.height-20)/3)
|
||||
chartWidth := max(30, m.width-4)
|
||||
|
||||
m.hrChart.Width = chartWidth
|
||||
m.hrChart.Height = chartHeight
|
||||
m.elevationChart.Width = chartWidth
|
||||
m.elevationChart.Height = chartHeight
|
||||
|
||||
// Render charts with spacing
|
||||
if len(m.activity.Metrics.HeartRateData) > 0 {
|
||||
content.WriteString("\n" + m.hrChart.View() + "\n")
|
||||
}
|
||||
if len(m.activity.Metrics.ElevationData) > 0 {
|
||||
content.WriteString("\n" + m.elevationChart.View() + "\n")
|
||||
}
|
||||
}
|
||||
|
||||
// Analysis Section with formatted output
|
||||
@@ -241,5 +253,13 @@ func (m *ActivityDetail) setContent() {
|
||||
}
|
||||
}
|
||||
|
||||
m.viewport.SetContent(m.styles.Viewport.Render(content.String()))
|
||||
m.viewport.SetContent(content.String())
|
||||
}
|
||||
|
||||
// Helper function for max since it's not available in older Go versions
|
||||
func max(a, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user