From 084ea252a25c362eee353ea2fa7ef26fd292fad6 Mon Sep 17 00:00:00 2001 From: Patryk Hegenberg Date: Fri, 4 Jul 2025 17:12:28 +0200 Subject: [PATCH] refactor: change history screen to use a list instead of cards --- internal/data/database.go | 17 +++++ internal/ui/history.go | 148 ++++++++++++++++++++++++++++++++----- internal/ui/theme/theme.go | 85 --------------------- 3 files changed, 147 insertions(+), 103 deletions(-) diff --git a/internal/data/database.go b/internal/data/database.go index 4bf797d..d0781b3 100644 --- a/internal/data/database.go +++ b/internal/data/database.go @@ -122,3 +122,20 @@ func (s *DatabaseService) GetTrainingCount() (int, error) { } 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 +} diff --git a/internal/ui/history.go b/internal/ui/history.go index da42543..45f3bc1 100644 --- a/internal/ui/history.go +++ b/internal/ui/history.go @@ -3,6 +3,7 @@ package ui import ( "fmt" "log" + "strconv" "fyne.io/fyne/v2" "fyne.io/fyne/v2/container" @@ -17,33 +18,137 @@ func MakeHistoryScreen(db *data.DatabaseService, parent fyne.Window) fyne.Canvas var history []data.TrainingSession placeholder := widget.NewLabel("Noch keine Trainingsdaten vorhanden.") + + var showDetailDialog func(session data.TrainingSession, id int) + var refreshData func() + list := widget.NewList( func() int { return len(history) }, func() fyne.CanvasObject { - 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 container.NewGridWithColumns(4, + widget.NewLabel(""), + widget.NewLabel(""), + widget.NewLabel(""), + widget.NewLabel(""), ) - return widget.NewCard("", "", container.NewVBox(widget.NewLabel(""), widget.NewSeparator(), stats)) }, func(i widget.ListItemID, o fyne.CanvasObject) { session := history[i] - card := o.(*widget.Card) - card.SetTitle(session.Date.Format("02.01.2006 15:04")) - - content := card.Content.(*fyne.Container) - programLabel := content.Objects[0].(*widget.Label) - 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)) + row := o.(*fyne.Container) + 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)) + row.Objects[2].(*widget.Label).SetText(utils.FormatDuration(session.Duration)) + row.Objects[3].(*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 history, err = db.GetHistory() if err != nil { @@ -61,8 +166,15 @@ func MakeHistoryScreen(db *data.DatabaseService, parent fyne.Window) fyne.Canvas 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)) - 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) if layout.Visible() { diff --git a/internal/ui/theme/theme.go b/internal/ui/theme/theme.go index 5142b98..daf6492 100644 --- a/internal/ui/theme/theme.go +++ b/internal/ui/theme/theme.go @@ -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 import ( @@ -86,21 +7,17 @@ import ( "fyne.io/fyne/v2/theme" ) -// One Dark Farben (angepasst für Fyne) var ( - // Basisfarben 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) 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) - // Akzentfarben (One Dark) OneDarkGreen = color.NRGBA{R: 152, G: 195, B: 121, A: 0xff} // #98c379 OneDarkRed = color.NRGBA{R: 224, G: 108, B: 117, A: 0xff} // #e06c75 OneDarkYellow = color.NRGBA{R: 229, G: 192, B: 123, A: 0xff} // #e5c07b ) -// KettlebellThemeOneDark ist das angepasste Theme type KettlebellThemeOneDark struct{} 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 case theme.ColorNameFocus: return OneDarkYellow - // case theme.ColorNameCardBackground: - // return OneDarkCardBackground default: return theme.DefaultTheme().Color(name, variant) }