refactor: first step to refactor the ui
This commit is contained in:
parent
a8ed9c9ed1
commit
9cae00d2a5
7 changed files with 330 additions and 158 deletions
75
cmd/main.go
75
cmd/main.go
|
|
@ -4,51 +4,70 @@ import (
|
|||
"log"
|
||||
"path/filepath"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/app"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
|
||||
"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
|
||||
)
|
||||
|
||||
func main() {
|
||||
myApp := app.NewWithID("com.example.kettlebell-tracker")
|
||||
// myApp.Settings().SetTheme(theme.DarkTheme())
|
||||
mainWIndow := myApp.NewWindow("Kettlebell Programm Tracker")
|
||||
// 1. App initialisieren und unser neues Theme setzen
|
||||
myApp := app.NewWithID("com.patani.kettlebell-tracker")
|
||||
myApp.Settings().SetTheme(&theme.KettlebellTheme{}) // <-- Setzt unser benutzerdefiniertes Theme
|
||||
|
||||
mainWindow := myApp.NewWindow("Kettlebell Tracker")
|
||||
|
||||
// 2. Services initialisieren (wie zuvor)
|
||||
dbDir := myApp.Storage().RootURI().Path()
|
||||
dbPath := filepath.Join(dbDir, "kb_training.db")
|
||||
log.Println("Datenbankpfad:", dbPath)
|
||||
|
||||
dbPath := filepath.Join(dbDir, "giant_training.db")
|
||||
dbService, err := data.NewDatabaseService(dbPath)
|
||||
if err != nil {
|
||||
log.Fatalf("Fehler bei der Initialisierung der Datenbank: %v", err)
|
||||
}
|
||||
|
||||
settingsService := services.NewSettingsService(myApp)
|
||||
apiService := services.NewApiService(myApp.UniqueID())
|
||||
|
||||
trainingService := services.NewTrainingService(dbService, settingsService, apiService)
|
||||
|
||||
homeScreen := ui.MakeHomeScreen()
|
||||
settingsScreen := ui.MakeSettingsScreen(settingsService, mainWIndow)
|
||||
historyScreen := ui.MakeHistoryScreen(dbService, mainWIndow)
|
||||
trainingScreen := ui.MakeTrainingScreen(trainingService, settingsService, mainWIndow)
|
||||
// 3. UI-Bildschirme erstellen
|
||||
// Wir erstellen einen Container, der die Bildschirme verwaltet (ähnlich einem Stack)
|
||||
contentContainer := container.NewMax()
|
||||
|
||||
tabs := container.NewAppTabs(
|
||||
container.NewTabItemWithIcon("Home", theme.HomeIcon(), homeScreen),
|
||||
container.NewTabItemWithIcon("Training", theme.MediaPlayIcon(), trainingScreen),
|
||||
container.NewTabItemWithIcon("Historie", theme.HistoryIcon(), historyScreen),
|
||||
container.NewTabItemWithIcon("Einstellungen", theme.SettingsIcon(), settingsScreen),
|
||||
)
|
||||
// Die Bildschirme erstellen und dem Container hinzufügen
|
||||
homeScreen := ui.MakeHomeScreen(trainingService, contentContainer, mainWindow)
|
||||
trainingScreen := ui.MakeTrainingScreen(trainingService, settingsService, mainWindow)
|
||||
historyScreen := ui.MakeHistoryScreen(dbService, mainWindow)
|
||||
settingsScreen := ui.MakeSettingsScreen(settingsService, mainWindow)
|
||||
|
||||
tabs.SetTabLocation(container.TabLocationBottom)
|
||||
contentContainer.Add(homeScreen)
|
||||
contentContainer.Add(trainingScreen)
|
||||
contentContainer.Add(historyScreen)
|
||||
contentContainer.Add(settingsScreen)
|
||||
|
||||
mainWIndow.Resize(fyne.NewSize(400, 600))
|
||||
mainWIndow.SetContent(tabs)
|
||||
mainWIndow.SetMaster()
|
||||
mainWIndow.ShowAndRun()
|
||||
// Initial nur den Home-Bildschirm anzeigen
|
||||
homeScreen.Show()
|
||||
trainingScreen.Hide()
|
||||
historyScreen.Hide()
|
||||
settingsScreen.Hide()
|
||||
|
||||
// 4. Benutzerdefinierte Navigationsleiste erstellen
|
||||
navBar := ui.MakeNavBar(map[string]fyne.CanvasObject{
|
||||
"home": homeScreen,
|
||||
"training": trainingScreen,
|
||||
"history": historyScreen,
|
||||
"settings": settingsScreen,
|
||||
}, contentContainer)
|
||||
|
||||
// 5. Hauptlayout mit Border-Layout erstellen
|
||||
// Der Inhalt ist im Zentrum, die Navigationsleiste am unteren Rand.
|
||||
mainLayout := container.NewBorder(nil, navBar, nil, nil, contentContainer)
|
||||
|
||||
mainWindow.SetContent(mainLayout)
|
||||
mainWindow.Resize(fyne.NewSize(360, 740)) // Mobile-freundliche Größe
|
||||
mainWindow.SetMaster()
|
||||
mainWindow.ShowAndRun()
|
||||
}
|
||||
|
|
|
|||
62
internal/ui/components/navbutton.go
Normal file
62
internal/ui/components/navbutton.go
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
package components
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
|
||||
"git.patanix.de/git/kettlebell-app/internal/ui/theme"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/canvas"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
// NavButton ist unser benutzerdefinierter Navigationsbutton
|
||||
type NavButton struct {
|
||||
widget.BaseWidget
|
||||
icon *canvas.Image
|
||||
label *canvas.Text
|
||||
onTapped func()
|
||||
isActive bool
|
||||
container *fyne.Container
|
||||
}
|
||||
|
||||
func NewNavButton(label string, iconRes fyne.Resource, active bool, tapped func()) *NavButton {
|
||||
icon := canvas.NewImageFromResource(iconRes)
|
||||
icon.FillMode = canvas.ImageFillContain
|
||||
icon.SetMinSize(fyne.NewSize(28, 28))
|
||||
|
||||
text := canvas.NewText(label, color.White)
|
||||
text.TextSize = 12
|
||||
text.Alignment = fyne.TextAlignCenter
|
||||
|
||||
button := &NavButton{
|
||||
icon: icon,
|
||||
label: text,
|
||||
onTapped: tapped,
|
||||
}
|
||||
button.ExtendBaseWidget(button)
|
||||
button.container = container.NewVBox(icon, text)
|
||||
button.SetActive(active)
|
||||
return button
|
||||
}
|
||||
|
||||
func (b *NavButton) CreateRenderer() fyne.WidgetRenderer {
|
||||
return widget.NewSimpleRenderer(b.container)
|
||||
}
|
||||
|
||||
func (b *NavButton) Tapped(*fyne.PointEvent) {
|
||||
b.onTapped()
|
||||
}
|
||||
|
||||
func (b *NavButton) SetActive(active bool) {
|
||||
b.isActive = active
|
||||
if b.isActive {
|
||||
// b.icon.Resource.Color = theme.ColorSky400
|
||||
b.label.Color = theme.ColorSky400
|
||||
} else {
|
||||
// b.icon.Resource.Color = theme.ColorSlate400
|
||||
b.label.Color = theme.ColorSlate400
|
||||
}
|
||||
b.Refresh()
|
||||
}
|
||||
|
|
@ -1,36 +1,57 @@
|
|||
package ui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.patanix.de/git/kettlebell-app/internal/services"
|
||||
|
||||
"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
|
||||
func MakeHomeScreen(ts *services.TrainingService, content *fyne.Container, parent fyne.Window) fyne.CanvasObject {
|
||||
// Header
|
||||
header := container.NewVBox(
|
||||
widget.NewLabel("Hallo,"),
|
||||
widget.NewLabelWithStyle("Patanix", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}), //, Size: 24}),
|
||||
)
|
||||
|
||||
// Zentriert den Inhalt
|
||||
return container.NewCenter(content)
|
||||
// Nächstes Training CTA
|
||||
state := ts.State
|
||||
nextTrainingCard := widget.NewCard(
|
||||
"Nächstes Training",
|
||||
fmt.Sprintf("%s - Tag %d", state.CurrentProgram, state.CurrentBlockDay),
|
||||
container.NewVBox(
|
||||
widget.NewLabel(fmt.Sprintf("Ziel: %d Wiederholungen pro Satz", state.CurrentReps)),
|
||||
widget.NewButton("Training starten", func() {
|
||||
// Wechsle zum Trainingsbildschirm
|
||||
for _, child := range content.Objects {
|
||||
if _, ok := child.(*fyne.Container).Objects[0].(*widget.Card); ok {
|
||||
// Heuristik, um den Trainings-Screen zu finden
|
||||
child.Show()
|
||||
} else {
|
||||
child.Hide()
|
||||
}
|
||||
}
|
||||
content.Refresh()
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
// Letzte Leistung
|
||||
// HINWEIS: Diese Daten müssten aus dem dbService geladen werden.
|
||||
// Hier verwenden wir vorerst Platzhalter.
|
||||
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})),
|
||||
))
|
||||
|
||||
return container.NewVBox(
|
||||
header,
|
||||
widget.NewSeparator(),
|
||||
nextTrainingCard,
|
||||
statsCard,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
45
internal/ui/navbar.go
Normal file
45
internal/ui/navbar.go
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
package ui
|
||||
|
||||
import (
|
||||
"git.patanix.de/git/kettlebell-app/internal/ui/components"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
)
|
||||
|
||||
// MakeNavBar erstellt die benutzerdefinierte Navigationsleiste am unteren Rand.
|
||||
func MakeNavBar(screens map[string]fyne.CanvasObject, content *fyne.Container) fyne.CanvasObject {
|
||||
buttons := make(map[string]*components.NavButton)
|
||||
|
||||
// Funktion zum Umschalten der Ansichten
|
||||
navigateTo := func(name string) {
|
||||
for key, screen := range screens {
|
||||
if key == name {
|
||||
screen.Show()
|
||||
} else {
|
||||
screen.Hide()
|
||||
}
|
||||
}
|
||||
for key, button := range buttons {
|
||||
if key == name {
|
||||
button.SetActive(true)
|
||||
} else {
|
||||
button.SetActive(false)
|
||||
}
|
||||
}
|
||||
content.Refresh()
|
||||
}
|
||||
|
||||
buttons["home"] = components.NewNavButton("Home", theme.HomeIcon(), true, func() { navigateTo("home") })
|
||||
buttons["training"] = components.NewNavButton("Training", theme.MediaPlayIcon(), false, func() { navigateTo("training") })
|
||||
buttons["history"] = components.NewNavButton("Historie", theme.ListIcon(), false, func() { navigateTo("history") })
|
||||
buttons["settings"] = components.NewNavButton("Einstellungen", theme.SettingsIcon(), false, func() { navigateTo("settings") })
|
||||
|
||||
return container.NewGridWithColumns(4,
|
||||
buttons["home"],
|
||||
buttons["training"],
|
||||
buttons["history"],
|
||||
buttons["settings"],
|
||||
)
|
||||
}
|
||||
75
internal/ui/theme/theme.go
Normal file
75
internal/ui/theme/theme.go
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
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
|
||||
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 ColorSlate800
|
||||
case theme.ColorNamePrimary:
|
||||
return ColorSky500
|
||||
case theme.ColorNamePlaceHolder:
|
||||
return ColorSlate400
|
||||
case theme.ColorNameHover:
|
||||
return color.NRGBA{R: 0x33, G: 0x41, B: 0x55, A: 0xff} // Slate 700
|
||||
case theme.ColorNameForeground:
|
||||
return ColorSlate200
|
||||
case theme.ColorNameDisabled:
|
||||
return ColorSlate400
|
||||
case theme.ColorNameError:
|
||||
return ColorRed500
|
||||
case theme.ColorNameInputBackground:
|
||||
return ColorSlate800
|
||||
case theme.ColorNameSeparator:
|
||||
return color.NRGBA{R: 0x33, G: 0x41, B: 0x55, A: 0xff} // Slate 700
|
||||
default:
|
||||
return theme.DefaultTheme().Color(name, variant)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *KettlebellTheme) Icon(name fyne.ThemeIconName) fyne.Resource {
|
||||
// Hier könnten wir benutzerdefinierte Icons laden, wenn nötig.
|
||||
// Vorerst verwenden wir die Standard-Icons.
|
||||
return theme.DefaultTheme().Icon(name)
|
||||
}
|
||||
|
||||
func (t *KettlebellTheme) Font(style fyne.TextStyle) fyne.Resource {
|
||||
// Hier könnten wir eine benutzerdefinierte Schriftart wie 'Inter' laden.
|
||||
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 28
|
||||
default:
|
||||
return theme.DefaultTheme().Size(name)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,6 @@ package ui
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"git.patanix.de/git/kettlebell-app/internal/data"
|
||||
|
|
@ -10,69 +9,46 @@ import (
|
|||
|
||||
"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})
|
||||
// UI-Elemente
|
||||
timerLabel := widget.NewLabelWithStyle("20:00", fyne.TextAlignCenter, fyne.TextStyle{Bold: true})
|
||||
// timerLabel.TextSize = 60
|
||||
|
||||
timerLabel := widget.NewLabelWithStyle("00:00", fyne.TextAlignCenter, fyne.TextStyle{Bold: true})
|
||||
setsLabel := widget.NewLabelWithStyle("0 / 8", fyne.TextAlignCenter, fyne.TextStyle{Bold: true})
|
||||
// setsLabel.TextSize = 48
|
||||
// setsLabel.Color = theme.ColorSky400
|
||||
|
||||
progressBar := widget.NewProgressBar()
|
||||
progressLabel := widget.NewLabelWithStyle("Fortschritt: 0%", fyne.TextAlignCenter, fyne.TextStyle{})
|
||||
repsLabel := widget.NewLabelWithStyle("5 Wiederholungen", fyne.TextAlignCenter, fyne.TextStyle{})
|
||||
// repsLabel.TextSize = 20
|
||||
|
||||
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
|
||||
var mainTimer *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()
|
||||
setsLabel.SetText(fmt.Sprintf("%d / %d", state.SetsDone, state.GoalSets))
|
||||
repsLabel.SetText(fmt.Sprintf("%d Wiederholungen", state.RepsPerSet))
|
||||
}
|
||||
|
||||
stopTimers := func() {
|
||||
finishAction := func() {
|
||||
if mainTimer != nil {
|
||||
mainTimer.Stop()
|
||||
mainTimer = nil
|
||||
}
|
||||
if lastSetTimer != nil {
|
||||
lastSetTimer.Stop()
|
||||
lastSetTimer = nil
|
||||
// ... (Logik zum Speichern wie zuvor) ...
|
||||
session := &data.TrainingSession{
|
||||
Date: time.Now(),
|
||||
Sets: int64(ts.State.SetsDone),
|
||||
WeightLeft: ss.LoadSettings().WeightLeft,
|
||||
WeightRight: ss.LoadSettings().WeightRight,
|
||||
RepsPerSet: int64(ts.State.RepsPerSet),
|
||||
Duration: int64(ts.State.InitialDurationSeconds - ts.State.RemainingSeconds),
|
||||
}
|
||||
ts.FinishTraining(session)
|
||||
fyne.CurrentApp().SendNotification(&fyne.Notification{Title: "Training gespeichert!", Content: "Gut gemacht!"})
|
||||
updateUI()
|
||||
}
|
||||
|
||||
startAction := func() {
|
||||
|
|
@ -84,78 +60,42 @@ func MakeTrainingScreen(ts *services.TrainingService, ss *services.SettingsServi
|
|||
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()
|
||||
finishAction()
|
||||
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(
|
||||
// Layout im "Cockpit"-Stil
|
||||
topPart := 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,
|
||||
)
|
||||
|
||||
middlePart := container.NewVBox(
|
||||
widget.NewLabelWithStyle("Sätze", fyne.TextAlignCenter, fyne.TextStyle{}),
|
||||
setsLabel,
|
||||
repsLabel,
|
||||
)
|
||||
|
||||
finishButton := widget.NewButton("Training beenden", finishAction)
|
||||
// Wir simulieren den roten Button durch die Error-Farbe des Themes
|
||||
finishButton.Importance = widget.HighImportance
|
||||
|
||||
bottomPart := container.NewVBox(
|
||||
widget.NewButton("Satz abschließen", setAction),
|
||||
finishButton,
|
||||
)
|
||||
// HINWEIS: Start-Button fehlt hier, da der Flow vom Home-Screen ausgeht.
|
||||
// Man könnte ihn bei Bedarf hinzufügen.
|
||||
|
||||
return container.NewBorder(topPart, bottomPart, nil, nil, container.NewCenter(middlePart))
|
||||
}
|
||||
|
|
|
|||
10
internal/utils/utils.go
Normal file
10
internal/utils/utils.go
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
package utils
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Hilfsfunktion (kann in einer utils.go Datei platziert werden)
|
||||
func formatDuration(totalSeconds int64) string {
|
||||
mins := totalSeconds / 60
|
||||
secs := totalSeconds % 60
|
||||
return fmt.Sprintf("%02d:%02d", mins, secs)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue