refactor: rebuild the app in golang with the fyne framework
This commit is contained in:
parent
479e4dffa8
commit
a8ed9c9ed1
161 changed files with 1212 additions and 6641 deletions
92
internal/ui/history.go
Normal file
92
internal/ui/history.go
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
package ui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"git.patanix.de/git/kettlebell-app/internal/data"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/dialog"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
func formatDuration(totalSeconds int64) string {
|
||||
mins := totalSeconds / 60
|
||||
secs := totalSeconds % 60
|
||||
return fmt.Sprintf("%02d:%02d", mins, secs)
|
||||
}
|
||||
|
||||
// MakeHistoryScreen erstellt den Bildschirm für die Trainingshistorie.
|
||||
func MakeHistoryScreen(db *data.DatabaseService, parent fyne.Window) fyne.CanvasObject {
|
||||
var history []data.TrainingSession
|
||||
|
||||
// Platzhalter, wenn die Liste leer ist
|
||||
placeholder := widget.NewLabel("Noch keine Trainingsdaten vorhanden.")
|
||||
placeholder.Alignment = fyne.TextAlignCenter
|
||||
|
||||
list := widget.NewList(
|
||||
func() int {
|
||||
return len(history)
|
||||
},
|
||||
func() fyne.CanvasObject {
|
||||
// Template für einen Listeneintrag
|
||||
return widget.NewCard("", "", container.NewVBox(
|
||||
widget.NewLabel(""), // Datum
|
||||
widget.NewSeparator(),
|
||||
widget.NewLabel(""), // Sätze
|
||||
widget.NewLabel(""), // Gewicht
|
||||
widget.NewLabel(""), // Reps
|
||||
widget.NewLabel(""), // Dauer
|
||||
))
|
||||
},
|
||||
func(i widget.ListItemID, o fyne.CanvasObject) {
|
||||
// Template mit Daten füllen
|
||||
session := history[i]
|
||||
card := o.(*widget.Card)
|
||||
|
||||
// Datum als Titel der Karte
|
||||
card.SetTitle(session.Date.Format("02.01.2006 15:04"))
|
||||
|
||||
// Details im Inhalt der Karte
|
||||
box := card.Content.(*fyne.Container)
|
||||
labels := box.Objects
|
||||
labels[0].(*widget.Label).SetText(fmt.Sprintf("Programm: %s - Tag %d", session.Program, session.BlockDay))
|
||||
labels[2].(*widget.Label).SetText(fmt.Sprintf("Sätze: %d", session.Sets))
|
||||
labels[3].(*widget.Label).SetText(fmt.Sprintf("Kettlebells: %.1fkg / %.1fkg", session.WeightLeft, session.WeightRight))
|
||||
labels[4].(*widget.Label).SetText(fmt.Sprintf("Reps pro Satz: %d", session.RepsPerSet))
|
||||
labels[5].(*widget.Label).SetText(fmt.Sprintf("Dauer: %s", formatDuration(session.Duration)))
|
||||
},
|
||||
)
|
||||
|
||||
// Funktion zum Neuladen der Daten
|
||||
refreshData := func() {
|
||||
var err error
|
||||
history, err = db.GetHistory()
|
||||
if err != nil {
|
||||
log.Printf("Fehler beim Laden der Historie: %v", err)
|
||||
dialog.ShowError(err, parent)
|
||||
}
|
||||
|
||||
if len(history) == 0 {
|
||||
placeholder.Show()
|
||||
list.Hide()
|
||||
} else {
|
||||
placeholder.Hide()
|
||||
list.Show()
|
||||
}
|
||||
list.Refresh()
|
||||
}
|
||||
|
||||
// Initiales Laden
|
||||
refreshData()
|
||||
|
||||
// Toolbar mit Refresh-Button
|
||||
toolbar := widget.NewToolbar(
|
||||
widget.NewToolbarAction(theme.ViewRefreshIcon(), refreshData),
|
||||
)
|
||||
|
||||
return container.NewBorder(toolbar, nil, nil, nil, container.NewStack(list, placeholder))
|
||||
}
|
||||
36
internal/ui/home.go
Normal file
36
internal/ui/home.go
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
package ui
|
||||
|
||||
import (
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/canvas"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
// MakeHomeScreen erstellt den statischen Willkommensbildschirm.
|
||||
func MakeHomeScreen() fyne.CanvasObject {
|
||||
primaryColor := theme.PrimaryColor()
|
||||
|
||||
title := canvas.NewText("Willkommen beim Giant Programm Tracker!", primaryColor)
|
||||
title.TextStyle.Bold = true
|
||||
title.Alignment = fyne.TextAlignCenter
|
||||
title.TextSize = 24
|
||||
|
||||
subtitle := widget.NewLabel("Verwalte deine Kettlebell-Trainings effizient.")
|
||||
subtitle.Alignment = fyne.TextAlignCenter
|
||||
|
||||
icon := widget.NewIcon(theme.MediaPlayIcon())
|
||||
icon.Resize(fyne.NewSize(150, 150))
|
||||
|
||||
// Layout erstellen, das dem Flutter-Layout entspricht
|
||||
content := container.NewVBox(
|
||||
title,
|
||||
widget.NewSeparator(),
|
||||
subtitle,
|
||||
container.NewPadded(icon), // Icon mit etwas Abstand
|
||||
)
|
||||
|
||||
// Zentriert den Inhalt
|
||||
return container.NewCenter(content)
|
||||
}
|
||||
64
internal/ui/settings.go
Normal file
64
internal/ui/settings.go
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
package ui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"git.patanix.de/git/kettlebell-app/internal/services"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/dialog"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
func MakeSettingsScreen(settingsService *services.SettingsService, parent fyne.Window) fyne.CanvasObject {
|
||||
currentSettings := settingsService.LoadSettings()
|
||||
|
||||
trainingTimeEntry := widget.NewEntry()
|
||||
trainingTimeEntry.SetText(fmt.Sprintf("%d", currentSettings.TrainingTimeMinutes))
|
||||
|
||||
weightLeftEntry := widget.NewEntry()
|
||||
weightLeftEntry.SetText(fmt.Sprintf("%.1f", currentSettings.WeightLeft))
|
||||
|
||||
weightRightEntry := widget.NewEntry()
|
||||
weightRightEntry.SetText(fmt.Sprintf("%.1f", currentSettings.WeightRight))
|
||||
|
||||
goalSetsEntry := widget.NewEntry()
|
||||
goalSetsEntry.SetText(fmt.Sprintf("%d", currentSettings.GoalSets))
|
||||
|
||||
form := &widget.Form{
|
||||
Items: []*widget.FormItem{
|
||||
{Text: "Trainingszeit (Minuten)", Widget: trainingTimeEntry},
|
||||
{Text: "Linke Kettlebell (kg)", Widget: weightLeftEntry},
|
||||
{Text: "Rechte Kettlebell (kg)", Widget: weightRightEntry},
|
||||
{Text: "Ziel-Sätze", Widget: goalSetsEntry},
|
||||
},
|
||||
OnSubmit: func() {
|
||||
timeMin, err1 := strconv.Atoi(trainingTimeEntry.Text)
|
||||
weightL, err2 := strconv.ParseFloat(weightLeftEntry.Text, 64)
|
||||
weightR, err3 := strconv.ParseFloat(weightRightEntry.Text, 64)
|
||||
goal, err4 := strconv.Atoi(goalSetsEntry.Text)
|
||||
|
||||
if err1 != nil || err2 != nil || err3 != nil || err4 != nil {
|
||||
dialog.ShowError(fmt.Errorf("Bitte gib gültige Zahlen ein"), parent)
|
||||
return
|
||||
}
|
||||
|
||||
newSettings := &services.Settings{
|
||||
TrainingTimeMinutes: timeMin,
|
||||
WeightLeft: weightL,
|
||||
WeightRight: weightR,
|
||||
GoalSets: goal,
|
||||
}
|
||||
settingsService.SaveSettings(newSettings)
|
||||
|
||||
fyne.CurrentApp().SendNotification(&fyne.Notification{
|
||||
Title: "Gespeichert",
|
||||
Content: "Die Einstellungen wurden erfolgreich aktualisiert.",
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
return container.NewPadded(form)
|
||||
}
|
||||
161
internal/ui/training.go
Normal file
161
internal/ui/training.go
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
package ui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"git.patanix.de/git/kettlebell-app/internal/data"
|
||||
"git.patanix.de/git/kettlebell-app/internal/services"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/dialog"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
func MakeTrainingScreen(ts *services.TrainingService, ss *services.SettingsService, parent fyne.Window) fyne.CanvasObject {
|
||||
programLabel := widget.NewLabelWithStyle("", fyne.TextAlignCenter, fyne.TextStyle{Bold: true})
|
||||
blockDayLabel := widget.NewLabelWithStyle("", fyne.TextAlignCenter, fyne.TextStyle{})
|
||||
repsLabel := widget.NewLabelWithStyle("", fyne.TextAlignCenter, fyne.TextStyle{Bold: true})
|
||||
|
||||
timerLabel := widget.NewLabelWithStyle("00:00", fyne.TextAlignCenter, fyne.TextStyle{Bold: true})
|
||||
|
||||
progressBar := widget.NewProgressBar()
|
||||
progressLabel := widget.NewLabelWithStyle("Fortschritt: 0%", fyne.TextAlignCenter, fyne.TextStyle{})
|
||||
|
||||
var startButton, setButton, finishButton *widget.Button
|
||||
|
||||
setHistoryList := widget.NewList(
|
||||
func() int {
|
||||
return len(ts.State.SetTimes)
|
||||
},
|
||||
func() fyne.CanvasObject {
|
||||
return widget.NewLabel("")
|
||||
},
|
||||
func(id widget.ListItemID, obj fyne.CanvasObject) {
|
||||
t := ts.State.SetTimes[id]
|
||||
obj.(*widget.Label).SetText(fmt.Sprintf("#%d um %s (%d Reps)", id+1, t.Format("15:04:05"), ts.State.CurrentReps))
|
||||
},
|
||||
)
|
||||
|
||||
var mainTimer, lastSetTimer *time.Ticker
|
||||
|
||||
updateUI := func() {
|
||||
state := ts.State
|
||||
programLabel.SetText(state.CurrentProgram)
|
||||
blockDayLabel.SetText(fmt.Sprintf("Block Tag: %d", state.CurrentBlockDay))
|
||||
repsLabel.SetText(fmt.Sprintf("Reps pro Satz: %d", state.CurrentReps))
|
||||
|
||||
timerLabel.SetText(formatDuration(int64(state.RemainingSeconds)))
|
||||
progressBar.SetValue(state.Progress)
|
||||
progressLabel.SetText(fmt.Sprintf("Fortschritt: %.0f%%", state.Progress*100))
|
||||
|
||||
if state.IsTrainingRunning {
|
||||
startButton.Disable()
|
||||
setButton.Enable()
|
||||
finishButton.Enable()
|
||||
} else {
|
||||
startButton.Enable()
|
||||
setButton.Disable()
|
||||
finishButton.Disable()
|
||||
}
|
||||
setHistoryList.Refresh()
|
||||
}
|
||||
|
||||
stopTimers := func() {
|
||||
if mainTimer != nil {
|
||||
mainTimer.Stop()
|
||||
mainTimer = nil
|
||||
}
|
||||
if lastSetTimer != nil {
|
||||
lastSetTimer.Stop()
|
||||
lastSetTimer = nil
|
||||
}
|
||||
}
|
||||
|
||||
startAction := func() {
|
||||
settings := ss.LoadSettings()
|
||||
ts.StartTraining(settings.TrainingTimeMinutes, settings.GoalSets)
|
||||
updateUI()
|
||||
|
||||
mainTimer = time.NewTicker(time.Second)
|
||||
go func() {
|
||||
for range mainTimer.C {
|
||||
if ts.State.RemainingSeconds <= 0 {
|
||||
stopTimers()
|
||||
fyne.CurrentApp().SendNotification(&fyne.Notification{
|
||||
Title: "Zeit abgelaufen!",
|
||||
Content: "Training wird automatisch gespeichert.",
|
||||
})
|
||||
finishButton.OnTapped()
|
||||
return
|
||||
}
|
||||
ts.Tick()
|
||||
updateUI()
|
||||
}
|
||||
}()
|
||||
|
||||
lastSetTimer = time.NewTicker(time.Second)
|
||||
go func() {
|
||||
for range lastSetTimer.C {
|
||||
ts.TickLastSetTimer()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
setAction := func() {
|
||||
ts.CompleteSet()
|
||||
fyne.CurrentApp().SendNotification(&fyne.Notification{Title: "Satz gespeichert!", Content: ""})
|
||||
updateUI()
|
||||
}
|
||||
|
||||
finishAction := func() {
|
||||
stopTimers()
|
||||
settings := ss.LoadSettings()
|
||||
state := ts.State
|
||||
|
||||
session := &data.TrainingSession{
|
||||
Date: time.Now(),
|
||||
Sets: int64(state.SetsDone),
|
||||
WeightLeft: settings.WeightLeft,
|
||||
WeightRight: settings.WeightRight,
|
||||
RepsPerSet: int64(state.RepsPerSet),
|
||||
Duration: int64(state.InitialDurationSeconds - state.RemainingSeconds),
|
||||
}
|
||||
|
||||
if err := ts.FinishTraining(session); err != nil {
|
||||
dialog.ShowError(err, parent)
|
||||
log.Printf("Fehler beim Speichern des Trainings: %v", err)
|
||||
} else {
|
||||
fyne.CurrentApp().SendNotification(&fyne.Notification{Title: "Training gespeichert!", Content: "Gut gemacht!"})
|
||||
}
|
||||
|
||||
updateUI()
|
||||
}
|
||||
|
||||
startButton = widget.NewButtonWithIcon("Start", theme.MediaPlayIcon(), startAction)
|
||||
setButton = widget.NewButtonWithIcon("Satz", theme.ConfirmIcon(), setAction)
|
||||
finishButton = widget.NewButtonWithIcon("Beenden", theme.MediaStopIcon(), finishAction)
|
||||
|
||||
updateUI()
|
||||
|
||||
headerCard := widget.NewCard("", "", container.NewVBox(programLabel, blockDayLabel, repsLabel))
|
||||
timerCard := widget.NewCard("", "", container.NewVBox(
|
||||
widget.NewLabelWithStyle("Verbleibende Zeit", fyne.TextAlignCenter, fyne.TextStyle{}),
|
||||
timerLabel,
|
||||
progressBar,
|
||||
progressLabel,
|
||||
))
|
||||
|
||||
actionButtons := container.NewGridWithColumns(3, startButton, setButton, finishButton)
|
||||
historyCard := widget.NewCard("Satz-Historie", "", setHistoryList)
|
||||
|
||||
return container.NewVBox(
|
||||
headerCard,
|
||||
timerCard,
|
||||
actionButtons,
|
||||
historyCard,
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue