refactor: ui refactor step 2

This commit is contained in:
Patryk Hegenberg 2025-06-27 19:55:34 +02:00
parent 9cae00d2a5
commit 24430d0fae
9 changed files with 167 additions and 121 deletions

View file

@ -34,36 +34,36 @@ func main() {
trainingService := services.NewTrainingService(dbService, settingsService, apiService) trainingService := services.NewTrainingService(dbService, settingsService, apiService)
// 3. UI-Bildschirme erstellen // 3. UI-Bildschirme erstellen
// Wir erstellen einen Container, der die Bildschirme verwaltet (ähnlich einem Stack)
contentContainer := container.NewMax() contentContainer := container.NewMax()
// Navigationsfunktion definieren, die wir an die Screens weitergeben können
var navigateTo func(string)
// Die Bildschirme erstellen und dem Container hinzufügen // Die Bildschirme erstellen und dem Container hinzufügen
homeScreen := ui.MakeHomeScreen(trainingService, contentContainer, mainWindow) homeScreen := ui.MakeHomeScreen(trainingService, func() { navigateTo("training") })
trainingScreen := ui.MakeTrainingScreen(trainingService, settingsService, mainWindow) trainingScreen := ui.MakeTrainingScreen(trainingService, settingsService, mainWindow)
historyScreen := ui.MakeHistoryScreen(dbService, mainWindow) historyScreen := ui.MakeHistoryScreen(dbService, mainWindow)
settingsScreen := ui.MakeSettingsScreen(settingsService, mainWindow) settingsScreen := ui.MakeSettingsScreen(settingsService, mainWindow)
contentContainer.Add(homeScreen) screens := map[string]fyne.CanvasObject{
contentContainer.Add(trainingScreen)
contentContainer.Add(historyScreen)
contentContainer.Add(settingsScreen)
// Initial nur den Home-Bildschirm anzeigen
homeScreen.Show()
trainingScreen.Hide()
historyScreen.Hide()
settingsScreen.Hide()
// 4. Benutzerdefinierte Navigationsleiste erstellen
navBar := ui.MakeNavBar(map[string]fyne.CanvasObject{
"home": homeScreen, "home": homeScreen,
"training": trainingScreen, "training": trainingScreen,
"history": historyScreen, "history": historyScreen,
"settings": settingsScreen, "settings": settingsScreen,
}, contentContainer) }
for _, s := range screens {
contentContainer.Add(s)
}
// 4. Benutzerdefinierte Navigationsleiste erstellen
navBar, navigateFunc := ui.MakeNavBar(screens, contentContainer)
navigateTo = navigateFunc // Weisen der Navigationsfunktion die Implementierung aus der Navbar zu
// Initial den Home-Bildschirm anzeigen
navigateTo("home")
// 5. Hauptlayout mit Border-Layout erstellen // 5. Hauptlayout mit Border-Layout erstellen
// Der Inhalt ist im Zentrum, die Navigationsleiste am unteren Rand.
mainLayout := container.NewBorder(nil, navBar, nil, nil, contentContainer) mainLayout := container.NewBorder(nil, navBar, nil, nil, contentContainer)
mainWindow.SetContent(mainLayout) mainWindow.SetContent(mainLayout)

View file

@ -5,6 +5,7 @@ import (
"log" "log"
"git.patanix.de/git/kettlebell-app/internal/data" "git.patanix.de/git/kettlebell-app/internal/data"
"git.patanix.de/git/kettlebell-app/internal/ui/utils"
"fyne.io/fyne/v2" "fyne.io/fyne/v2"
"fyne.io/fyne/v2/container" "fyne.io/fyne/v2/container"
@ -13,55 +14,42 @@ import (
"fyne.io/fyne/v2/widget" "fyne.io/fyne/v2/widget"
) )
func formatDuration(totalSeconds int64) string {
mins := totalSeconds / 60
secs := totalSeconds % 60
return fmt.Sprintf("%02d:%02d", mins, secs)
}
// MakeHistoryScreen erstellt den Bildschirm für die Trainingshistorie.
func MakeHistoryScreen(db *data.DatabaseService, parent fyne.Window) fyne.CanvasObject { func MakeHistoryScreen(db *data.DatabaseService, parent fyne.Window) fyne.CanvasObject {
var history []data.TrainingSession var history []data.TrainingSession
// Platzhalter, wenn die Liste leer ist
placeholder := widget.NewLabel("Noch keine Trainingsdaten vorhanden.") placeholder := widget.NewLabel("Noch keine Trainingsdaten vorhanden.")
placeholder.Alignment = fyne.TextAlignCenter
list := widget.NewList( list := widget.NewList(
func() int { func() int {
return len(history) return len(history)
}, },
func() fyne.CanvasObject { func() fyne.CanvasObject {
// Template für einen Listeneintrag
return widget.NewCard("", "", container.NewVBox( return widget.NewCard("", "", container.NewVBox(
widget.NewLabel(""), // Datum widget.NewLabel(""),
widget.NewSeparator(), widget.NewSeparator(),
widget.NewLabel(""), // Sätze container.NewGridWithColumns(3,
widget.NewLabel(""), // Gewicht container.NewVBox(widget.NewLabel("Sätze"), widget.NewLabelWithStyle("", fyne.TextAlignCenter, fyne.TextStyle{Bold: true})),
widget.NewLabel(""), // Reps container.NewVBox(widget.NewLabel("Dauer"), widget.NewLabelWithStyle("", fyne.TextAlignCenter, fyne.TextStyle{Bold: true})),
widget.NewLabel(""), // Dauer container.NewVBox(widget.NewLabel("Reps/Satz"), widget.NewLabelWithStyle("", fyne.TextAlignCenter, fyne.TextStyle{Bold: true})),
),
)) ))
}, },
func(i widget.ListItemID, o fyne.CanvasObject) { func(i widget.ListItemID, o fyne.CanvasObject) {
// Template mit Daten füllen
session := history[i] session := history[i]
card := o.(*widget.Card) card := o.(*widget.Card)
// Datum als Titel der Karte
card.SetTitle(session.Date.Format("02.01.2006 15:04")) card.SetTitle(session.Date.Format("02.01.2006 15:04"))
// Details im Inhalt der Karte content := card.Content.(*fyne.Container)
box := card.Content.(*fyne.Container) programLabel := content.Objects[0].(*widget.Label)
labels := box.Objects statsGrid := content.Objects[2].(*fyne.Container)
labels[0].(*widget.Label).SetText(fmt.Sprintf("Programm: %s - Tag %d", session.Program, session.BlockDay))
labels[2].(*widget.Label).SetText(fmt.Sprintf("Sätze: %d", session.Sets)) programLabel.SetText(fmt.Sprintf("%s - Tag %d", session.Program, session.BlockDay))
labels[3].(*widget.Label).SetText(fmt.Sprintf("Kettlebells: %.1fkg / %.1fkg", session.WeightLeft, session.WeightRight))
labels[4].(*widget.Label).SetText(fmt.Sprintf("Reps pro Satz: %d", session.RepsPerSet)) statsGrid.Objects[0].(*fyne.Container).Objects[1].(*widget.Label).SetText(fmt.Sprintf("%d", session.Sets))
labels[5].(*widget.Label).SetText(fmt.Sprintf("Dauer: %s", formatDuration(session.Duration))) statsGrid.Objects[1].(*fyne.Container).Objects[1].(*widget.Label).SetText(utils.FormatDuration(session.Duration))
statsGrid.Objects[2].(*fyne.Container).Objects[1].(*widget.Label).SetText(fmt.Sprintf("%d", session.RepsPerSet))
}, },
) )
// Funktion zum Neuladen der Daten
refreshData := func() { refreshData := func() {
var err error var err error
history, err = db.GetHistory() history, err = db.GetHistory()
@ -69,24 +57,31 @@ func MakeHistoryScreen(db *data.DatabaseService, parent fyne.Window) fyne.Canvas
log.Printf("Fehler beim Laden der Historie: %v", err) log.Printf("Fehler beim Laden der Historie: %v", err)
dialog.ShowError(err, parent) dialog.ShowError(err, parent)
} }
if len(history) == 0 { if len(history) == 0 {
placeholder.Show()
list.Hide() list.Hide()
placeholder.Show()
} else { } else {
placeholder.Hide()
list.Show() list.Show()
placeholder.Hide()
} }
list.Refresh() list.Refresh()
} }
// Initiales Laden list.OnSelected = func(id widget.ListItemID) {
refreshData() list.Unselect(id)
}
// Toolbar mit Refresh-Button
toolbar := widget.NewToolbar( toolbar := widget.NewToolbar(
widget.NewToolbarAction(theme.ViewRefreshIcon(), refreshData), widget.NewToolbarAction(theme.ViewRefreshIcon(), refreshData),
) )
return container.NewBorder(toolbar, nil, nil, nil, container.NewStack(list, placeholder)) layout := container.NewBorder(toolbar, nil, nil, nil, container.NewStack(list, container.NewCenter(placeholder)))
wrapper := container.NewMax(layout)
if wrapper.Visible() {
refreshData()
}
// wrapper.OnVisible = refreshData
return wrapper
} }

View file

@ -4,54 +4,51 @@ import (
"fmt" "fmt"
"git.patanix.de/git/kettlebell-app/internal/services" "git.patanix.de/git/kettlebell-app/internal/services"
"git.patanix.de/git/kettlebell-app/internal/ui/theme"
"fyne.io/fyne/v2" "fyne.io/fyne/v2"
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/container" "fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/widget" "fyne.io/fyne/v2/widget"
) )
func MakeHomeScreen(ts *services.TrainingService, content *fyne.Container, parent fyne.Window) fyne.CanvasObject { func MakeHomeScreen(ts *services.TrainingService, onStart func()) fyne.CanvasObject {
// Header // Header
headerTitle := canvas.NewText("Patanix", theme.ColorSlate200)
headerTitle.TextSize = 28
headerTitle.TextStyle.Bold = true
header := container.NewVBox( header := container.NewVBox(
widget.NewLabel("Hallo,"), widget.NewLabel("Hallo,"),
widget.NewLabelWithStyle("Patanix", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}), //, Size: 24}), headerTitle,
) )
// Nächstes Training CTA // Nächstes Training CTA
state := ts.State state := ts.State
// Verwende ein Card-Widget für das Styling
nextTrainingCard := widget.NewCard( nextTrainingCard := widget.NewCard(
"Nächstes Training", "Nächstes Training",
fmt.Sprintf("%s - Tag %d", state.CurrentProgram, state.CurrentBlockDay), fmt.Sprintf("%s - Tag %d", state.CurrentProgram, state.CurrentBlockDay),
container.NewVBox( container.NewVBox(
widget.NewLabel(fmt.Sprintf("Ziel: %d Wiederholungen pro Satz", state.CurrentReps)), widget.NewLabel(fmt.Sprintf("Ziel: %d Wiederholungen pro Satz", state.CurrentReps)),
widget.NewButton("Training starten", func() { widget.NewButton("Training starten", onStart),
// Wechsle zum Trainingsbildschirm
for _, child := range content.Objects {
if _, ok := child.(*fyne.Container).Objects[0].(*widget.Card); ok {
// Heuristik, um den Trainings-Screen zu finden
child.Show()
} else {
child.Hide()
}
}
content.Refresh()
}),
), ),
) )
// Letzte Leistung // Letzte Leistung
// HINWEIS: Diese Daten müssten aus dem dbService geladen werden.
// Hier verwenden wir vorerst Platzhalter.
statsCard := widget.NewCard("Letzte Leistung", "", container.NewGridWithColumns(3, statsCard := widget.NewCard("Letzte Leistung", "", container.NewGridWithColumns(3,
container.NewVBox(widget.NewLabel("Sätze"), widget.NewLabelWithStyle("8", fyne.TextAlignCenter, fyne.TextStyle{Bold: true})), container.NewVBox(widget.NewLabel("Sätze"), widget.NewLabelWithStyle("8", fyne.TextAlignCenter, fyne.TextStyle{Bold: true})),
container.NewVBox(widget.NewLabel("Dauer"), widget.NewLabelWithStyle("18:45", fyne.TextAlignCenter, fyne.TextStyle{Bold: true})), container.NewVBox(widget.NewLabel("Dauer"), widget.NewLabelWithStyle("18:45", fyne.TextAlignCenter, fyne.TextStyle{Bold: true})),
container.NewVBox(widget.NewLabel("Gewicht"), widget.NewLabelWithStyle("16kg", fyne.TextAlignCenter, fyne.TextStyle{Bold: true})), container.NewVBox(widget.NewLabel("Gewicht"), widget.NewLabelWithStyle("16kg", fyne.TextAlignCenter, fyne.TextStyle{Bold: true})),
)) ))
return container.NewVBox( layout := container.NewVBox(
header, header,
widget.NewSeparator(), widget.NewSeparator(),
nextTrainingCard, nextTrainingCard,
statsCard, statsCard,
) )
return container.NewPadded(layout)
} }

View file

@ -8,38 +8,38 @@ import (
"fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/theme"
) )
// MakeNavBar erstellt die benutzerdefinierte Navigationsleiste am unteren Rand. // MakeNavBar erstellt die benutzerdefinierte Navigationsleiste und gibt die Navigationsfunktion zurück.
func MakeNavBar(screens map[string]fyne.CanvasObject, content *fyne.Container) fyne.CanvasObject { func MakeNavBar(screens map[string]fyne.CanvasObject, content *fyne.Container) (fyne.CanvasObject, func(string)) {
buttons := make(map[string]*components.NavButton) buttons := make(map[string]*components.NavButton)
// Funktion zum Umschalten der Ansichten // Funktion zum Umschalten der Ansichten
navigateTo := func(name string) { navigateTo := func(name string) {
for key, screen := range screens { for key, screen := range screens {
screen.Hide()
if key == name { if key == name {
screen.Show() screen.Show()
} else {
screen.Hide()
} }
} }
for key, button := range buttons { for key, button := range buttons {
button.SetActive(false)
if key == name { if key == name {
button.SetActive(true) button.SetActive(true)
} else {
button.SetActive(false)
} }
} }
content.Refresh() content.Refresh()
} }
buttons["home"] = components.NewNavButton("Home", theme.HomeIcon(), true, func() { navigateTo("home") }) buttons["home"] = components.NewNavButton("Home", theme.HomeIcon(), false, func() { navigateTo("home") })
buttons["training"] = components.NewNavButton("Training", theme.MediaPlayIcon(), false, func() { navigateTo("training") }) buttons["training"] = components.NewNavButton("Training", theme.MediaPlayIcon(), false, func() { navigateTo("training") })
buttons["history"] = components.NewNavButton("Historie", theme.ListIcon(), false, func() { navigateTo("history") }) buttons["history"] = components.NewNavButton("Historie", theme.ListIcon(), false, func() { navigateTo("history") })
buttons["settings"] = components.NewNavButton("Einstellungen", theme.SettingsIcon(), false, func() { navigateTo("settings") }) buttons["settings"] = components.NewNavButton("Einstellungen", theme.SettingsIcon(), false, func() { navigateTo("settings") })
return container.NewGridWithColumns(4, navContainer := container.NewGridWithColumns(4,
buttons["home"], buttons["home"],
buttons["training"], buttons["training"],
buttons["history"], buttons["history"],
buttons["settings"], buttons["settings"],
) )
return navContainer, navigateTo
} }

View file

@ -8,48 +8,57 @@ import (
"fyne.io/fyne/v2" "fyne.io/fyne/v2"
"fyne.io/fyne/v2/container" "fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/dialog"
"fyne.io/fyne/v2/widget" "fyne.io/fyne/v2/widget"
) )
func MakeSettingsScreen(settingsService *services.SettingsService, parent fyne.Window) fyne.CanvasObject { func MakeSettingsScreen(settingsService *services.SettingsService, parent fyne.Window) fyne.CanvasObject {
currentSettings := settingsService.LoadSettings() currentSettings := settingsService.LoadSettings()
trainingTimeEntry := widget.NewEntry() timeEntry := widget.NewEntry()
trainingTimeEntry.SetText(fmt.Sprintf("%d", currentSettings.TrainingTimeMinutes)) timeEntry.SetText(fmt.Sprintf("%d", currentSettings.TrainingTimeMinutes))
timeEntry.Validator = func(s string) error {
if _, err := strconv.Atoi(s); err != nil {
return fmt.Errorf("muss eine Zahl sein")
}
return nil
}
setsEntry := widget.NewEntry()
setsEntry.SetText(fmt.Sprintf("%d", currentSettings.GoalSets))
setsEntry.Validator = timeEntry.Validator // Gleicher Validator
weightLeftEntry := widget.NewEntry() weightLeftEntry := widget.NewEntry()
weightLeftEntry.SetText(fmt.Sprintf("%.1f", currentSettings.WeightLeft)) weightLeftEntry.SetText(fmt.Sprintf("%.1f", currentSettings.WeightLeft))
weightLeftEntry.Validator = func(s string) error {
if _, err := strconv.ParseFloat(s, 64); err != nil {
return fmt.Errorf("muss eine Zahl sein")
}
return nil
}
weightRightEntry := widget.NewEntry() weightRightEntry := widget.NewEntry()
weightRightEntry.SetText(fmt.Sprintf("%.1f", currentSettings.WeightRight)) weightRightEntry.SetText(fmt.Sprintf("%.1f", currentSettings.WeightRight))
weightRightEntry.Validator = weightLeftEntry.Validator // Gleicher Validator
goalSetsEntry := widget.NewEntry()
goalSetsEntry.SetText(fmt.Sprintf("%d", currentSettings.GoalSets))
form := &widget.Form{ form := &widget.Form{
Items: []*widget.FormItem{ Items: []*widget.FormItem{
{Text: "Trainingszeit (Minuten)", Widget: trainingTimeEntry}, {Text: "Trainingszeit (Minuten)", Widget: timeEntry},
{Text: "Linke Kettlebell (kg)", Widget: weightLeftEntry}, {Text: "Ziel-Sätze", Widget: setsEntry},
{Text: "Rechte Kettlebell (kg)", Widget: weightRightEntry}, {Text: "Links (kg)", Widget: weightLeftEntry},
{Text: "Ziel-Sätze", Widget: goalSetsEntry}, {Text: "Rechts (kg)", Widget: weightRightEntry},
}, },
OnSubmit: func() { OnSubmit: func() {
timeMin, err1 := strconv.Atoi(trainingTimeEntry.Text) timeMin, _ := strconv.Atoi(timeEntry.Text)
weightL, err2 := strconv.ParseFloat(weightLeftEntry.Text, 64) goal, _ := strconv.Atoi(setsEntry.Text)
weightR, err3 := strconv.ParseFloat(weightRightEntry.Text, 64) weightL, _ := strconv.ParseFloat(weightLeftEntry.Text, 64)
goal, err4 := strconv.Atoi(goalSetsEntry.Text) weightR, _ := strconv.ParseFloat(weightRightEntry.Text, 64)
if err1 != nil || err2 != nil || err3 != nil || err4 != nil {
dialog.ShowError(fmt.Errorf("Bitte gib gültige Zahlen ein"), parent)
return
}
newSettings := &services.Settings{ newSettings := &services.Settings{
TrainingTimeMinutes: timeMin, TrainingTimeMinutes: timeMin,
GoalSets: goal,
WeightLeft: weightL, WeightLeft: weightL,
WeightRight: weightR, WeightRight: weightR,
GoalSets: goal, InitialProgram: currentSettings.InitialProgram, // Beibehalten
} }
settingsService.SaveSettings(newSettings) settingsService.SaveSettings(newSettings)
@ -60,5 +69,14 @@ func MakeSettingsScreen(settingsService *services.SettingsService, parent fyne.W
}, },
} }
return container.NewPadded(form) title := widget.NewLabelWithStyle("Einstellungen", fyne.TextAlignLeading, fyne.TextStyle{Bold: true})
title.TextStyle.Monospace = true // Workaround to force refresh on size change
layout := container.NewVBox(
title,
widget.NewSeparator(),
form,
)
return container.NewPadded(layout)
} }

View file

@ -11,6 +11,7 @@ import (
var ( var (
ColorSlate900 = color.NRGBA{R: 0x0f, G: 0x17, B: 0x2a, A: 0xff} // bg-slate-900 ColorSlate900 = color.NRGBA{R: 0x0f, G: 0x17, B: 0x2a, A: 0xff} // bg-slate-900
ColorSlate800 = color.NRGBA{R: 0x1e, G: 0x29, B: 0x3b, A: 0xff} // bg-slate-800 ColorSlate800 = color.NRGBA{R: 0x1e, G: 0x29, B: 0x3b, A: 0xff} // bg-slate-800
ColorSlate700 = color.NRGBA{R: 0x33, G: 0x41, B: 0x55, A: 0xff} // bg-slate-700
ColorSlate400 = color.NRGBA{R: 0x94, G: 0xa3, B: 0xb8, A: 0xff} // text-slate-400 ColorSlate400 = color.NRGBA{R: 0x94, G: 0xa3, B: 0xb8, A: 0xff} // text-slate-400
ColorSlate200 = color.NRGBA{R: 0xe2, G: 0xe8, B: 0xf0, A: 0xff} // text-slate-200 ColorSlate200 = color.NRGBA{R: 0xe2, G: 0xe8, B: 0xf0, A: 0xff} // text-slate-200
ColorSky500 = color.NRGBA{R: 0x0e, G: 0xa5, B: 0xe9, A: 0xff} // bg-sky-500 ColorSky500 = color.NRGBA{R: 0x0e, G: 0xa5, B: 0xe9, A: 0xff} // bg-sky-500
@ -28,13 +29,13 @@ func (t *KettlebellTheme) Color(name fyne.ThemeColorName, variant fyne.ThemeVari
case theme.ColorNameButton: case theme.ColorNameButton:
return ColorSlate800 return ColorSlate800
case theme.ColorNameDisabledButton: case theme.ColorNameDisabledButton:
return ColorSlate800 return ColorSlate700
case theme.ColorNamePrimary: case theme.ColorNamePrimary:
return ColorSky500 return ColorSky500
case theme.ColorNamePlaceHolder: case theme.ColorNamePlaceHolder:
return ColorSlate400 return ColorSlate400
case theme.ColorNameHover: case theme.ColorNameHover:
return color.NRGBA{R: 0x33, G: 0x41, B: 0x55, A: 0xff} // Slate 700 return ColorSlate700
case theme.ColorNameForeground: case theme.ColorNameForeground:
return ColorSlate200 return ColorSlate200
case theme.ColorNameDisabled: case theme.ColorNameDisabled:
@ -44,20 +45,17 @@ func (t *KettlebellTheme) Color(name fyne.ThemeColorName, variant fyne.ThemeVari
case theme.ColorNameInputBackground: case theme.ColorNameInputBackground:
return ColorSlate800 return ColorSlate800
case theme.ColorNameSeparator: case theme.ColorNameSeparator:
return color.NRGBA{R: 0x33, G: 0x41, B: 0x55, A: 0xff} // Slate 700 return ColorSlate700
default: default:
return theme.DefaultTheme().Color(name, variant) return theme.DefaultTheme().Color(name, variant)
} }
} }
func (t *KettlebellTheme) Icon(name fyne.ThemeIconName) fyne.Resource { func (t *KettlebellTheme) Icon(name fyne.ThemeIconName) fyne.Resource {
// Hier könnten wir benutzerdefinierte Icons laden, wenn nötig.
// Vorerst verwenden wir die Standard-Icons.
return theme.DefaultTheme().Icon(name) return theme.DefaultTheme().Icon(name)
} }
func (t *KettlebellTheme) Font(style fyne.TextStyle) fyne.Resource { func (t *KettlebellTheme) Font(style fyne.TextStyle) fyne.Resource {
// Hier könnten wir eine benutzerdefinierte Schriftart wie 'Inter' laden.
return theme.DefaultTheme().Font(style) return theme.DefaultTheme().Font(style)
} }
@ -68,7 +66,9 @@ func (t *KettlebellTheme) Size(name fyne.ThemeSizeName) float32 {
case theme.SizeNameText: case theme.SizeNameText:
return 16 return 16
case theme.SizeNameHeadingText: case theme.SizeNameHeadingText:
return 28 return 24
case theme.SizeNameSubHeadingText:
return 20
default: default:
return theme.DefaultTheme().Size(name) return theme.DefaultTheme().Size(name)
} }

View file

@ -6,38 +6,56 @@ import (
"git.patanix.de/git/kettlebell-app/internal/data" "git.patanix.de/git/kettlebell-app/internal/data"
"git.patanix.de/git/kettlebell-app/internal/services" "git.patanix.de/git/kettlebell-app/internal/services"
"git.patanix.de/git/kettlebell-app/internal/ui/theme"
"git.patanix.de/git/kettlebell-app/internal/ui/utils"
"fyne.io/fyne/v2" "fyne.io/fyne/v2"
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/container" "fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/widget" "fyne.io/fyne/v2/widget"
) )
func MakeTrainingScreen(ts *services.TrainingService, ss *services.SettingsService, parent fyne.Window) fyne.CanvasObject { func MakeTrainingScreen(ts *services.TrainingService, ss *services.SettingsService, parent fyne.Window) fyne.CanvasObject {
// UI-Elemente // UI-Elemente mit canvas.Text für die Größensteuerung
timerLabel := widget.NewLabelWithStyle("20:00", fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) timerLabel := canvas.NewText("20:00", theme.ColorSlate200)
// timerLabel.TextSize = 60 timerLabel.TextSize = 60
timerLabel.TextStyle.Bold = true
timerLabel.Alignment = fyne.TextAlignCenter
setsLabel := widget.NewLabelWithStyle("0 / 8", fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) setsLabel := canvas.NewText("0 / 8", theme.ColorSky400)
// setsLabel.TextSize = 48 setsLabel.TextSize = 48
// setsLabel.Color = theme.ColorSky400 setsLabel.TextStyle.Bold = true
setsLabel.Alignment = fyne.TextAlignCenter
repsLabel := widget.NewLabelWithStyle("5 Wiederholungen", fyne.TextAlignCenter, fyne.TextStyle{}) repsLabel := canvas.NewText("5 Wiederholungen", theme.ColorSlate200)
// repsLabel.TextSize = 20 repsLabel.TextSize = 20
repsLabel.Alignment = fyne.TextAlignCenter
var mainTimer *time.Ticker var mainTimer *time.Ticker
var startButton *widget.Button // Start-Button deklarieren
updateUI := func() { updateUI := func() {
state := ts.State state := ts.State
timerLabel.SetText(formatDuration(int64(state.RemainingSeconds))) timerLabel.Text = utils.FormatDuration(int64(state.RemainingSeconds))
setsLabel.SetText(fmt.Sprintf("%d / %d", state.SetsDone, state.GoalSets)) setsLabel.Text = fmt.Sprintf("%d / %d", state.SetsDone, state.GoalSets)
repsLabel.SetText(fmt.Sprintf("%d Wiederholungen", state.RepsPerSet)) repsLabel.Text = fmt.Sprintf("%d Wiederholungen", state.RepsPerSet)
timerLabel.Refresh()
setsLabel.Refresh()
repsLabel.Refresh()
if state.IsTrainingRunning {
startButton.Disable()
} else {
startButton.Enable()
}
} }
finishAction := func() { finishAction := func() {
if mainTimer != nil { if mainTimer != nil {
mainTimer.Stop() mainTimer.Stop()
mainTimer = nil
} }
// ... (Logik zum Speichern wie zuvor) ...
session := &data.TrainingSession{ session := &data.TrainingSession{
Date: time.Now(), Date: time.Now(),
Sets: int64(ts.State.SetsDone), Sets: int64(ts.State.SetsDone),
@ -58,7 +76,8 @@ func MakeTrainingScreen(ts *services.TrainingService, ss *services.SettingsServi
mainTimer = time.NewTicker(time.Second) mainTimer = time.NewTicker(time.Second)
go func() { go func() {
for range mainTimer.C { for mainTimer != nil {
<-mainTimer.C
if ts.State.RemainingSeconds <= 0 { if ts.State.RemainingSeconds <= 0 {
finishAction() finishAction()
return return
@ -70,10 +89,16 @@ func MakeTrainingScreen(ts *services.TrainingService, ss *services.SettingsServi
} }
setAction := func() { setAction := func() {
if !ts.State.IsTrainingRunning {
// Starte das Training, wenn es noch nicht läuft
startAction()
}
ts.CompleteSet() ts.CompleteSet()
updateUI() updateUI()
} }
startButton = widget.NewButton("Training beginnen", startAction)
// Layout im "Cockpit"-Stil // Layout im "Cockpit"-Stil
topPart := container.NewVBox( topPart := container.NewVBox(
widget.NewLabelWithStyle("Verbleibende Zeit", fyne.TextAlignCenter, fyne.TextStyle{}), widget.NewLabelWithStyle("Verbleibende Zeit", fyne.TextAlignCenter, fyne.TextStyle{}),
@ -87,15 +112,13 @@ func MakeTrainingScreen(ts *services.TrainingService, ss *services.SettingsServi
) )
finishButton := widget.NewButton("Training beenden", finishAction) finishButton := widget.NewButton("Training beenden", finishAction)
// Wir simulieren den roten Button durch die Error-Farbe des Themes
finishButton.Importance = widget.HighImportance finishButton.Importance = widget.HighImportance
bottomPart := container.NewVBox( bottomPart := container.NewVBox(
startButton, // Start-Button hinzugefügt
widget.NewButton("Satz abschließen", setAction), widget.NewButton("Satz abschließen", setAction),
finishButton, finishButton,
) )
// HINWEIS: Start-Button fehlt hier, da der Flow vom Home-Screen ausgeht.
// Man könnte ihn bei Bedarf hinzufügen.
return container.NewBorder(topPart, bottomPart, nil, nil, container.NewCenter(middlePart)) return container.NewBorder(topPart, bottomPart, nil, nil, container.NewCenter(middlePart))
} }

View file

@ -0,0 +1,13 @@
package utils
import "fmt"
// FormatDuration wandelt Sekunden in einen MM:SS String um.
func FormatDuration(totalSeconds int64) string {
if totalSeconds < 0 {
totalSeconds = 0
}
mins := totalSeconds / 60
secs := totalSeconds % 60
return fmt.Sprintf("%02d:%02d", mins, secs)
}