refactor: change history screen to use a list instead of cards

This commit is contained in:
Patryk Hegenberg 2025-07-04 17:12:28 +02:00
parent 059db8f2fb
commit 084ea252a2
3 changed files with 147 additions and 103 deletions

View file

@ -122,3 +122,20 @@ func (s *DatabaseService) GetTrainingCount() (int, error) {
} }
return count, nil return count, nil
} }
func (s *DatabaseService) DeleteTraining(id int64) error {
query := "DELETE FROM training WHERE id = ?;"
_, err := s.DB.Exec(query, id)
return err
}
func (s *DatabaseService) UpdateTraining(session *TrainingSession) error {
dateStr := session.Date.Format(time.RFC3339)
query := `
UPDATE training
SET date = ?, sets = ?, weightLeft = ?, weightRight = ?, repsPerSet = ?, duration = ?, program = ?, blockDay = ?
WHERE id = ?;
`
_, err := s.DB.Exec(query, dateStr, session.Sets, session.WeightLeft, session.WeightRight, session.RepsPerSet, session.Duration, session.Program, session.BlockDay, session.ID)
return err
}

View file

@ -3,6 +3,7 @@ package ui
import ( import (
"fmt" "fmt"
"log" "log"
"strconv"
"fyne.io/fyne/v2" "fyne.io/fyne/v2"
"fyne.io/fyne/v2/container" "fyne.io/fyne/v2/container"
@ -17,33 +18,137 @@ func MakeHistoryScreen(db *data.DatabaseService, parent fyne.Window) fyne.Canvas
var history []data.TrainingSession var history []data.TrainingSession
placeholder := widget.NewLabel("Noch keine Trainingsdaten vorhanden.") placeholder := widget.NewLabel("Noch keine Trainingsdaten vorhanden.")
var showDetailDialog func(session data.TrainingSession, id int)
var refreshData func()
list := widget.NewList( list := widget.NewList(
func() int { return len(history) }, func() int { return len(history) },
func() fyne.CanvasObject { func() fyne.CanvasObject {
stats := container.NewGridWithColumns(3, return container.NewGridWithColumns(4,
container.NewVBox(widget.NewLabel("Sätze"), widget.NewLabelWithStyle("", fyne.TextAlignCenter, fyne.TextStyle{Bold: true})), widget.NewLabel(""),
container.NewVBox(widget.NewLabel("Dauer"), widget.NewLabelWithStyle("", fyne.TextAlignCenter, fyne.TextStyle{Bold: true})), widget.NewLabel(""),
container.NewVBox(widget.NewLabel("Reps/Satz"), widget.NewLabelWithStyle("", fyne.TextAlignCenter, fyne.TextStyle{Bold: true})), widget.NewLabel(""),
widget.NewLabel(""),
) )
return widget.NewCard("", "", container.NewVBox(widget.NewLabel(""), widget.NewSeparator(), stats))
}, },
func(i widget.ListItemID, o fyne.CanvasObject) { func(i widget.ListItemID, o fyne.CanvasObject) {
session := history[i] session := history[i]
card := o.(*widget.Card) row := o.(*fyne.Container)
card.SetTitle(session.Date.Format("02.01.2006 15:04")) row.Objects[0].(*widget.Label).SetText(session.Date.Format("02.01.2006 15:04"))
row.Objects[1].(*widget.Label).SetText(fmt.Sprintf("%d", session.Sets))
content := card.Content.(*fyne.Container) row.Objects[2].(*widget.Label).SetText(utils.FormatDuration(session.Duration))
programLabel := content.Objects[0].(*widget.Label) row.Objects[3].(*widget.Label).SetText(fmt.Sprintf("%d", session.RepsPerSet))
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))
}, },
) )
refreshData := func() { list.OnSelected = func(id widget.ListItemID) {
session := history[id]
showDetailDialog(session, id)
list.Unselect(id)
}
showDetailDialog = func(session data.TrainingSession, id int) {
details := container.NewVBox(
widget.NewLabel(fmt.Sprintf("Datum: %s", session.Date.Format("02.01.2006 15:04"))),
widget.NewLabel(fmt.Sprintf("Programm: %s", session.Program)),
widget.NewLabel(fmt.Sprintf("Block-Tag: %d", session.BlockDay)),
widget.NewLabel(fmt.Sprintf("Sätze: %d", session.Sets)),
widget.NewLabel(fmt.Sprintf("Dauer: %s", utils.FormatDuration(session.Duration))),
widget.NewLabel(fmt.Sprintf("Reps/Satz: %d", session.RepsPerSet)),
widget.NewLabel(fmt.Sprintf("Gewicht links: %.1f kg", session.WeightLeft)),
widget.NewLabel(fmt.Sprintf("Gewicht rechts: %.1f kg", session.WeightRight)),
)
deleteBtn := widget.NewButtonWithIcon("Löschen", theme.DeleteIcon(), func() {
dialog.ShowConfirm("Eintrag löschen", "Möchtest du diesen Eintrag wirklich löschen?", func(ok bool) {
if ok {
err := db.DeleteTraining(session.ID)
if err != nil {
dialog.ShowError(err, parent)
return
}
refreshData()
}
}, parent)
})
editBtn := widget.NewButtonWithIcon("Bearbeiten", theme.DocumentCreateIcon(), func() {
setsEntry := widget.NewEntry()
setsEntry.SetText(fmt.Sprintf("%d", session.Sets))
durationEntry := widget.NewEntry()
durationEntry.SetText(fmt.Sprintf("%d", session.Duration)) // Dauer in Sekunden
repsEntry := widget.NewEntry()
repsEntry.SetText(fmt.Sprintf("%d", session.RepsPerSet))
weightLeftEntry := widget.NewEntry()
weightLeftEntry.SetText(fmt.Sprintf("%.1f", session.WeightLeft))
weightRightEntry := widget.NewEntry()
weightRightEntry.SetText(fmt.Sprintf("%.1f", session.WeightRight))
form := &widget.Form{
Items: []*widget.FormItem{
{Text: "Sätze", Widget: setsEntry},
{Text: "Dauer (Sekunden)", Widget: durationEntry},
{Text: "Reps/Satz", Widget: repsEntry},
{Text: "Gewicht links (kg)", Widget: weightLeftEntry},
{Text: "Gewicht rechts (kg)", Widget: weightRightEntry},
},
OnSubmit: func() {
var (
sets, reps, duration int
weightLeft, weightRight float64
err error
)
if sets, err = strconv.Atoi(setsEntry.Text); err != nil {
dialog.ShowError(fmt.Errorf("Ungültige Sätze-Zahl"), parent)
return
}
if duration, err = strconv.Atoi(durationEntry.Text); err != nil {
dialog.ShowError(fmt.Errorf("Ungültige Dauer"), parent)
return
}
if reps, err = strconv.Atoi(repsEntry.Text); err != nil {
dialog.ShowError(fmt.Errorf("Ungültige Wiederholungszahl"), parent)
return
}
if weightLeft, err = strconv.ParseFloat(weightLeftEntry.Text, 64); err != nil {
dialog.ShowError(fmt.Errorf("Ungültiges Gewicht links"), parent)
return
}
if weightRight, err = strconv.ParseFloat(weightRightEntry.Text, 64); err != nil {
dialog.ShowError(fmt.Errorf("Ungültiges Gewicht rechts"), parent)
return
}
session.Sets = int64(sets)
session.Duration = int64(duration)
session.RepsPerSet = int64(reps)
session.WeightLeft = weightLeft
session.WeightRight = weightRight
if err := db.UpdateTraining(&session); err != nil {
dialog.ShowError(err, parent)
return
}
dialog.ShowInformation("Erfolg", "Trainingseintrag aktualisiert.", parent)
refreshData()
},
}
dialog.ShowForm("Training bearbeiten", "Speichern", "Abbrechen", form.Items, func(ok bool) {
}, parent)
})
btns := container.NewHBox(editBtn, deleteBtn)
dialog.ShowCustom("Training Details", "Schließen", container.NewVBox(details, btns), parent)
}
refreshData = func() {
var err error var err error
history, err = db.GetHistory() history, err = db.GetHistory()
if err != nil { if err != nil {
@ -61,8 +166,15 @@ func MakeHistoryScreen(db *data.DatabaseService, parent fyne.Window) fyne.Canvas
list.Refresh() list.Refresh()
} }
header := container.NewGridWithColumns(4,
widget.NewLabelWithStyle("Datum", fyne.TextAlignCenter, fyne.TextStyle{Bold: true}),
widget.NewLabelWithStyle("Sätze", fyne.TextAlignCenter, fyne.TextStyle{Bold: true}),
widget.NewLabelWithStyle("Dauer", fyne.TextAlignCenter, fyne.TextStyle{Bold: true}),
widget.NewLabelWithStyle("Reps/Satz", fyne.TextAlignCenter, fyne.TextStyle{Bold: true}),
)
toolbar := widget.NewToolbar(widget.NewToolbarAction(theme.ViewRefreshIcon(), refreshData)) toolbar := widget.NewToolbar(widget.NewToolbarAction(theme.ViewRefreshIcon(), refreshData))
content := container.NewStack(list, container.NewCenter(placeholder)) content := container.NewVBox(header, container.NewStack(list, container.NewCenter(placeholder)))
layout := container.NewBorder(toolbar, nil, nil, nil, content) layout := container.NewBorder(toolbar, nil, nil, nil, content)
if layout.Visible() { if layout.Visible() {

View file

@ -1,82 +1,3 @@
// package theme
//
// import (
//
// "image/color"
//
// "fyne.io/fyne/v2"
// "fyne.io/fyne/v2/theme"
//
// )
//
// // Definiere unsere benutzerdefinierten Farben basierend auf dem Mock-up
// var (
//
// 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
// 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
// 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
// ColorSky400 = color.NRGBA{R: 0x38, G: 0xbd, B: 0xf8, A: 0xff} // text-sky-400
// ColorRed500 = color.NRGBA{R: 0xef, G: 0x44, B: 0x44, A: 0xff} // red-500
//
// )
//
// // KettlebellTheme ist unsere benutzerdefinierte Theme-Implementierung
// type KettlebellTheme struct{}
//
// func (t *KettlebellTheme) Color(name fyne.ThemeColorName, variant fyne.ThemeVariant) color.Color {
// switch name {
// case theme.ColorNameBackground:
// return ColorSlate900
// case theme.ColorNameButton:
// return ColorSlate800
// case theme.ColorNameDisabledButton:
// return ColorSlate700
// case theme.ColorNamePrimary:
// return ColorSky500
// case theme.ColorNamePlaceHolder:
// return ColorSlate400
// case theme.ColorNameHover:
// return ColorSlate700
// case theme.ColorNameForeground:
// return ColorSlate200
// case theme.ColorNameDisabled:
// return ColorSlate400
// case theme.ColorNameError:
// return ColorRed500
// case theme.ColorNameInputBackground:
// return ColorSlate800
// case theme.ColorNameSeparator:
// return ColorSlate700
// default:
// return theme.DefaultTheme().Color(name, variant)
// }
// }
//
// func (t *KettlebellTheme) Icon(name fyne.ThemeIconName) fyne.Resource {
// return theme.DefaultTheme().Icon(name)
// }
//
// func (t *KettlebellTheme) Font(style fyne.TextStyle) fyne.Resource {
// return theme.DefaultTheme().Font(style)
// }
//
// func (t *KettlebellTheme) Size(name fyne.ThemeSizeName) float32 {
// switch name {
// case theme.SizeNamePadding:
// return 8
// case theme.SizeNameText:
// return 16
// case theme.SizeNameHeadingText:
// return 24
// case theme.SizeNameSubHeadingText:
// return 20
// default:
// return theme.DefaultTheme().Size(name)
// }
// }
package theme package theme
import ( import (
@ -86,21 +7,17 @@ import (
"fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/theme"
) )
// One Dark Farben (angepasst für Fyne)
var ( var (
// Basisfarben
OneDarkBackground = color.NRGBA{R: 40, G: 44, B: 52, A: 0xff} // #282c34 (helleres Grau) OneDarkBackground = color.NRGBA{R: 40, G: 44, B: 52, A: 0xff} // #282c34 (helleres Grau)
OneDarkCardBackground = color.NRGBA{R: 30, G: 32, B: 40, A: 0xff} // #1e2028 (dunkler für Cards) OneDarkCardBackground = color.NRGBA{R: 30, G: 32, B: 40, A: 0xff} // #1e2028 (dunkler für Cards)
OneDarkText = color.NRGBA{R: 171, G: 178, B: 191, A: 0xff} // #abb2bf (Standard-Text) OneDarkText = color.NRGBA{R: 171, G: 178, B: 191, A: 0xff} // #abb2bf (Standard-Text)
OneDarkSubtleText = color.NRGBA{R: 110, G: 115, B: 141, A: 0xff} // #6e738d (deaktiviert, Placeholder) OneDarkSubtleText = color.NRGBA{R: 110, G: 115, B: 141, A: 0xff} // #6e738d (deaktiviert, Placeholder)
// Akzentfarben (One Dark)
OneDarkGreen = color.NRGBA{R: 152, G: 195, B: 121, A: 0xff} // #98c379 OneDarkGreen = color.NRGBA{R: 152, G: 195, B: 121, A: 0xff} // #98c379
OneDarkRed = color.NRGBA{R: 224, G: 108, B: 117, A: 0xff} // #e06c75 OneDarkRed = color.NRGBA{R: 224, G: 108, B: 117, A: 0xff} // #e06c75
OneDarkYellow = color.NRGBA{R: 229, G: 192, B: 123, A: 0xff} // #e5c07b OneDarkYellow = color.NRGBA{R: 229, G: 192, B: 123, A: 0xff} // #e5c07b
) )
// KettlebellThemeOneDark ist das angepasste Theme
type KettlebellThemeOneDark struct{} type KettlebellThemeOneDark struct{}
func (t *KettlebellThemeOneDark) Color(name fyne.ThemeColorName, variant fyne.ThemeVariant) color.Color { func (t *KettlebellThemeOneDark) Color(name fyne.ThemeColorName, variant fyne.ThemeVariant) color.Color {
@ -133,8 +50,6 @@ func (t *KettlebellThemeOneDark) Color(name fyne.ThemeColorName, variant fyne.Th
return OneDarkCardBackground return OneDarkCardBackground
case theme.ColorNameFocus: case theme.ColorNameFocus:
return OneDarkYellow return OneDarkYellow
// case theme.ColorNameCardBackground:
// return OneDarkCardBackground
default: default:
return theme.DefaultTheme().Color(name, variant) return theme.DefaultTheme().Color(name, variant)
} }