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
90
internal/services/api.go
Normal file
90
internal/services/api.go
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
package services
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"git.patanix.de/git/kettlebell-app/internal/data"
|
||||
)
|
||||
|
||||
// TrainingPayload ist die JSON-Struktur, die an das Backend gesendet wird.
|
||||
// Die `json:"..."`-Tags stellen sicher, dass die Feldnamen im JSON korrekt sind.
|
||||
type TrainingPayload struct {
|
||||
Reps int `json:"reps"`
|
||||
Rest float64 `json:"rest"`
|
||||
Sets int `json:"sets"`
|
||||
UUID string `json:"uuid"`
|
||||
}
|
||||
|
||||
// ApiService kümmert sich um die Kommunikation mit dem Backend.
|
||||
type ApiService struct {
|
||||
client *http.Client
|
||||
endpoint string
|
||||
uuid string
|
||||
}
|
||||
|
||||
// NewApiService erstellt einen neuen Service für die API-Kommunikation.
|
||||
func NewApiService(appUUID string) *ApiService {
|
||||
return &ApiService{
|
||||
// Erstellt einen HTTP-Client mit einem 5-Sekunden-Timeout, genau wie in deiner Flutter-App.
|
||||
client: &http.Client{
|
||||
Timeout: 5 * time.Second,
|
||||
},
|
||||
endpoint: "http://192.168.178.43:8080/trainings/",
|
||||
uuid: appUUID,
|
||||
}
|
||||
}
|
||||
|
||||
// SendTrainingData sendet eine abgeschlossene Trainingseinheit an das Backend.
|
||||
func (s *ApiService) SendTrainingData(session *data.TrainingSession) {
|
||||
// Berechnung für 'rest' durchführen.
|
||||
var rest float64
|
||||
if session.Sets > 0 {
|
||||
rest = float64(session.Duration) / float64(session.Sets)
|
||||
}
|
||||
|
||||
// Die zu sendenden Daten vorbereiten.
|
||||
payload := TrainingPayload{
|
||||
Reps: int(session.RepsPerSet),
|
||||
Rest: rest,
|
||||
Sets: int(session.Sets),
|
||||
UUID: s.uuid,
|
||||
}
|
||||
|
||||
// Daten in JSON umwandeln.
|
||||
jsonData, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
log.Printf("API Fehler: Konnte Payload nicht in JSON umwandeln: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Den HTTP-Request erstellen.
|
||||
req, err := http.NewRequest("POST", s.endpoint, bytes.NewBuffer(jsonData))
|
||||
if err != nil {
|
||||
log.Printf("API Fehler: Konnte Request nicht erstellen: %v", err)
|
||||
return
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
// Request senden.
|
||||
log.Printf("Sende Training an Backend: %s", string(jsonData))
|
||||
resp, err := s.client.Do(req)
|
||||
if err != nil {
|
||||
log.Printf("API Fehler: Fehler beim Senden des Trainings: %v", err)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Antwort des Servers prüfen.
|
||||
if resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusCreated {
|
||||
log.Println("Training erfolgreich an Backend gesendet.")
|
||||
} else {
|
||||
log.Printf("API Fehler: Unerwarteter Statuscode: %s", resp.Status)
|
||||
// Optional: Den Body der Antwort lesen, um mehr Details zu erhalten.
|
||||
// body, _ := io.ReadAll(resp.Body)
|
||||
// log.Printf("Antwort-Body: %s", string(body))
|
||||
}
|
||||
}
|
||||
41
internal/services/settings.go
Normal file
41
internal/services/settings.go
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
package services
|
||||
|
||||
import (
|
||||
"fyne.io/fyne/v2"
|
||||
)
|
||||
|
||||
type Settings struct {
|
||||
TrainingTimeMinutes int
|
||||
WeightLeft float64
|
||||
WeightRight float64
|
||||
GoalSets int
|
||||
InitialProgram string
|
||||
}
|
||||
|
||||
type SettingsService struct {
|
||||
prefs fyne.Preferences
|
||||
}
|
||||
|
||||
func NewSettingsService(app fyne.App) *SettingsService {
|
||||
return &SettingsService{
|
||||
prefs: app.Preferences(),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SettingsService) LoadSettings() *Settings {
|
||||
return &Settings{
|
||||
TrainingTimeMinutes: s.prefs.IntWithFallback("trainingTimeMinutes", 20),
|
||||
WeightLeft: s.prefs.FloatWithFallback("weightLeft", 16.0),
|
||||
WeightRight: s.prefs.FloatWithFallback("weightRight", 16.0),
|
||||
GoalSets: s.prefs.IntWithFallback("goalSets", 5),
|
||||
InitialProgram: s.prefs.StringWithFallback("initialProgram", "giant_1.0"),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SettingsService) SaveSettings(settings *Settings) {
|
||||
s.prefs.SetInt("trainingTimeMinutes", settings.TrainingTimeMinutes)
|
||||
s.prefs.SetFloat("weightLeft", settings.WeightLeft)
|
||||
s.prefs.SetFloat("weightRight", settings.WeightRight)
|
||||
s.prefs.SetInt("goalSets", settings.GoalSets)
|
||||
s.prefs.SetString("initialProgram", settings.InitialProgram)
|
||||
}
|
||||
356
internal/services/training.go
Normal file
356
internal/services/training.go
Normal file
|
|
@ -0,0 +1,356 @@
|
|||
// package services
|
||||
//
|
||||
// import (
|
||||
//
|
||||
// "log"
|
||||
// "time"
|
||||
//
|
||||
// "git.patanix.de/git/kettlebell-app/internal/data"
|
||||
//
|
||||
// )
|
||||
//
|
||||
// // TrainingState hält den aktuellen Zustand einer laufenden Trainingseinheit.
|
||||
//
|
||||
// type TrainingState struct {
|
||||
// IsTrainingRunning bool
|
||||
// RemainingSeconds int
|
||||
// InitialDurationSeconds int
|
||||
// SetsDone int
|
||||
// GoalSets int
|
||||
// RepsPerSet int
|
||||
// SetTimes []time.Time
|
||||
// Progress float64
|
||||
// SecondsSinceLastSet int
|
||||
// LastSetTimestamp *time.Time
|
||||
// CurrentProgram string
|
||||
// CurrentBlockDay int
|
||||
// CurrentReps int
|
||||
// TotalTrainingDays int
|
||||
// }
|
||||
//
|
||||
// func NewTrainingState() *TrainingState {
|
||||
// return &TrainingState{
|
||||
// IsTrainingRunning: false,
|
||||
// RemainingSeconds: 0,
|
||||
// InitialDurationSeconds: 0,
|
||||
// SetsDone: 0,
|
||||
// GoalSets: 5,
|
||||
// RepsPerSet: 5,
|
||||
// Progress: 0.0,
|
||||
// SecondsSinceLastSet: 0,
|
||||
// LastSetTimestamp: nil,
|
||||
// CurrentProgram: "giant_1.0",
|
||||
// CurrentBlockDay: 1,
|
||||
// CurrentReps: 5,
|
||||
// TotalTrainingDays: 0,
|
||||
// SetTimes: []time.Time{},
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// type TrainingService struct {
|
||||
// State *TrainingState
|
||||
// dbService *data.DatabaseService
|
||||
// settingsService *SettingsService
|
||||
// }
|
||||
//
|
||||
// func NewTrainingService(db *data.DatabaseService, settings *SettingsService) *TrainingService {
|
||||
// initialState := NewTrainingState()
|
||||
// trainingCount, err := db.GetTrainingCount()
|
||||
// if err != nil {
|
||||
// log.Printf("Fehler beim Abrufen der Trainingsanzahl, setze auf 0: %v", err)
|
||||
// initialState.TotalTrainingDays = 0
|
||||
// } else {
|
||||
// initialState.TotalTrainingDays = trainingCount
|
||||
// }
|
||||
// return &TrainingService{
|
||||
// State: initialState,
|
||||
// dbService: db,
|
||||
// settingsService: settings,
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// func (s *TrainingService) updateProgram() {
|
||||
// st := s.State
|
||||
// newTotalDays := st.TotalTrainingDays + 1
|
||||
// newProgram := st.CurrentProgram
|
||||
// newDay := (st.CurrentBlockDay % 3) + 1
|
||||
// newReps := st.CurrentReps
|
||||
//
|
||||
// if newTotalDays > 0 && newTotalDays%12 == 0 {
|
||||
// switch st.CurrentProgram {
|
||||
// case "giant_1.0":
|
||||
// newProgram = "ksk_1.0"
|
||||
// case "giant_1.1":
|
||||
// newProgram = "ksk_1.1"
|
||||
// case "giant_1.2":
|
||||
// newProgram = "ksk_1.2"
|
||||
// case "ksk_1.0":
|
||||
// newProgram = "giant_1.1"
|
||||
// case "ksk_1.1":
|
||||
// newProgram = "giant_1.2"
|
||||
// case "ksk_1.2":
|
||||
// newProgram = "giant_1.0"
|
||||
// default:
|
||||
// newProgram = "giant_1.0"
|
||||
// }
|
||||
// newDay = 1
|
||||
// }
|
||||
//
|
||||
// repsMap := map[string][]int{
|
||||
// "giant_1.0": {5, 6, 4},
|
||||
// "giant_1.1": {6, 8, 7},
|
||||
// "giant_1.2": {7, 9, 8},
|
||||
// "ksk_1.0": {5, 6, 4},
|
||||
// "ksk_1.1": {6, 8, 7},
|
||||
// "ksk_1.2": {7, 9, 8},
|
||||
// }
|
||||
//
|
||||
// if reps, ok := repsMap[newProgram]; ok && len(reps) >= newDay {
|
||||
// newReps = reps[newDay-1]
|
||||
// } else {
|
||||
// newReps = 5
|
||||
// }
|
||||
//
|
||||
// st.CurrentProgram = newProgram
|
||||
// st.CurrentBlockDay = newDay
|
||||
// st.CurrentReps = newReps
|
||||
// st.TotalTrainingDays = newTotalDays
|
||||
// }
|
||||
//
|
||||
// func (s *TrainingService) StartTraining(minutes, goal int) {
|
||||
// s.updateProgram()
|
||||
// duration := minutes * 60
|
||||
// s.State = &TrainingState{
|
||||
// IsTrainingRunning: true,
|
||||
// InitialDurationSeconds: duration,
|
||||
// RemainingSeconds: duration,
|
||||
// GoalSets: goal,
|
||||
// RepsPerSet: s.State.CurrentReps,
|
||||
// CurrentProgram: s.State.CurrentProgram,
|
||||
// CurrentBlockDay: s.State.CurrentBlockDay,
|
||||
// TotalTrainingDays: s.State.TotalTrainingDays,
|
||||
// SetTimes: []time.Time{},
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// func (s *TrainingService) Tick() {
|
||||
// if s.State.RemainingSeconds > 0 {
|
||||
// s.State.RemainingSeconds--
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// func (s *TrainingService) TickLastSetTimer() {
|
||||
// if s.State.IsTrainingRunning && s.State.LastSetTimestamp != nil {
|
||||
// s.State.SecondsSinceLastSet = int(time.Since(*s.State.LastSetTimestamp).Seconds())
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// func (s *TrainingService) CompleteSet() {
|
||||
// st := s.State
|
||||
// st.SetsDone++
|
||||
// now := time.Now()
|
||||
// st.SetTimes = append(st.SetTimes, now)
|
||||
// if st.GoalSets > 0 {
|
||||
// st.Progress = min(float64(st.SetsDone)/float64(st.GoalSets), 1.0)
|
||||
// }
|
||||
// st.LastSetTimestamp = &now
|
||||
// st.SecondsSinceLastSet = 0
|
||||
// }
|
||||
//
|
||||
// func (s *TrainingService) FinishTraining(session *data.TrainingSession) error {
|
||||
// session.Program = s.State.CurrentProgram
|
||||
// session.BlockDay = int64(s.State.CurrentBlockDay)
|
||||
//
|
||||
// err := s.dbService.SaveTraining(session)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// // Platzhalter für den API-Aufruf (aus api_service.dart)
|
||||
// s.sendToBackend(session)
|
||||
//
|
||||
// s.ResetTraining()
|
||||
// return nil
|
||||
// }
|
||||
//
|
||||
// func (s *TrainingService) ResetTraining() {
|
||||
// // Diesen Teil nochmals pruefen
|
||||
// s.State = NewTrainingState()
|
||||
// trainingCount, err := s.dbService.GetTrainingCount()
|
||||
// if err != nil {
|
||||
// log.Print("Unable to get training count")
|
||||
// }
|
||||
// s.State.CurrentBlockDay = trainingCount
|
||||
// // Hier müsste man TotalTrainingDays wieder korrekt laden.
|
||||
// }
|
||||
//
|
||||
// // sendToBackend ist ein Platzhalter für deinen API-Aufruf.
|
||||
//
|
||||
// func (s *TrainingService) sendToBackend(session *data.TrainingSession) {
|
||||
// // Hier würde die Logik aus deinem `api_service.dart` hinkommen.
|
||||
// // z.B. ein HTTP POST Request mit den Trainingsdaten.
|
||||
// // Da der Service nicht existiert, loggen wir es nur.
|
||||
// log.Println("Sende Trainingsdaten an das Backend (Platzhalter)...")
|
||||
// // rest := float64(session.Duration) / float64(session.Sets)
|
||||
// // log.Printf("Reps: %d, Rest: %.2f, Sets: %d", session.RepsPerSet, rest, session.Sets)
|
||||
// }
|
||||
package services
|
||||
|
||||
import (
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"git.patanix.de/git/kettlebell-app/internal/data"
|
||||
)
|
||||
|
||||
type TrainingState struct {
|
||||
IsTrainingRunning bool
|
||||
RemainingSeconds int
|
||||
InitialDurationSeconds int
|
||||
SetsDone int
|
||||
GoalSets int
|
||||
RepsPerSet int
|
||||
SetTimes []time.Time
|
||||
Progress float64
|
||||
SecondsSinceLastSet int
|
||||
LastSetTimestamp *time.Time
|
||||
CurrentProgram string
|
||||
CurrentBlockDay int
|
||||
CurrentReps int
|
||||
TotalTrainingDays int
|
||||
}
|
||||
|
||||
func calculateStateByDayCount(totalDays int) (program string, blockDay, reps int) {
|
||||
program = "giant_1.0"
|
||||
blockDay = 1
|
||||
reps = 5
|
||||
|
||||
if totalDays > 0 {
|
||||
cycleIndex := (totalDays / 12) % 6
|
||||
|
||||
programs := []string{"giant_1.0", "ksk_1.0", "giant_1.1", "ksk_1.1", "giant_1.2", "ksk_1.2"}
|
||||
program = programs[cycleIndex]
|
||||
|
||||
blockDay = (totalDays % 3) + 1
|
||||
}
|
||||
|
||||
repsMap := map[string][]int{
|
||||
"giant_1.0": {5, 6, 4},
|
||||
"giant_1.1": {6, 8, 7},
|
||||
"giant_1.2": {7, 9, 8},
|
||||
"ksk_1.0": {5, 6, 4},
|
||||
"ksk_1.1": {6, 8, 7},
|
||||
"ksk_1.2": {7, 9, 8},
|
||||
}
|
||||
|
||||
if r, ok := repsMap[program]; ok && len(r) >= blockDay {
|
||||
reps = r[blockDay-1]
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func NewTrainingState(db *data.DatabaseService) *TrainingState {
|
||||
trainingCount, err := db.GetTrainingCount()
|
||||
if err != nil {
|
||||
log.Printf("Fehler beim Abrufen der Trainingsanzahl, setze auf 0: %v", err)
|
||||
trainingCount = 0
|
||||
}
|
||||
|
||||
program, blockDay, reps := calculateStateByDayCount(trainingCount)
|
||||
|
||||
return &TrainingState{
|
||||
IsTrainingRunning: false,
|
||||
TotalTrainingDays: trainingCount,
|
||||
CurrentProgram: program,
|
||||
CurrentBlockDay: blockDay,
|
||||
CurrentReps: reps,
|
||||
SetTimes: []time.Time{},
|
||||
GoalSets: 5,
|
||||
RepsPerSet: reps,
|
||||
}
|
||||
}
|
||||
|
||||
type TrainingService struct {
|
||||
State *TrainingState
|
||||
dbService *data.DatabaseService
|
||||
settingsService *SettingsService
|
||||
apiService *ApiService
|
||||
}
|
||||
|
||||
func NewTrainingService(db *data.DatabaseService, settings *SettingsService, api *ApiService) *TrainingService {
|
||||
return &TrainingService{
|
||||
State: NewTrainingState(db),
|
||||
dbService: db,
|
||||
settingsService: settings,
|
||||
apiService: api,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TrainingService) StartTraining(minutes, goal int) {
|
||||
program, blockDay, reps := calculateStateByDayCount(s.State.TotalTrainingDays)
|
||||
|
||||
st := s.State
|
||||
st.IsTrainingRunning = true
|
||||
st.InitialDurationSeconds = minutes * 60
|
||||
st.RemainingSeconds = st.InitialDurationSeconds
|
||||
st.GoalSets = goal
|
||||
st.CurrentProgram = program
|
||||
st.CurrentBlockDay = blockDay
|
||||
st.CurrentReps = reps
|
||||
st.RepsPerSet = reps
|
||||
|
||||
st.SetsDone = 0
|
||||
st.Progress = 0.0
|
||||
st.SetTimes = []time.Time{}
|
||||
st.LastSetTimestamp = nil
|
||||
}
|
||||
|
||||
func (s *TrainingService) Tick() {
|
||||
if s.State.RemainingSeconds > 0 {
|
||||
s.State.RemainingSeconds--
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TrainingService) TickLastSetTimer() {
|
||||
if s.State.IsTrainingRunning && s.State.LastSetTimestamp != nil {
|
||||
s.State.SecondsSinceLastSet = int(time.Since(*s.State.LastSetTimestamp).Seconds())
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TrainingService) CompleteSet() {
|
||||
st := s.State
|
||||
st.SetsDone++
|
||||
now := time.Now()
|
||||
st.SetTimes = append(st.SetTimes, now)
|
||||
if st.GoalSets > 0 {
|
||||
st.Progress = min(float64(st.SetsDone)/float64(st.GoalSets), 1.0)
|
||||
}
|
||||
st.LastSetTimestamp = &now
|
||||
st.SecondsSinceLastSet = 0
|
||||
}
|
||||
|
||||
func (s *TrainingService) FinishTraining(session *data.TrainingSession) error {
|
||||
session.Program = s.State.CurrentProgram
|
||||
session.BlockDay = int64(s.State.CurrentBlockDay)
|
||||
|
||||
err := s.dbService.SaveTraining(session)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// s.sendToBackend(session)
|
||||
go s.apiService.SendTrainingData(session)
|
||||
|
||||
s.ResetTraining()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *TrainingService) ResetTraining() {
|
||||
s.State = NewTrainingState(s.dbService)
|
||||
}
|
||||
|
||||
// sendToBackend ist ein Platzhalter für deinen API-Aufruf.
|
||||
func (s *TrainingService) sendToBackend(session *data.TrainingSession) {
|
||||
log.Println("Sende Trainingsdaten an das Backend (Platzhalter)...")
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue