refactor: ui refactor step 2
This commit is contained in:
parent
9cae00d2a5
commit
24430d0fae
9 changed files with 167 additions and 121 deletions
34
cmd/main.go
34
cmd/main.go
|
|
@ -34,36 +34,36 @@ func main() {
|
||||||
trainingService := services.NewTrainingService(dbService, settingsService, apiService)
|
trainingService := services.NewTrainingService(dbService, settingsService, apiService)
|
||||||
|
|
||||||
// 3. UI-Bildschirme erstellen
|
// 3. UI-Bildschirme erstellen
|
||||||
// Wir erstellen einen Container, der die Bildschirme verwaltet (ähnlich einem Stack)
|
|
||||||
contentContainer := container.NewMax()
|
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
|
// Die Bildschirme erstellen und dem Container hinzufügen
|
||||||
homeScreen := ui.MakeHomeScreen(trainingService, contentContainer, mainWindow)
|
homeScreen := ui.MakeHomeScreen(trainingService, func() { navigateTo("training") })
|
||||||
trainingScreen := ui.MakeTrainingScreen(trainingService, settingsService, mainWindow)
|
trainingScreen := ui.MakeTrainingScreen(trainingService, settingsService, mainWindow)
|
||||||
historyScreen := ui.MakeHistoryScreen(dbService, mainWindow)
|
historyScreen := ui.MakeHistoryScreen(dbService, mainWindow)
|
||||||
settingsScreen := ui.MakeSettingsScreen(settingsService, mainWindow)
|
settingsScreen := ui.MakeSettingsScreen(settingsService, mainWindow)
|
||||||
|
|
||||||
contentContainer.Add(homeScreen)
|
screens := map[string]fyne.CanvasObject{
|
||||||
contentContainer.Add(trainingScreen)
|
|
||||||
contentContainer.Add(historyScreen)
|
|
||||||
contentContainer.Add(settingsScreen)
|
|
||||||
|
|
||||||
// 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,
|
"home": homeScreen,
|
||||||
"training": trainingScreen,
|
"training": trainingScreen,
|
||||||
"history": historyScreen,
|
"history": historyScreen,
|
||||||
"settings": settingsScreen,
|
"settings": settingsScreen,
|
||||||
}, contentContainer)
|
}
|
||||||
|
|
||||||
|
for _, s := range screens {
|
||||||
|
contentContainer.Add(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Benutzerdefinierte Navigationsleiste erstellen
|
||||||
|
navBar, navigateFunc := ui.MakeNavBar(screens, contentContainer)
|
||||||
|
navigateTo = navigateFunc // Weisen der Navigationsfunktion die Implementierung aus der Navbar zu
|
||||||
|
|
||||||
|
// Initial den Home-Bildschirm anzeigen
|
||||||
|
navigateTo("home")
|
||||||
|
|
||||||
// 5. Hauptlayout mit Border-Layout erstellen
|
// 5. Hauptlayout mit Border-Layout erstellen
|
||||||
// Der Inhalt ist im Zentrum, die Navigationsleiste am unteren Rand.
|
|
||||||
mainLayout := container.NewBorder(nil, navBar, nil, nil, contentContainer)
|
mainLayout := container.NewBorder(nil, navBar, nil, nil, contentContainer)
|
||||||
|
|
||||||
mainWindow.SetContent(mainLayout)
|
mainWindow.SetContent(mainLayout)
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
"git.patanix.de/git/kettlebell-app/internal/data"
|
"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"
|
||||||
|
|
@ -13,55 +14,42 @@ import (
|
||||||
"fyne.io/fyne/v2/widget"
|
"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 {
|
func MakeHistoryScreen(db *data.DatabaseService, parent fyne.Window) fyne.CanvasObject {
|
||||||
var history []data.TrainingSession
|
var history []data.TrainingSession
|
||||||
|
|
||||||
// Platzhalter, wenn die Liste leer ist
|
|
||||||
placeholder := widget.NewLabel("Noch keine Trainingsdaten vorhanden.")
|
placeholder := widget.NewLabel("Noch keine Trainingsdaten vorhanden.")
|
||||||
placeholder.Alignment = fyne.TextAlignCenter
|
|
||||||
|
|
||||||
list := widget.NewList(
|
list := widget.NewList(
|
||||||
func() int {
|
func() int {
|
||||||
return len(history)
|
return len(history)
|
||||||
},
|
},
|
||||||
func() fyne.CanvasObject {
|
func() fyne.CanvasObject {
|
||||||
// Template für einen Listeneintrag
|
|
||||||
return widget.NewCard("", "", container.NewVBox(
|
return widget.NewCard("", "", container.NewVBox(
|
||||||
widget.NewLabel(""), // Datum
|
widget.NewLabel(""),
|
||||||
widget.NewSeparator(),
|
widget.NewSeparator(),
|
||||||
widget.NewLabel(""), // Sätze
|
container.NewGridWithColumns(3,
|
||||||
widget.NewLabel(""), // Gewicht
|
container.NewVBox(widget.NewLabel("Sätze"), widget.NewLabelWithStyle("", fyne.TextAlignCenter, fyne.TextStyle{Bold: true})),
|
||||||
widget.NewLabel(""), // Reps
|
container.NewVBox(widget.NewLabel("Dauer"), widget.NewLabelWithStyle("", fyne.TextAlignCenter, fyne.TextStyle{Bold: true})),
|
||||||
widget.NewLabel(""), // Dauer
|
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) {
|
||||||
// Template mit Daten füllen
|
|
||||||
session := history[i]
|
session := history[i]
|
||||||
card := o.(*widget.Card)
|
card := o.(*widget.Card)
|
||||||
|
|
||||||
// Datum als Titel der Karte
|
|
||||||
card.SetTitle(session.Date.Format("02.01.2006 15:04"))
|
card.SetTitle(session.Date.Format("02.01.2006 15:04"))
|
||||||
|
|
||||||
// Details im Inhalt der Karte
|
content := card.Content.(*fyne.Container)
|
||||||
box := card.Content.(*fyne.Container)
|
programLabel := content.Objects[0].(*widget.Label)
|
||||||
labels := box.Objects
|
statsGrid := content.Objects[2].(*fyne.Container)
|
||||||
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))
|
programLabel.SetText(fmt.Sprintf("%s - Tag %d", session.Program, session.BlockDay))
|
||||||
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))
|
statsGrid.Objects[0].(*fyne.Container).Objects[1].(*widget.Label).SetText(fmt.Sprintf("%d", session.Sets))
|
||||||
labels[5].(*widget.Label).SetText(fmt.Sprintf("Dauer: %s", 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))
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
// Funktion zum Neuladen der Daten
|
|
||||||
refreshData := func() {
|
refreshData := func() {
|
||||||
var err error
|
var err error
|
||||||
history, err = db.GetHistory()
|
history, err = db.GetHistory()
|
||||||
|
|
@ -69,24 +57,31 @@ func MakeHistoryScreen(db *data.DatabaseService, parent fyne.Window) fyne.Canvas
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(history) == 0 {
|
if len(history) == 0 {
|
||||||
placeholder.Show()
|
|
||||||
list.Hide()
|
list.Hide()
|
||||||
|
placeholder.Show()
|
||||||
} else {
|
} else {
|
||||||
placeholder.Hide()
|
|
||||||
list.Show()
|
list.Show()
|
||||||
|
placeholder.Hide()
|
||||||
}
|
}
|
||||||
list.Refresh()
|
list.Refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initiales Laden
|
list.OnSelected = func(id widget.ListItemID) {
|
||||||
refreshData()
|
list.Unselect(id)
|
||||||
|
}
|
||||||
|
|
||||||
// Toolbar mit Refresh-Button
|
|
||||||
toolbar := widget.NewToolbar(
|
toolbar := widget.NewToolbar(
|
||||||
widget.NewToolbarAction(theme.ViewRefreshIcon(), refreshData),
|
widget.NewToolbarAction(theme.ViewRefreshIcon(), refreshData),
|
||||||
)
|
)
|
||||||
|
|
||||||
return container.NewBorder(toolbar, nil, nil, nil, container.NewStack(list, placeholder))
|
layout := container.NewBorder(toolbar, nil, nil, nil, container.NewStack(list, container.NewCenter(placeholder)))
|
||||||
|
|
||||||
|
wrapper := container.NewMax(layout)
|
||||||
|
if wrapper.Visible() {
|
||||||
|
refreshData()
|
||||||
|
}
|
||||||
|
// wrapper.OnVisible = refreshData
|
||||||
|
|
||||||
|
return wrapper
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,54 +4,51 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"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"
|
||||||
|
|
||||||
"fyne.io/fyne/v2"
|
"fyne.io/fyne/v2"
|
||||||
|
"fyne.io/fyne/v2/canvas"
|
||||||
"fyne.io/fyne/v2/container"
|
"fyne.io/fyne/v2/container"
|
||||||
"fyne.io/fyne/v2/widget"
|
"fyne.io/fyne/v2/widget"
|
||||||
)
|
)
|
||||||
|
|
||||||
func MakeHomeScreen(ts *services.TrainingService, content *fyne.Container, parent fyne.Window) fyne.CanvasObject {
|
func MakeHomeScreen(ts *services.TrainingService, onStart func()) fyne.CanvasObject {
|
||||||
// Header
|
// Header
|
||||||
|
headerTitle := canvas.NewText("Patanix", theme.ColorSlate200)
|
||||||
|
headerTitle.TextSize = 28
|
||||||
|
headerTitle.TextStyle.Bold = true
|
||||||
|
|
||||||
header := container.NewVBox(
|
header := container.NewVBox(
|
||||||
widget.NewLabel("Hallo,"),
|
widget.NewLabel("Hallo,"),
|
||||||
widget.NewLabelWithStyle("Patanix", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}), //, Size: 24}),
|
headerTitle,
|
||||||
)
|
)
|
||||||
|
|
||||||
// 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),
|
||||||
container.NewVBox(
|
container.NewVBox(
|
||||||
widget.NewLabel(fmt.Sprintf("Ziel: %d Wiederholungen pro Satz", state.CurrentReps)),
|
widget.NewLabel(fmt.Sprintf("Ziel: %d Wiederholungen pro Satz", state.CurrentReps)),
|
||||||
widget.NewButton("Training starten", func() {
|
widget.NewButton("Training starten", onStart),
|
||||||
// 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
|
// 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,
|
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"), 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("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("Gewicht"), widget.NewLabelWithStyle("16kg", fyne.TextAlignCenter, fyne.TextStyle{Bold: true})),
|
||||||
))
|
))
|
||||||
|
|
||||||
return container.NewVBox(
|
layout := container.NewVBox(
|
||||||
header,
|
header,
|
||||||
widget.NewSeparator(),
|
widget.NewSeparator(),
|
||||||
nextTrainingCard,
|
nextTrainingCard,
|
||||||
statsCard,
|
statsCard,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return container.NewPadded(layout)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,38 +8,38 @@ import (
|
||||||
"fyne.io/fyne/v2/theme"
|
"fyne.io/fyne/v2/theme"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MakeNavBar erstellt die benutzerdefinierte Navigationsleiste am unteren Rand.
|
// MakeNavBar erstellt die benutzerdefinierte Navigationsleiste und gibt die Navigationsfunktion zurück.
|
||||||
func MakeNavBar(screens map[string]fyne.CanvasObject, content *fyne.Container) fyne.CanvasObject {
|
func MakeNavBar(screens map[string]fyne.CanvasObject, content *fyne.Container) (fyne.CanvasObject, func(string)) {
|
||||||
buttons := make(map[string]*components.NavButton)
|
buttons := make(map[string]*components.NavButton)
|
||||||
|
|
||||||
// Funktion zum Umschalten der Ansichten
|
// Funktion zum Umschalten der Ansichten
|
||||||
navigateTo := func(name string) {
|
navigateTo := func(name string) {
|
||||||
for key, screen := range screens {
|
for key, screen := range screens {
|
||||||
|
screen.Hide()
|
||||||
if key == name {
|
if key == name {
|
||||||
screen.Show()
|
screen.Show()
|
||||||
} else {
|
|
||||||
screen.Hide()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for key, button := range buttons {
|
for key, button := range buttons {
|
||||||
|
button.SetActive(false)
|
||||||
if key == name {
|
if key == name {
|
||||||
button.SetActive(true)
|
button.SetActive(true)
|
||||||
} else {
|
|
||||||
button.SetActive(false)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
content.Refresh()
|
content.Refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
buttons["home"] = components.NewNavButton("Home", theme.HomeIcon(), true, func() { navigateTo("home") })
|
buttons["home"] = components.NewNavButton("Home", theme.HomeIcon(), false, func() { navigateTo("home") })
|
||||||
buttons["training"] = components.NewNavButton("Training", theme.MediaPlayIcon(), false, func() { navigateTo("training") })
|
buttons["training"] = components.NewNavButton("Training", theme.MediaPlayIcon(), false, func() { navigateTo("training") })
|
||||||
buttons["history"] = components.NewNavButton("Historie", theme.ListIcon(), false, func() { navigateTo("history") })
|
buttons["history"] = components.NewNavButton("Historie", theme.ListIcon(), false, func() { navigateTo("history") })
|
||||||
buttons["settings"] = components.NewNavButton("Einstellungen", theme.SettingsIcon(), false, func() { navigateTo("settings") })
|
buttons["settings"] = components.NewNavButton("Einstellungen", theme.SettingsIcon(), false, func() { navigateTo("settings") })
|
||||||
|
|
||||||
return container.NewGridWithColumns(4,
|
navContainer := container.NewGridWithColumns(4,
|
||||||
buttons["home"],
|
buttons["home"],
|
||||||
buttons["training"],
|
buttons["training"],
|
||||||
buttons["history"],
|
buttons["history"],
|
||||||
buttons["settings"],
|
buttons["settings"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return navContainer, navigateTo
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,48 +8,57 @@ import (
|
||||||
|
|
||||||
"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/widget"
|
"fyne.io/fyne/v2/widget"
|
||||||
)
|
)
|
||||||
|
|
||||||
func MakeSettingsScreen(settingsService *services.SettingsService, parent fyne.Window) fyne.CanvasObject {
|
func MakeSettingsScreen(settingsService *services.SettingsService, parent fyne.Window) fyne.CanvasObject {
|
||||||
currentSettings := settingsService.LoadSettings()
|
currentSettings := settingsService.LoadSettings()
|
||||||
|
|
||||||
trainingTimeEntry := widget.NewEntry()
|
timeEntry := widget.NewEntry()
|
||||||
trainingTimeEntry.SetText(fmt.Sprintf("%d", currentSettings.TrainingTimeMinutes))
|
timeEntry.SetText(fmt.Sprintf("%d", currentSettings.TrainingTimeMinutes))
|
||||||
|
timeEntry.Validator = func(s string) error {
|
||||||
|
if _, err := strconv.Atoi(s); err != nil {
|
||||||
|
return fmt.Errorf("muss eine Zahl sein")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
setsEntry := widget.NewEntry()
|
||||||
|
setsEntry.SetText(fmt.Sprintf("%d", currentSettings.GoalSets))
|
||||||
|
setsEntry.Validator = timeEntry.Validator // Gleicher Validator
|
||||||
|
|
||||||
weightLeftEntry := widget.NewEntry()
|
weightLeftEntry := widget.NewEntry()
|
||||||
weightLeftEntry.SetText(fmt.Sprintf("%.1f", currentSettings.WeightLeft))
|
weightLeftEntry.SetText(fmt.Sprintf("%.1f", currentSettings.WeightLeft))
|
||||||
|
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 := widget.NewEntry()
|
||||||
weightRightEntry.SetText(fmt.Sprintf("%.1f", currentSettings.WeightRight))
|
weightRightEntry.SetText(fmt.Sprintf("%.1f", currentSettings.WeightRight))
|
||||||
|
weightRightEntry.Validator = weightLeftEntry.Validator // Gleicher Validator
|
||||||
goalSetsEntry := widget.NewEntry()
|
|
||||||
goalSetsEntry.SetText(fmt.Sprintf("%d", currentSettings.GoalSets))
|
|
||||||
|
|
||||||
form := &widget.Form{
|
form := &widget.Form{
|
||||||
Items: []*widget.FormItem{
|
Items: []*widget.FormItem{
|
||||||
{Text: "Trainingszeit (Minuten)", Widget: trainingTimeEntry},
|
{Text: "Trainingszeit (Minuten)", Widget: timeEntry},
|
||||||
{Text: "Linke Kettlebell (kg)", Widget: weightLeftEntry},
|
{Text: "Ziel-Sätze", Widget: setsEntry},
|
||||||
{Text: "Rechte Kettlebell (kg)", Widget: weightRightEntry},
|
{Text: "Links (kg)", Widget: weightLeftEntry},
|
||||||
{Text: "Ziel-Sätze", Widget: goalSetsEntry},
|
{Text: "Rechts (kg)", Widget: weightRightEntry},
|
||||||
},
|
},
|
||||||
OnSubmit: func() {
|
OnSubmit: func() {
|
||||||
timeMin, err1 := strconv.Atoi(trainingTimeEntry.Text)
|
timeMin, _ := strconv.Atoi(timeEntry.Text)
|
||||||
weightL, err2 := strconv.ParseFloat(weightLeftEntry.Text, 64)
|
goal, _ := strconv.Atoi(setsEntry.Text)
|
||||||
weightR, err3 := strconv.ParseFloat(weightRightEntry.Text, 64)
|
weightL, _ := strconv.ParseFloat(weightLeftEntry.Text, 64)
|
||||||
goal, err4 := strconv.Atoi(goalSetsEntry.Text)
|
weightR, _ := strconv.ParseFloat(weightRightEntry.Text, 64)
|
||||||
|
|
||||||
if err1 != nil || err2 != nil || err3 != nil || err4 != nil {
|
|
||||||
dialog.ShowError(fmt.Errorf("Bitte gib gültige Zahlen ein"), parent)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
newSettings := &services.Settings{
|
newSettings := &services.Settings{
|
||||||
TrainingTimeMinutes: timeMin,
|
TrainingTimeMinutes: timeMin,
|
||||||
|
GoalSets: goal,
|
||||||
WeightLeft: weightL,
|
WeightLeft: weightL,
|
||||||
WeightRight: weightR,
|
WeightRight: weightR,
|
||||||
GoalSets: goal,
|
InitialProgram: currentSettings.InitialProgram, // Beibehalten
|
||||||
}
|
}
|
||||||
settingsService.SaveSettings(newSettings)
|
settingsService.SaveSettings(newSettings)
|
||||||
|
|
||||||
|
|
@ -60,5 +69,14 @@ func MakeSettingsScreen(settingsService *services.SettingsService, parent fyne.W
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return container.NewPadded(form)
|
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,
|
||||||
|
)
|
||||||
|
|
||||||
|
return container.NewPadded(layout)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import (
|
||||||
var (
|
var (
|
||||||
ColorSlate900 = color.NRGBA{R: 0x0f, G: 0x17, B: 0x2a, A: 0xff} // bg-slate-900
|
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
|
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
|
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
|
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
|
ColorSky500 = color.NRGBA{R: 0x0e, G: 0xa5, B: 0xe9, A: 0xff} // bg-sky-500
|
||||||
|
|
@ -28,13 +29,13 @@ func (t *KettlebellTheme) Color(name fyne.ThemeColorName, variant fyne.ThemeVari
|
||||||
case theme.ColorNameButton:
|
case theme.ColorNameButton:
|
||||||
return ColorSlate800
|
return ColorSlate800
|
||||||
case theme.ColorNameDisabledButton:
|
case theme.ColorNameDisabledButton:
|
||||||
return ColorSlate800
|
return ColorSlate700
|
||||||
case theme.ColorNamePrimary:
|
case theme.ColorNamePrimary:
|
||||||
return ColorSky500
|
return ColorSky500
|
||||||
case theme.ColorNamePlaceHolder:
|
case theme.ColorNamePlaceHolder:
|
||||||
return ColorSlate400
|
return ColorSlate400
|
||||||
case theme.ColorNameHover:
|
case theme.ColorNameHover:
|
||||||
return color.NRGBA{R: 0x33, G: 0x41, B: 0x55, A: 0xff} // Slate 700
|
return ColorSlate700
|
||||||
case theme.ColorNameForeground:
|
case theme.ColorNameForeground:
|
||||||
return ColorSlate200
|
return ColorSlate200
|
||||||
case theme.ColorNameDisabled:
|
case theme.ColorNameDisabled:
|
||||||
|
|
@ -44,20 +45,17 @@ func (t *KettlebellTheme) Color(name fyne.ThemeColorName, variant fyne.ThemeVari
|
||||||
case theme.ColorNameInputBackground:
|
case theme.ColorNameInputBackground:
|
||||||
return ColorSlate800
|
return ColorSlate800
|
||||||
case theme.ColorNameSeparator:
|
case theme.ColorNameSeparator:
|
||||||
return color.NRGBA{R: 0x33, G: 0x41, B: 0x55, A: 0xff} // Slate 700
|
return ColorSlate700
|
||||||
default:
|
default:
|
||||||
return theme.DefaultTheme().Color(name, variant)
|
return theme.DefaultTheme().Color(name, variant)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *KettlebellTheme) Icon(name fyne.ThemeIconName) fyne.Resource {
|
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)
|
return theme.DefaultTheme().Icon(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *KettlebellTheme) Font(style fyne.TextStyle) fyne.Resource {
|
func (t *KettlebellTheme) Font(style fyne.TextStyle) fyne.Resource {
|
||||||
// Hier könnten wir eine benutzerdefinierte Schriftart wie 'Inter' laden.
|
|
||||||
return theme.DefaultTheme().Font(style)
|
return theme.DefaultTheme().Font(style)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -68,7 +66,9 @@ func (t *KettlebellTheme) Size(name fyne.ThemeSizeName) float32 {
|
||||||
case theme.SizeNameText:
|
case theme.SizeNameText:
|
||||||
return 16
|
return 16
|
||||||
case theme.SizeNameHeadingText:
|
case theme.SizeNameHeadingText:
|
||||||
return 28
|
return 24
|
||||||
|
case theme.SizeNameSubHeadingText:
|
||||||
|
return 20
|
||||||
default:
|
default:
|
||||||
return theme.DefaultTheme().Size(name)
|
return theme.DefaultTheme().Size(name)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,38 +6,56 @@ import (
|
||||||
|
|
||||||
"git.patanix.de/git/kettlebell-app/internal/data"
|
"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/utils"
|
||||||
|
|
||||||
"fyne.io/fyne/v2"
|
"fyne.io/fyne/v2"
|
||||||
|
"fyne.io/fyne/v2/canvas"
|
||||||
"fyne.io/fyne/v2/container"
|
"fyne.io/fyne/v2/container"
|
||||||
"fyne.io/fyne/v2/widget"
|
"fyne.io/fyne/v2/widget"
|
||||||
)
|
)
|
||||||
|
|
||||||
func MakeTrainingScreen(ts *services.TrainingService, ss *services.SettingsService, parent fyne.Window) fyne.CanvasObject {
|
func MakeTrainingScreen(ts *services.TrainingService, ss *services.SettingsService, parent fyne.Window) fyne.CanvasObject {
|
||||||
// UI-Elemente
|
// UI-Elemente mit canvas.Text für die Größensteuerung
|
||||||
timerLabel := widget.NewLabelWithStyle("20:00", fyne.TextAlignCenter, fyne.TextStyle{Bold: true})
|
timerLabel := canvas.NewText("20:00", theme.ColorSlate200)
|
||||||
// timerLabel.TextSize = 60
|
timerLabel.TextSize = 60
|
||||||
|
timerLabel.TextStyle.Bold = true
|
||||||
|
timerLabel.Alignment = fyne.TextAlignCenter
|
||||||
|
|
||||||
setsLabel := widget.NewLabelWithStyle("0 / 8", fyne.TextAlignCenter, fyne.TextStyle{Bold: true})
|
setsLabel := canvas.NewText("0 / 8", theme.ColorSky400)
|
||||||
// setsLabel.TextSize = 48
|
setsLabel.TextSize = 48
|
||||||
// setsLabel.Color = theme.ColorSky400
|
setsLabel.TextStyle.Bold = true
|
||||||
|
setsLabel.Alignment = fyne.TextAlignCenter
|
||||||
|
|
||||||
repsLabel := widget.NewLabelWithStyle("5 Wiederholungen", fyne.TextAlignCenter, fyne.TextStyle{})
|
repsLabel := canvas.NewText("5 Wiederholungen", theme.ColorSlate200)
|
||||||
// repsLabel.TextSize = 20
|
repsLabel.TextSize = 20
|
||||||
|
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
|
||||||
timerLabel.SetText(formatDuration(int64(state.RemainingSeconds)))
|
timerLabel.Text = utils.FormatDuration(int64(state.RemainingSeconds))
|
||||||
setsLabel.SetText(fmt.Sprintf("%d / %d", state.SetsDone, state.GoalSets))
|
setsLabel.Text = fmt.Sprintf("%d / %d", state.SetsDone, state.GoalSets)
|
||||||
repsLabel.SetText(fmt.Sprintf("%d Wiederholungen", state.RepsPerSet))
|
repsLabel.Text = fmt.Sprintf("%d Wiederholungen", state.RepsPerSet)
|
||||||
|
|
||||||
|
timerLabel.Refresh()
|
||||||
|
setsLabel.Refresh()
|
||||||
|
repsLabel.Refresh()
|
||||||
|
|
||||||
|
if state.IsTrainingRunning {
|
||||||
|
startButton.Disable()
|
||||||
|
} else {
|
||||||
|
startButton.Enable()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
finishAction := func() {
|
finishAction := func() {
|
||||||
if mainTimer != nil {
|
if mainTimer != nil {
|
||||||
mainTimer.Stop()
|
mainTimer.Stop()
|
||||||
|
mainTimer = nil
|
||||||
}
|
}
|
||||||
// ... (Logik zum Speichern wie zuvor) ...
|
|
||||||
session := &data.TrainingSession{
|
session := &data.TrainingSession{
|
||||||
Date: time.Now(),
|
Date: time.Now(),
|
||||||
Sets: int64(ts.State.SetsDone),
|
Sets: int64(ts.State.SetsDone),
|
||||||
|
|
@ -58,7 +76,8 @@ func MakeTrainingScreen(ts *services.TrainingService, ss *services.SettingsServi
|
||||||
|
|
||||||
mainTimer = time.NewTicker(time.Second)
|
mainTimer = time.NewTicker(time.Second)
|
||||||
go func() {
|
go func() {
|
||||||
for range mainTimer.C {
|
for mainTimer != nil {
|
||||||
|
<-mainTimer.C
|
||||||
if ts.State.RemainingSeconds <= 0 {
|
if ts.State.RemainingSeconds <= 0 {
|
||||||
finishAction()
|
finishAction()
|
||||||
return
|
return
|
||||||
|
|
@ -70,10 +89,16 @@ func MakeTrainingScreen(ts *services.TrainingService, ss *services.SettingsServi
|
||||||
}
|
}
|
||||||
|
|
||||||
setAction := func() {
|
setAction := func() {
|
||||||
|
if !ts.State.IsTrainingRunning {
|
||||||
|
// Starte das Training, wenn es noch nicht läuft
|
||||||
|
startAction()
|
||||||
|
}
|
||||||
ts.CompleteSet()
|
ts.CompleteSet()
|
||||||
updateUI()
|
updateUI()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
startButton = widget.NewButton("Training beginnen", startAction)
|
||||||
|
|
||||||
// Layout im "Cockpit"-Stil
|
// Layout im "Cockpit"-Stil
|
||||||
topPart := container.NewVBox(
|
topPart := container.NewVBox(
|
||||||
widget.NewLabelWithStyle("Verbleibende Zeit", fyne.TextAlignCenter, fyne.TextStyle{}),
|
widget.NewLabelWithStyle("Verbleibende Zeit", fyne.TextAlignCenter, fyne.TextStyle{}),
|
||||||
|
|
@ -87,15 +112,13 @@ func MakeTrainingScreen(ts *services.TrainingService, ss *services.SettingsServi
|
||||||
)
|
)
|
||||||
|
|
||||||
finishButton := widget.NewButton("Training beenden", finishAction)
|
finishButton := widget.NewButton("Training beenden", finishAction)
|
||||||
// Wir simulieren den roten Button durch die Error-Farbe des Themes
|
|
||||||
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,
|
||||||
)
|
)
|
||||||
// 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))
|
return container.NewBorder(topPart, bottomPart, nil, nil, container.NewCenter(middlePart))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
13
internal/ui/utils/format.go
Normal file
13
internal/ui/utils/format.go
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// FormatDuration wandelt Sekunden in einen MM:SS String um.
|
||||||
|
func FormatDuration(totalSeconds int64) string {
|
||||||
|
if totalSeconds < 0 {
|
||||||
|
totalSeconds = 0
|
||||||
|
}
|
||||||
|
mins := totalSeconds / 60
|
||||||
|
secs := totalSeconds % 60
|
||||||
|
return fmt.Sprintf("%02d:%02d", mins, secs)
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue