From c15cdea57d996e7641c225ced9714dccb684f314 Mon Sep 17 00:00:00 2001 From: Patryk Hegenberg Date: Fri, 27 Jun 2025 20:18:32 +0200 Subject: [PATCH] refactor: new ui step3 --- cmd/main.go | 34 ++++++++------- internal/data/database.go | 91 ++++++++++++++++++++++----------------- internal/ui/history.go | 50 ++++++++------------- internal/ui/home.go | 40 ++++++++++++++--- internal/ui/settings.go | 44 +++++++++---------- internal/ui/training.go | 66 +++++++++++++--------------- 6 files changed, 172 insertions(+), 153 deletions(-) diff --git a/cmd/main.go b/cmd/main.go index f32cc2a..fd17b7b 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -4,25 +4,23 @@ import ( "log" "path/filepath" - "git.patanix.de/git/kettlebell-app/internal/data" - "git.patanix.de/git/kettlebell-app/internal/services" - "git.patanix.de/git/kettlebell-app/internal/ui" - "fyne.io/fyne/v2" "fyne.io/fyne/v2/app" "fyne.io/fyne/v2/container" - - "git.patanix.de/git/kettlebell-app/internal/ui/theme" // <-- Neuer Theme-Import + "git.patanix.de/git/kettlebell-app/internal/data" + "git.patanix.de/git/kettlebell-app/internal/services" + "git.patanix.de/git/kettlebell-app/internal/ui" + "git.patanix.de/git/kettlebell-app/internal/ui/theme" ) func main() { // 1. App initialisieren und unser neues Theme setzen myApp := app.NewWithID("com.patani.kettlebell-tracker") - myApp.Settings().SetTheme(&theme.KettlebellTheme{}) // <-- Setzt unser benutzerdefiniertes Theme + myApp.Settings().SetTheme(&theme.KettlebellTheme{}) mainWindow := myApp.NewWindow("Kettlebell Tracker") - // 2. Services initialisieren (wie zuvor) + // 2. Services initialisieren dbDir := myApp.Storage().RootURI().Path() dbPath := filepath.Join(dbDir, "giant_training.db") dbService, err := data.NewDatabaseService(dbPath) @@ -33,15 +31,19 @@ func main() { apiService := services.NewApiService(myApp.UniqueID()) trainingService := services.NewTrainingService(dbService, settingsService, apiService) - // 3. UI-Bildschirme erstellen + // 3. UI-Bildschirme und Aktionen erstellen 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 - homeScreen := ui.MakeHomeScreen(trainingService, func() { navigateTo("training") }) - trainingScreen := ui.MakeTrainingScreen(trainingService, settingsService, mainWindow) + // Erstelle den Trainings-Screen und hole seine Start-Aktion + trainingScreen, startTrainingAction := ui.MakeTrainingScreen(trainingService, settingsService, mainWindow) + + // Erstelle den Home-Screen und übergebe ihm die Start-Aktion und die Navigationsfunktion + homeScreen := ui.MakeHomeScreen(trainingService, dbService, func() { + startTrainingAction() + navigateTo("training") + }) + historyScreen := ui.MakeHistoryScreen(dbService, mainWindow) settingsScreen := ui.MakeSettingsScreen(settingsService, mainWindow) @@ -58,7 +60,7 @@ func main() { // 4. Benutzerdefinierte Navigationsleiste erstellen navBar, navigateFunc := ui.MakeNavBar(screens, contentContainer) - navigateTo = navigateFunc // Weisen der Navigationsfunktion die Implementierung aus der Navbar zu + navigateTo = navigateFunc // Initial den Home-Bildschirm anzeigen navigateTo("home") @@ -67,7 +69,7 @@ func main() { mainLayout := container.NewBorder(nil, navBar, nil, nil, contentContainer) mainWindow.SetContent(mainLayout) - mainWindow.Resize(fyne.NewSize(360, 740)) // Mobile-freundliche Größe + mainWindow.Resize(fyne.NewSize(360, 740)) mainWindow.SetMaster() mainWindow.ShowAndRun() } diff --git a/internal/data/database.go b/internal/data/database.go index 4989cc9..b56f1f6 100644 --- a/internal/data/database.go +++ b/internal/data/database.go @@ -5,9 +5,10 @@ import ( "log" "time" - _ "modernc.org/sqlite" // Importiert den SQLite-Treiber + _ "modernc.org/sqlite" ) +// ... (DatabaseService struct und NewDatabaseService bleiben gleich) ... type DatabaseService struct { DB *sql.DB } @@ -17,11 +18,9 @@ func NewDatabaseService(dbPath string) (*DatabaseService, error) { if err != nil { return nil, err } - if err = db.Ping(); err != nil { return nil, err } - createTableSQL := ` CREATE TABLE IF NOT EXISTS training ( id INTEGER PRIMARY KEY AUTOINCREMENT, @@ -34,23 +33,18 @@ func NewDatabaseService(dbPath string) (*DatabaseService, error) { program TEXT, blockDay INTEGER );` - _, err = db.Exec(createTableSQL) if err != nil { log.Printf("Fehler beim Erstellen der Tabelle: %v", err) return nil, err } - - // Hier könnten wir auch komplexere Migrationen wie dein _onUpgrade handle, - // aber für den Anfang reicht das Erstellen der Tabelle. - log.Println("Datenbank erfolgreich initialisiert.") return &DatabaseService{DB: db}, nil } +// ... (SaveTraining und GetHistory bleiben gleich) ... func (s *DatabaseService) SaveTraining(session *TrainingSession) error { dateStr := session.Date.Format(time.RFC3339) - query := ` INSERT INTO training (id, date, sets, weightLeft, weightRight, repsPerSet, duration, program, blockDay) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) @@ -68,11 +62,58 @@ func (s *DatabaseService) SaveTraining(session *TrainingSession) error { if session.ID != 0 { id = session.ID } - _, err := s.DB.Exec(query, id, dateStr, session.Sets, session.WeightLeft, session.WeightRight, session.RepsPerSet, session.Duration, session.Program, session.BlockDay) return err } +func (s *DatabaseService) GetHistory() ([]TrainingSession, error) { + query := `SELECT id, date, sets, weightLeft, weightRight, repsPerSet, duration, program, blockDay FROM training ORDER BY date DESC LIMIT 20;` + rows, err := s.DB.Query(query) + if err != nil { + return nil, err + } + defer rows.Close() + var sessions []TrainingSession + for rows.Next() { + var sess TrainingSession + var dateStr string + err := rows.Scan(&sess.ID, &dateStr, &sess.Sets, &sess.WeightLeft, &sess.WeightRight, &sess.RepsPerSet, &sess.Duration, &sess.Program, &sess.BlockDay) + if err != nil { + return nil, err + } + sess.Date, err = time.Parse(time.RFC3339, dateStr) + if err != nil { + return nil, err + } + sessions = append(sessions, sess) + } + return sessions, nil +} + +// GetLastTraining ruft die letzte einzelne Trainingseinheit ab. +func (s *DatabaseService) GetLastTraining() (*TrainingSession, error) { + query := `SELECT id, date, sets, weightLeft, weightRight, repsPerSet, duration, program, blockDay FROM training ORDER BY date DESC LIMIT 1;` + row := s.DB.QueryRow(query) + + var session TrainingSession + var dateStr string + err := row.Scan(&session.ID, &dateStr, &session.Sets, &session.WeightLeft, &session.WeightRight, &session.RepsPerSet, &session.Duration, &session.Program, &session.BlockDay) + if err != nil { + if err == sql.ErrNoRows { + return nil, nil // Kein Fehler, nur keine Einträge + } + return nil, err + } + + session.Date, err = time.Parse(time.RFC3339, dateStr) + if err != nil { + return nil, err + } + + return &session, nil +} + +// GetTrainingCount zählt alle Trainingseinheiten. func (s *DatabaseService) GetTrainingCount() (int, error) { var count int query := "SELECT COUNT(*) FROM training;" @@ -85,33 +126,3 @@ func (s *DatabaseService) GetTrainingCount() (int, error) { } return count, nil } - -func (s *DatabaseService) GetHistory() ([]TrainingSession, error) { - query := `SELECT id, date, sets, weightLeft, weightRight, repsPerSet, duration, program, blockDay FROM training ORDER BY date DESC LIMIT 20;` - - rows, err := s.DB.Query(query) - if err != nil { - return nil, err - } - defer rows.Close() - - var sessions []TrainingSession - for rows.Next() { - var s TrainingSession - var dateStr string - - err := rows.Scan(&s.ID, &dateStr, &s.Sets, &s.WeightLeft, &s.WeightRight, &s.RepsPerSet, &s.Duration, &s.Program, &s.BlockDay) - if err != nil { - return nil, err - } - - s.Date, err = time.Parse(time.RFC3339, dateStr) - if err != nil { - return nil, err - } - - sessions = append(sessions, s) - } - - return sessions, nil -} diff --git a/internal/ui/history.go b/internal/ui/history.go index 98c40f5..71bb629 100644 --- a/internal/ui/history.go +++ b/internal/ui/history.go @@ -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 } diff --git a/internal/ui/home.go b/internal/ui/home.go index 11e4fd3..d1c95e5 100644 --- a/internal/ui/home.go +++ b/internal/ui/home.go @@ -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 } diff --git a/internal/ui/settings.go b/internal/ui/settings.go index 81a60b2..61f765d 100644 --- a/internal/ui/settings.go +++ b/internal/ui/settings.go @@ -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 } diff --git a/internal/ui/training.go b/internal/ui/training.go index ce9b921..6490db5 100644 --- a/internal/ui/training.go +++ b/internal/ui/training.go @@ -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 }