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,25 +4,23 @@ import (
"log" "log"
"path/filepath" "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"
"fyne.io/fyne/v2/app" "fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/container" "fyne.io/fyne/v2/container"
"git.patanix.de/git/kettlebell-app/internal/data"
"git.patanix.de/git/kettlebell-app/internal/ui/theme" // <-- Neuer Theme-Import "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() { func main() {
// 1. App initialisieren und unser neues Theme setzen // 1. App initialisieren und unser neues Theme setzen
myApp := app.NewWithID("com.patani.kettlebell-tracker") 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") mainWindow := myApp.NewWindow("Kettlebell Tracker")
// 2. Services initialisieren (wie zuvor) // 2. Services initialisieren
dbDir := myApp.Storage().RootURI().Path() dbDir := myApp.Storage().RootURI().Path()
dbPath := filepath.Join(dbDir, "giant_training.db") dbPath := filepath.Join(dbDir, "giant_training.db")
dbService, err := data.NewDatabaseService(dbPath) dbService, err := data.NewDatabaseService(dbPath)
@ -33,15 +31,19 @@ func main() {
apiService := services.NewApiService(myApp.UniqueID()) apiService := services.NewApiService(myApp.UniqueID())
trainingService := services.NewTrainingService(dbService, settingsService, apiService) trainingService := services.NewTrainingService(dbService, settingsService, apiService)
// 3. UI-Bildschirme erstellen // 3. UI-Bildschirme und Aktionen erstellen
contentContainer := container.NewMax() contentContainer := container.NewMax()
// Navigationsfunktion definieren, die wir an die Screens weitergeben können
var navigateTo func(string) var navigateTo func(string)
// Die Bildschirme erstellen und dem Container hinzufügen // Erstelle den Trainings-Screen und hole seine Start-Aktion
homeScreen := ui.MakeHomeScreen(trainingService, func() { navigateTo("training") }) trainingScreen, startTrainingAction := ui.MakeTrainingScreen(trainingService, settingsService, mainWindow)
trainingScreen := 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) historyScreen := ui.MakeHistoryScreen(dbService, mainWindow)
settingsScreen := ui.MakeSettingsScreen(settingsService, mainWindow) settingsScreen := ui.MakeSettingsScreen(settingsService, mainWindow)
@ -58,7 +60,7 @@ func main() {
// 4. Benutzerdefinierte Navigationsleiste erstellen // 4. Benutzerdefinierte Navigationsleiste erstellen
navBar, navigateFunc := ui.MakeNavBar(screens, contentContainer) navBar, navigateFunc := ui.MakeNavBar(screens, contentContainer)
navigateTo = navigateFunc // Weisen der Navigationsfunktion die Implementierung aus der Navbar zu navigateTo = navigateFunc
// Initial den Home-Bildschirm anzeigen // Initial den Home-Bildschirm anzeigen
navigateTo("home") navigateTo("home")
@ -67,7 +69,7 @@ func main() {
mainLayout := container.NewBorder(nil, navBar, nil, nil, contentContainer) mainLayout := container.NewBorder(nil, navBar, nil, nil, contentContainer)
mainWindow.SetContent(mainLayout) mainWindow.SetContent(mainLayout)
mainWindow.Resize(fyne.NewSize(360, 740)) // Mobile-freundliche Größe mainWindow.Resize(fyne.NewSize(360, 740))
mainWindow.SetMaster() mainWindow.SetMaster()
mainWindow.ShowAndRun() mainWindow.ShowAndRun()
} }

View file

@ -5,9 +5,10 @@ import (
"log" "log"
"time" "time"
_ "modernc.org/sqlite" // Importiert den SQLite-Treiber _ "modernc.org/sqlite"
) )
// ... (DatabaseService struct und NewDatabaseService bleiben gleich) ...
type DatabaseService struct { type DatabaseService struct {
DB *sql.DB DB *sql.DB
} }
@ -17,11 +18,9 @@ func NewDatabaseService(dbPath string) (*DatabaseService, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
if err = db.Ping(); err != nil { if err = db.Ping(); err != nil {
return nil, err return nil, err
} }
createTableSQL := ` createTableSQL := `
CREATE TABLE IF NOT EXISTS training ( CREATE TABLE IF NOT EXISTS training (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
@ -34,23 +33,18 @@ func NewDatabaseService(dbPath string) (*DatabaseService, error) {
program TEXT, program TEXT,
blockDay INTEGER blockDay INTEGER
);` );`
_, err = db.Exec(createTableSQL) _, err = db.Exec(createTableSQL)
if err != nil { if err != nil {
log.Printf("Fehler beim Erstellen der Tabelle: %v", err) log.Printf("Fehler beim Erstellen der Tabelle: %v", err)
return nil, 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.") log.Println("Datenbank erfolgreich initialisiert.")
return &DatabaseService{DB: db}, nil return &DatabaseService{DB: db}, nil
} }
// ... (SaveTraining und GetHistory bleiben gleich) ...
func (s *DatabaseService) SaveTraining(session *TrainingSession) error { func (s *DatabaseService) SaveTraining(session *TrainingSession) error {
dateStr := session.Date.Format(time.RFC3339) dateStr := session.Date.Format(time.RFC3339)
query := ` query := `
INSERT INTO training (id, date, sets, weightLeft, weightRight, repsPerSet, duration, program, blockDay) INSERT INTO training (id, date, sets, weightLeft, weightRight, repsPerSet, duration, program, blockDay)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
@ -68,11 +62,58 @@ func (s *DatabaseService) SaveTraining(session *TrainingSession) error {
if session.ID != 0 { if session.ID != 0 {
id = session.ID id = session.ID
} }
_, err := s.DB.Exec(query, id, dateStr, session.Sets, session.WeightLeft, session.WeightRight, session.RepsPerSet, session.Duration, session.Program, session.BlockDay) _, err := s.DB.Exec(query, id, dateStr, session.Sets, session.WeightLeft, session.WeightRight, session.RepsPerSet, session.Duration, session.Program, session.BlockDay)
return err 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) { func (s *DatabaseService) GetTrainingCount() (int, error) {
var count int var count int
query := "SELECT COUNT(*) FROM training;" query := "SELECT COUNT(*) FROM training;"
@ -85,33 +126,3 @@ func (s *DatabaseService) GetTrainingCount() (int, error) {
} }
return count, nil 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
}

View file

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

View file

@ -2,9 +2,12 @@ package ui
import ( import (
"fmt" "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/services"
"git.patanix.de/git/kettlebell-app/internal/ui/theme" "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/canvas"
@ -12,7 +15,7 @@ import (
"fyne.io/fyne/v2/widget" "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 // Header
headerTitle := canvas.NewText("Patanix", theme.ColorSlate200) headerTitle := canvas.NewText("Patanix", theme.ColorSlate200)
headerTitle.TextSize = 28 headerTitle.TextSize = 28
@ -25,8 +28,6 @@ func MakeHomeScreen(ts *services.TrainingService, onStart func()) fyne.CanvasObj
// 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),
@ -37,12 +38,30 @@ func MakeHomeScreen(ts *services.TrainingService, onStart func()) fyne.CanvasObj
) )
// Letzte Leistung // 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, 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"), setsValue),
container.NewVBox(widget.NewLabel("Dauer"), widget.NewLabelWithStyle("18:45", fyne.TextAlignCenter, fyne.TextStyle{Bold: true})), container.NewVBox(widget.NewLabel("Dauer"), durationValue),
container.NewVBox(widget.NewLabel("Gewicht"), widget.NewLabelWithStyle("16kg", fyne.TextAlignCenter, fyne.TextStyle{Bold: true})), 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( layout := container.NewVBox(
header, header,
widget.NewSeparator(), widget.NewSeparator(),
@ -50,5 +69,12 @@ func MakeHomeScreen(ts *services.TrainingService, onStart func()) fyne.CanvasObj
statsCard, 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 { func MakeSettingsScreen(settingsService *services.SettingsService, parent fyne.Window) fyne.CanvasObject {
currentSettings := settingsService.LoadSettings() var timeEntry, setsEntry, weightLeftEntry, weightRightEntry *widget.Entry
timeEntry := widget.NewEntry() loadData := func() {
timeEntry.SetText(fmt.Sprintf("%d", currentSettings.TrainingTimeMinutes)) 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 { timeEntry.Validator = func(s string) error {
if _, err := strconv.Atoi(s); err != nil { if _, err := strconv.Atoi(s); err != nil {
return fmt.Errorf("muss eine Zahl sein") return fmt.Errorf("muss eine Zahl sein")
@ -23,22 +30,18 @@ func MakeSettingsScreen(settingsService *services.SettingsService, parent fyne.W
return nil return nil
} }
setsEntry := widget.NewEntry() setsEntry = widget.NewEntry()
setsEntry.SetText(fmt.Sprintf("%d", currentSettings.GoalSets)) setsEntry.Validator = timeEntry.Validator
setsEntry.Validator = timeEntry.Validator // Gleicher Validator
weightLeftEntry := widget.NewEntry() weightLeftEntry = widget.NewEntry()
weightLeftEntry.SetText(fmt.Sprintf("%.1f", currentSettings.WeightLeft))
weightLeftEntry.Validator = func(s string) error { weightLeftEntry.Validator = func(s string) error {
if _, err := strconv.ParseFloat(s, 64); err != nil { if _, err := strconv.ParseFloat(s, 64); err != nil {
return fmt.Errorf("muss eine Zahl sein") return fmt.Errorf("muss eine Zahl sein")
} }
return nil return nil
} }
weightRightEntry = widget.NewEntry()
weightRightEntry := widget.NewEntry() weightRightEntry.Validator = weightLeftEntry.Validator
weightRightEntry.SetText(fmt.Sprintf("%.1f", currentSettings.WeightRight))
weightRightEntry.Validator = weightLeftEntry.Validator // Gleicher Validator
form := &widget.Form{ form := &widget.Form{
Items: []*widget.FormItem{ Items: []*widget.FormItem{
@ -58,10 +61,9 @@ func MakeSettingsScreen(settingsService *services.SettingsService, parent fyne.W
GoalSets: goal, GoalSets: goal,
WeightLeft: weightL, WeightLeft: weightL,
WeightRight: weightR, WeightRight: weightR,
InitialProgram: currentSettings.InitialProgram, // Beibehalten InitialProgram: settingsService.LoadSettings().InitialProgram,
} }
settingsService.SaveSettings(newSettings) settingsService.SaveSettings(newSettings)
fyne.CurrentApp().SendNotification(&fyne.Notification{ fyne.CurrentApp().SendNotification(&fyne.Notification{
Title: "Gespeichert", Title: "Gespeichert",
Content: "Die Einstellungen wurden erfolgreich aktualisiert.", 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 := 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( return paddedLayout
title,
widget.NewSeparator(),
form,
)
return container.NewPadded(layout)
} }

View file

@ -15,24 +15,23 @@ import (
"fyne.io/fyne/v2/widget" "fyne.io/fyne/v2/widget"
) )
func MakeTrainingScreen(ts *services.TrainingService, ss *services.SettingsService, parent fyne.Window) fyne.CanvasObject { // MakeTrainingScreen gibt jetzt das CanvasObject und die Start-Funktion zurück
// UI-Elemente mit canvas.Text für die Größensteuerung func MakeTrainingScreen(ts *services.TrainingService, ss *services.SettingsService, parent fyne.Window) (fyne.CanvasObject, func()) {
timerLabel := canvas.NewText("20:00", theme.ColorSlate200) timerLabel := canvas.NewText("00:00", theme.ColorSlate200)
timerLabel.TextSize = 60 timerLabel.TextSize = 60
timerLabel.TextStyle.Bold = true timerLabel.TextStyle.Bold = true
timerLabel.Alignment = fyne.TextAlignCenter timerLabel.Alignment = fyne.TextAlignCenter
setsLabel := canvas.NewText("0 / 8", theme.ColorSky400) setsLabel := canvas.NewText("0 / 0", theme.ColorSky400)
setsLabel.TextSize = 48 setsLabel.TextSize = 48
setsLabel.TextStyle.Bold = true setsLabel.TextStyle.Bold = true
setsLabel.Alignment = fyne.TextAlignCenter setsLabel.Alignment = fyne.TextAlignCenter
repsLabel := canvas.NewText("5 Wiederholungen", theme.ColorSlate200) repsLabel := canvas.NewText("0 Wiederholungen", theme.ColorSlate200)
repsLabel.TextSize = 20 repsLabel.TextSize = 20
repsLabel.Alignment = fyne.TextAlignCenter 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
@ -43,12 +42,6 @@ func MakeTrainingScreen(ts *services.TrainingService, ss *services.SettingsServi
timerLabel.Refresh() timerLabel.Refresh()
setsLabel.Refresh() setsLabel.Refresh()
repsLabel.Refresh() repsLabel.Refresh()
if state.IsTrainingRunning {
startButton.Disable()
} else {
startButton.Enable()
}
} }
finishAction := func() { finishAction := func() {
@ -56,6 +49,9 @@ func MakeTrainingScreen(ts *services.TrainingService, ss *services.SettingsServi
mainTimer.Stop() mainTimer.Stop()
mainTimer = nil mainTimer = nil
} }
if !ts.State.IsTrainingRunning {
return // Nichts tun, wenn kein Training läuft
}
session := &data.TrainingSession{ session := &data.TrainingSession{
Date: time.Now(), Date: time.Now(),
Sets: int64(ts.State.SetsDone), Sets: int64(ts.State.SetsDone),
@ -70,6 +66,9 @@ func MakeTrainingScreen(ts *services.TrainingService, ss *services.SettingsServi
} }
startAction := func() { startAction := func() {
if ts.State.IsTrainingRunning {
return // Verhindere Neustart
}
settings := ss.LoadSettings() settings := ss.LoadSettings()
ts.StartTraining(settings.TrainingTimeMinutes, settings.GoalSets) ts.StartTraining(settings.TrainingTimeMinutes, settings.GoalSets)
updateUI() updateUI()
@ -77,48 +76,43 @@ func MakeTrainingScreen(ts *services.TrainingService, ss *services.SettingsServi
mainTimer = time.NewTicker(time.Second) mainTimer = time.NewTicker(time.Second)
go func() { go func() {
for mainTimer != nil { for mainTimer != nil {
<-mainTimer.C select {
if ts.State.RemainingSeconds <= 0 { case <-mainTimer.C:
finishAction() if ts.State.RemainingSeconds <= 0 {
return finishAction()
return
}
ts.Tick()
updateUI()
} }
ts.Tick()
updateUI()
} }
}() }()
} }
setAction := func() { setAction := func() {
if !ts.State.IsTrainingRunning { if !ts.State.IsTrainingRunning {
// Starte das Training, wenn es noch nicht läuft return // Kein Satz ohne laufendes Training
startAction()
} }
ts.CompleteSet() ts.CompleteSet()
updateUI() updateUI()
} }
startButton = widget.NewButton("Training beginnen", startAction) 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)
// 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,
)
finishButton := widget.NewButton("Training beenden", finishAction) finishButton := widget.NewButton("Training beenden", finishAction)
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,
) )
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
} }