refactor: new ui step3

This commit is contained in:
Patryk Hegenberg 2025-06-27 20:18:32 +02:00
parent 24430d0fae
commit c15cdea57d
6 changed files with 172 additions and 153 deletions

View file

@ -4,14 +4,13 @@ import (
"fmt"
"log"
"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/container"
"fyne.io/fyne/v2/dialog"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
"git.patanix.de/git/kettlebell-app/internal/data"
"git.patanix.de/git/kettlebell-app/internal/ui/utils"
)
func MakeHistoryScreen(db *data.DatabaseService, parent fyne.Window) fyne.CanvasObject {
@ -19,19 +18,14 @@ func MakeHistoryScreen(db *data.DatabaseService, parent fyne.Window) fyne.Canvas
placeholder := widget.NewLabel("Noch keine Trainingsdaten vorhanden.")
list := widget.NewList(
func() int {
return len(history)
},
func() int { return len(history) },
func() fyne.CanvasObject {
return widget.NewCard("", "", container.NewVBox(
widget.NewLabel(""),
widget.NewSeparator(),
container.NewGridWithColumns(3,
container.NewVBox(widget.NewLabel("Sätze"), widget.NewLabelWithStyle("", fyne.TextAlignCenter, fyne.TextStyle{Bold: true})),
container.NewVBox(widget.NewLabel("Dauer"), widget.NewLabelWithStyle("", fyne.TextAlignCenter, fyne.TextStyle{Bold: true})),
container.NewVBox(widget.NewLabel("Reps/Satz"), widget.NewLabelWithStyle("", fyne.TextAlignCenter, fyne.TextStyle{Bold: true})),
),
))
stats := container.NewGridWithColumns(3,
container.NewVBox(widget.NewLabel("Sätze"), widget.NewLabelWithStyle("", fyne.TextAlignCenter, fyne.TextStyle{Bold: true})),
container.NewVBox(widget.NewLabel("Dauer"), widget.NewLabelWithStyle("", fyne.TextAlignCenter, fyne.TextStyle{Bold: true})),
container.NewVBox(widget.NewLabel("Reps/Satz"), widget.NewLabelWithStyle("", fyne.TextAlignCenter, fyne.TextStyle{Bold: true})),
)
return widget.NewCard("", "", container.NewVBox(widget.NewLabel(""), widget.NewSeparator(), stats))
},
func(i widget.ListItemID, o fyne.CanvasObject) {
session := history[i]
@ -43,7 +37,6 @@ func MakeHistoryScreen(db *data.DatabaseService, parent fyne.Window) fyne.Canvas
statsGrid := content.Objects[2].(*fyne.Container)
programLabel.SetText(fmt.Sprintf("%s - Tag %d", session.Program, session.BlockDay))
statsGrid.Objects[0].(*fyne.Container).Objects[1].(*widget.Label).SetText(fmt.Sprintf("%d", session.Sets))
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))
@ -56,32 +49,25 @@ func MakeHistoryScreen(db *data.DatabaseService, parent fyne.Window) fyne.Canvas
if err != nil {
log.Printf("Fehler beim Laden der Historie: %v", err)
dialog.ShowError(err, parent)
return
}
if len(history) == 0 {
list.Hide()
placeholder.Show()
list.Hide()
} else {
list.Show()
placeholder.Hide()
list.Show()
}
list.Refresh()
}
list.OnSelected = func(id widget.ListItemID) {
list.Unselect(id)
}
toolbar := widget.NewToolbar(widget.NewToolbarAction(theme.ViewRefreshIcon(), refreshData))
content := container.NewStack(list, container.NewCenter(placeholder))
layout := container.NewBorder(toolbar, nil, nil, nil, content)
toolbar := widget.NewToolbar(
widget.NewToolbarAction(theme.ViewRefreshIcon(), refreshData),
)
layout := container.NewBorder(toolbar, nil, nil, nil, container.NewStack(list, container.NewCenter(placeholder)))
wrapper := container.NewMax(layout)
if wrapper.Visible() {
// Daten laden, wenn der Container sichtbar wird
if layout.Visible() {
refreshData()
}
// wrapper.OnVisible = refreshData
return wrapper
return layout
}

View file

@ -2,9 +2,12 @@ package ui
import (
"fmt"
"log"
"git.patanix.de/git/kettlebell-app/internal/data"
"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/canvas"
@ -12,7 +15,7 @@ import (
"fyne.io/fyne/v2/widget"
)
func MakeHomeScreen(ts *services.TrainingService, onStart func()) fyne.CanvasObject {
func MakeHomeScreen(ts *services.TrainingService, db *data.DatabaseService, onStart func()) fyne.CanvasObject {
// Header
headerTitle := canvas.NewText("Patanix", theme.ColorSlate200)
headerTitle.TextSize = 28
@ -25,8 +28,6 @@ func MakeHomeScreen(ts *services.TrainingService, onStart func()) fyne.CanvasObj
// Nächstes Training CTA
state := ts.State
// Verwende ein Card-Widget für das Styling
nextTrainingCard := widget.NewCard(
"Nächstes Training",
fmt.Sprintf("%s - Tag %d", state.CurrentProgram, state.CurrentBlockDay),
@ -37,12 +38,30 @@ func MakeHomeScreen(ts *services.TrainingService, onStart func()) fyne.CanvasObj
)
// Letzte Leistung
setsValue := widget.NewLabelWithStyle("", fyne.TextAlignCenter, fyne.TextStyle{Bold: true})
durationValue := widget.NewLabelWithStyle("", fyne.TextAlignCenter, fyne.TextStyle{Bold: true})
weightValue := widget.NewLabelWithStyle("", fyne.TextAlignCenter, fyne.TextStyle{Bold: true})
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("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("Sätze"), setsValue),
container.NewVBox(widget.NewLabel("Dauer"), durationValue),
container.NewVBox(widget.NewLabel("Gewicht"), weightValue),
))
// Funktion zum Laden der letzten Leistung
loadLastPerformance := func() {
lastSession, err := db.GetLastTraining()
if err != nil {
log.Printf("Fehler beim Laden der letzten Session: %v", err)
return
}
if lastSession != nil {
setsValue.SetText(fmt.Sprintf("%d", lastSession.Sets))
durationValue.SetText(utils.FormatDuration(lastSession.Duration))
weightValue.SetText(fmt.Sprintf("%.1fkg", lastSession.WeightLeft)) // Annahme: linkes Gewicht ist repräsentativ
}
}
layout := container.NewVBox(
header,
widget.NewSeparator(),
@ -50,5 +69,12 @@ func MakeHomeScreen(ts *services.TrainingService, onStart func()) fyne.CanvasObj
statsCard,
)
return container.NewPadded(layout)
paddedLayout := container.NewPadded(layout)
// Daten laden, wenn der Bildschirm sichtbar wird
if paddedLayout.Visible() {
loadLastPerformance()
}
// paddedLayout.OnVisible = loadLastPerformance
return paddedLayout
}

View file

@ -12,10 +12,17 @@ import (
)
func MakeSettingsScreen(settingsService *services.SettingsService, parent fyne.Window) fyne.CanvasObject {
currentSettings := settingsService.LoadSettings()
var timeEntry, setsEntry, weightLeftEntry, weightRightEntry *widget.Entry
timeEntry := widget.NewEntry()
timeEntry.SetText(fmt.Sprintf("%d", currentSettings.TrainingTimeMinutes))
loadData := func() {
currentSettings := settingsService.LoadSettings()
timeEntry.SetText(fmt.Sprintf("%d", currentSettings.TrainingTimeMinutes))
setsEntry.SetText(fmt.Sprintf("%d", currentSettings.GoalSets))
weightLeftEntry.SetText(fmt.Sprintf("%.1f", currentSettings.WeightLeft))
weightRightEntry.SetText(fmt.Sprintf("%.1f", currentSettings.WeightRight))
}
timeEntry = widget.NewEntry()
timeEntry.Validator = func(s string) error {
if _, err := strconv.Atoi(s); err != nil {
return fmt.Errorf("muss eine Zahl sein")
@ -23,22 +30,18 @@ func MakeSettingsScreen(settingsService *services.SettingsService, parent fyne.W
return nil
}
setsEntry := widget.NewEntry()
setsEntry.SetText(fmt.Sprintf("%d", currentSettings.GoalSets))
setsEntry.Validator = timeEntry.Validator // Gleicher Validator
setsEntry = widget.NewEntry()
setsEntry.Validator = timeEntry.Validator
weightLeftEntry := widget.NewEntry()
weightLeftEntry.SetText(fmt.Sprintf("%.1f", currentSettings.WeightLeft))
weightLeftEntry = widget.NewEntry()
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.SetText(fmt.Sprintf("%.1f", currentSettings.WeightRight))
weightRightEntry.Validator = weightLeftEntry.Validator // Gleicher Validator
weightRightEntry = widget.NewEntry()
weightRightEntry.Validator = weightLeftEntry.Validator
form := &widget.Form{
Items: []*widget.FormItem{
@ -58,10 +61,9 @@ func MakeSettingsScreen(settingsService *services.SettingsService, parent fyne.W
GoalSets: goal,
WeightLeft: weightL,
WeightRight: weightR,
InitialProgram: currentSettings.InitialProgram, // Beibehalten
InitialProgram: settingsService.LoadSettings().InitialProgram,
}
settingsService.SaveSettings(newSettings)
fyne.CurrentApp().SendNotification(&fyne.Notification{
Title: "Gespeichert",
Content: "Die Einstellungen wurden erfolgreich aktualisiert.",
@ -70,13 +72,11 @@ func MakeSettingsScreen(settingsService *services.SettingsService, parent fyne.W
}
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)
paddedLayout := container.NewPadded(layout)
if paddedLayout.Visible() {
loadData()
} // Daten laden, wenn sichtbar
layout := container.NewVBox(
title,
widget.NewSeparator(),
form,
)
return container.NewPadded(layout)
return paddedLayout
}

View file

@ -15,24 +15,23 @@ import (
"fyne.io/fyne/v2/widget"
)
func MakeTrainingScreen(ts *services.TrainingService, ss *services.SettingsService, parent fyne.Window) fyne.CanvasObject {
// UI-Elemente mit canvas.Text für die Größensteuerung
timerLabel := canvas.NewText("20:00", theme.ColorSlate200)
// MakeTrainingScreen gibt jetzt das CanvasObject und die Start-Funktion zurück
func MakeTrainingScreen(ts *services.TrainingService, ss *services.SettingsService, parent fyne.Window) (fyne.CanvasObject, func()) {
timerLabel := canvas.NewText("00:00", theme.ColorSlate200)
timerLabel.TextSize = 60
timerLabel.TextStyle.Bold = true
timerLabel.Alignment = fyne.TextAlignCenter
setsLabel := canvas.NewText("0 / 8", theme.ColorSky400)
setsLabel := canvas.NewText("0 / 0", theme.ColorSky400)
setsLabel.TextSize = 48
setsLabel.TextStyle.Bold = true
setsLabel.Alignment = fyne.TextAlignCenter
repsLabel := canvas.NewText("5 Wiederholungen", theme.ColorSlate200)
repsLabel := canvas.NewText("0 Wiederholungen", theme.ColorSlate200)
repsLabel.TextSize = 20
repsLabel.Alignment = fyne.TextAlignCenter
var mainTimer *time.Ticker
var startButton *widget.Button // Start-Button deklarieren
updateUI := func() {
state := ts.State
@ -43,12 +42,6 @@ func MakeTrainingScreen(ts *services.TrainingService, ss *services.SettingsServi
timerLabel.Refresh()
setsLabel.Refresh()
repsLabel.Refresh()
if state.IsTrainingRunning {
startButton.Disable()
} else {
startButton.Enable()
}
}
finishAction := func() {
@ -56,6 +49,9 @@ func MakeTrainingScreen(ts *services.TrainingService, ss *services.SettingsServi
mainTimer.Stop()
mainTimer = nil
}
if !ts.State.IsTrainingRunning {
return // Nichts tun, wenn kein Training läuft
}
session := &data.TrainingSession{
Date: time.Now(),
Sets: int64(ts.State.SetsDone),
@ -70,6 +66,9 @@ func MakeTrainingScreen(ts *services.TrainingService, ss *services.SettingsServi
}
startAction := func() {
if ts.State.IsTrainingRunning {
return // Verhindere Neustart
}
settings := ss.LoadSettings()
ts.StartTraining(settings.TrainingTimeMinutes, settings.GoalSets)
updateUI()
@ -77,48 +76,43 @@ func MakeTrainingScreen(ts *services.TrainingService, ss *services.SettingsServi
mainTimer = time.NewTicker(time.Second)
go func() {
for mainTimer != nil {
<-mainTimer.C
if ts.State.RemainingSeconds <= 0 {
finishAction()
return
select {
case <-mainTimer.C:
if ts.State.RemainingSeconds <= 0 {
finishAction()
return
}
ts.Tick()
updateUI()
}
ts.Tick()
updateUI()
}
}()
}
setAction := func() {
if !ts.State.IsTrainingRunning {
// Starte das Training, wenn es noch nicht läuft
startAction()
return // Kein Satz ohne laufendes Training
}
ts.CompleteSet()
updateUI()
}
startButton = widget.NewButton("Training beginnen", startAction)
// Layout im "Cockpit"-Stil
topPart := container.NewVBox(
widget.NewLabelWithStyle("Verbleibende Zeit", fyne.TextAlignCenter, fyne.TextStyle{}),
timerLabel,
)
middlePart := container.NewVBox(
widget.NewLabelWithStyle("Sätze", fyne.TextAlignCenter, fyne.TextStyle{}),
setsLabel,
repsLabel,
)
topPart := container.NewVBox(widget.NewLabelWithStyle("Verbleibende Zeit", fyne.TextAlignCenter, fyne.TextStyle{}), timerLabel)
middlePart := container.NewVBox(widget.NewLabelWithStyle("Sätze", fyne.TextAlignCenter, fyne.TextStyle{}), setsLabel, repsLabel)
finishButton := widget.NewButton("Training beenden", finishAction)
finishButton.Importance = widget.HighImportance
bottomPart := container.NewVBox(
startButton, // Start-Button hinzugefügt
widget.NewButton("Satz abschließen", setAction),
finishButton,
)
return container.NewBorder(topPart, bottomPart, nil, nil, container.NewCenter(middlePart))
layout := container.NewBorder(topPart, bottomPart, nil, nil, container.NewCenter(middlePart))
// UI aktualisieren, wenn der Bildschirm sichtbar wird
if layout.Visible() {
updateUI()
}
// layout.OnVisible = updateUI
return layout, startAction
}