refactor: rebuild the app in golang with the fyne framework

This commit is contained in:
Patryk Hegenberg 2025-06-27 08:16:24 +02:00
parent 479e4dffa8
commit a8ed9c9ed1
161 changed files with 1212 additions and 6641 deletions

45
.gitignore vendored
View file

@ -1,45 +0,0 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.build/
.buildlog/
.history
.svn/
.swiftpm/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.pub-cache/
.pub/
/build/
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release

View file

@ -1,45 +0,0 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: "c23637390482d4cf9598c3ce3f2be31aa7332daf"
channel: "stable"
project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
- platform: android
create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
- platform: ios
create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
- platform: linux
create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
- platform: macos
create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
- platform: web
create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
- platform: windows
create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'

BIN
Icon.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View file

@ -1,16 +0,0 @@
# kettlebell_tracker
A new Flutter project.
## Getting Started
This project is a starting point for a Flutter application.
A few resources to get you started if this is your first Flutter project:
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
For help getting started with Flutter development, view the
[online documentation](https://docs.flutter.dev/), which offers tutorials,
samples, guidance on mobile development, and a full API reference.

View file

@ -1,28 +0,0 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at https://dart.dev/lints.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

14
android/.gitignore vendored
View file

@ -1,14 +0,0 @@
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
.cxx/
# Remember to never publicly share your keystore.
# See https://flutter.dev/to/reference-keystore
key.properties
**/*.keystore
**/*.jks

View file

@ -1,45 +0,0 @@
plugins {
id("com.android.application")
id("kotlin-android")
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id("dev.flutter.flutter-gradle-plugin")
}
android {
namespace = "com.example.kettlebell_tracker"
compileSdk = flutter.compileSdkVersion
// ndkVersion = flutter.ndkVersion
ndkVersion = "27.0.12077973"
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_11.toString()
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "com.example.kettlebell_tracker"
// You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
versionName = flutter.versionName
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig = signingConfigs.getByName("debug")
}
}
}
flutter {
source = "../.."
}

View file

@ -1,7 +0,0 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View file

@ -1,45 +0,0 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:label="kettlebell_tracker"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
</intent>
</queries>
</manifest>

View file

@ -1,5 +0,0 @@
package com.example.kettlebell_tracker
import io.flutter.embedding.android.FlutterActivity
class MainActivity : FlutterActivity()

View file

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

View file

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -1,18 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View file

@ -1,18 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View file

@ -1,7 +0,0 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View file

@ -1,21 +0,0 @@
allprojects {
repositories {
google()
mavenCentral()
}
}
val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get()
rootProject.layout.buildDirectory.value(newBuildDir)
subprojects {
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
project.layout.buildDirectory.value(newSubprojectBuildDir)
}
subprojects {
project.evaluationDependsOn(":app")
}
tasks.register<Delete>("clean") {
delete(rootProject.layout.buildDirectory)
}

View file

@ -1,3 +0,0 @@
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true
android.enableJetifier=true

View file

@ -1,5 +0,0 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip

View file

@ -1,25 +0,0 @@
pluginManagement {
val flutterSdkPath = run {
val properties = java.util.Properties()
file("local.properties").inputStream().use { properties.load(it) }
val flutterSdkPath = properties.getProperty("flutter.sdk")
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
flutterSdkPath
}
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
plugins {
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
id("com.android.application") version "8.7.0" apply false
id("org.jetbrains.kotlin.android") version "1.8.22" apply false
}
include(":app")

Binary file not shown.

Binary file not shown.

View file

@ -1,17 +0,0 @@
# Changelog
All notable changes to this project will be documented in this file.
## [0.0.2] - 2025-06-22
### 🚀 Features
- Implement autoregulation for kettlebell training
### 💼 Other
- Update dependenvies
## [0.0.1] - 2025-06-16
<!-- generated by git-cliff -->

8
cmd/FyneApp.toml Normal file
View file

@ -0,0 +1,8 @@
Website = "https://patanix.de"
[Details]
Icon = "Icon.png"
Name = "kettlebell_tracker"
ID = "de.patanix.kettlebell_tracker"
Version = "1.0.0"
Build = 5

BIN
cmd/Icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

1
cmd/build.sh Normal file
View file

@ -0,0 +1 @@
fyne package -os android -release --tags -ldflags="-s -w"

54
cmd/main.go Normal file
View file

@ -0,0 +1,54 @@
package main
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"
)
func main() {
myApp := app.NewWithID("com.example.kettlebell-tracker")
// myApp.Settings().SetTheme(theme.DarkTheme())
mainWIndow := myApp.NewWindow("Kettlebell Programm Tracker")
dbDir := myApp.Storage().RootURI().Path()
dbPath := filepath.Join(dbDir, "kb_training.db")
log.Println("Datenbankpfad:", dbPath)
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)
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),
)
tabs.SetTabLocation(container.TabLocationBottom)
mainWIndow.Resize(fyne.NewSize(400, 600))
mainWIndow.SetContent(tabs)
mainWIndow.SetMaster()
mainWIndow.ShowAndRun()
}

52
go.mod Normal file
View file

@ -0,0 +1,52 @@
module git.patanix.de/git/kettlebell-app
go 1.24.4
require (
fyne.io/fyne/v2 v2.6.1
modernc.org/sqlite v1.38.0
)
require (
fyne.io/systray v1.11.0 // indirect
github.com/BurntSushi/toml v1.4.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/fredbi/uri v1.1.0 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/fyne-io/gl-js v0.1.0 // indirect
github.com/fyne-io/glfw-js v0.2.0 // indirect
github.com/fyne-io/image v0.1.1 // indirect
github.com/fyne-io/oksvg v0.1.0 // indirect
github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 // indirect
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a // indirect
github.com/go-text/render v0.2.0 // indirect
github.com/go-text/typesetting v0.2.1 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hack-pad/go-indexeddb v0.3.2 // indirect
github.com/hack-pad/safejs v0.1.0 // indirect
github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08 // indirect
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
github.com/nicksnyder/go-i18n/v2 v2.5.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rymdport/portal v0.4.1 // indirect
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c // indirect
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect
github.com/stretchr/testify v1.10.0 // indirect
github.com/yuin/goldmark v1.7.8 // indirect
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
golang.org/x/image v0.24.0 // indirect
golang.org/x/net v0.35.0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/text v0.22.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
modernc.org/libc v1.65.10 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect
)

123
go.sum Normal file
View file

@ -0,0 +1,123 @@
fyne.io/fyne/v2 v2.6.1 h1:kjPJD4/rBS9m2nHJp+npPSuaK79yj6ObMTuzR6VQ1Is=
fyne.io/fyne/v2 v2.6.1/go.mod h1:YZt7SksjvrSNJCwbWFV32WON3mE1Sr7L41D29qMZ/lU=
fyne.io/systray v1.11.0 h1:D9HISlxSkx+jHSniMBR6fCFOUjk1x/OOOJLa9lJYAKg=
fyne.io/systray v1.11.0/go.mod h1:RVwqP9nYMo7h5zViCBHri2FgjXF7H2cub7MAq4NSoLs=
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g=
github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw=
github.com/fredbi/uri v1.1.0 h1:OqLpTXtyRg9ABReqvDGdJPqZUxs8cyBDOMXBbskCaB8=
github.com/fredbi/uri v1.1.0/go.mod h1:aYTUoAXBOq7BLfVJ8GnKmfcuURosB1xyHDIfWeC/iW4=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/fyne-io/gl-js v0.1.0 h1:8luJzNs0ntEAJo+8x8kfUOXujUlP8gB3QMOxO2mUdpM=
github.com/fyne-io/gl-js v0.1.0/go.mod h1:ZcepK8vmOYLu96JoxbCKJy2ybr+g1pTnaBDdl7c3ajI=
github.com/fyne-io/glfw-js v0.2.0 h1:8GUZtN2aCoTPNqgRDxK5+kn9OURINhBEBc7M4O1KrmM=
github.com/fyne-io/glfw-js v0.2.0/go.mod h1:Ri6te7rdZtBgBpxLW19uBpp3Dl6K9K/bRaYdJ22G8Jk=
github.com/fyne-io/image v0.1.1 h1:WH0z4H7qfvNUw5l4p3bC1q70sa5+YWVt6HCj7y4VNyA=
github.com/fyne-io/image v0.1.1/go.mod h1:xrfYBh6yspc+KjkgdZU/ifUC9sPA5Iv7WYUBzQKK7JM=
github.com/fyne-io/oksvg v0.1.0 h1:7EUKk3HV3Y2E+qypp3nWqMXD7mum0hCw2KEGhI1fnBw=
github.com/fyne-io/oksvg v0.1.0/go.mod h1:dJ9oEkPiWhnTFNCmRgEze+YNprJF7YRbpjgpWS4kzoI=
github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 h1:5BVwOaUSBTlVZowGO6VZGw2H/zl9nrd3eCZfYV+NfQA=
github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a h1:vxnBhFDDT+xzxf1jTJKMKZw3H0swfWk9RpWbBbDK5+0=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-text/render v0.2.0 h1:LBYoTmp5jYiJ4NPqDc2pz17MLmA3wHw1dZSVGcOdeAc=
github.com/go-text/render v0.2.0/go.mod h1:CkiqfukRGKJA5vZZISkjSYrcdtgKQWRa2HIzvwNN5SU=
github.com/go-text/typesetting v0.2.1 h1:x0jMOGyO3d1qFAPI0j4GSsh7M0Q3Ypjzr4+CEVg82V8=
github.com/go-text/typesetting v0.2.1/go.mod h1:mTOxEwasOFpAMBjEQDhdWRckoLLeI/+qrQeBCTGEt6M=
github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066 h1:qCuYC+94v2xrb1PoS4NIDe7DGYtLnU2wWiQe9a1B1c0=
github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o=
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hack-pad/go-indexeddb v0.3.2 h1:DTqeJJYc1usa45Q5r52t01KhvlSN02+Oq+tQbSBI91A=
github.com/hack-pad/go-indexeddb v0.3.2/go.mod h1:QvfTevpDVlkfomY498LhstjwbPW6QC4VC/lxYb0Kom0=
github.com/hack-pad/safejs v0.1.0 h1:qPS6vjreAqh2amUqj4WNG1zIw7qlRQJ9K10eDKMCnE8=
github.com/hack-pad/safejs v0.1.0/go.mod h1:HdS+bKF1NrE72VoXZeWzxFOVQVUSqZJAG0xNCnb+Tio=
github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08 h1:wMeVzrPO3mfHIWLZtDcSaGAe2I4PW9B/P5nMkRSwCAc=
github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08/go.mod h1:ZDXo8KHryOWSIqnsb/CiDq7hQUYryCgdVnxbj8tDG7o=
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 h1:YLvr1eE6cdCqjOe972w/cYF+FjW34v27+9Vo5106B4M=
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25/go.mod h1:kLgvv7o6UM+0QSf0QjAse3wReFDsb9qbZJdfexWlrQw=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
github.com/nicksnyder/go-i18n/v2 v2.5.1 h1:IxtPxYsR9Gp60cGXjfuR/llTqV8aYMsC472zD0D1vHk=
github.com/nicksnyder/go-i18n/v2 v2.5.1/go.mod h1:DrhgsSDZxoAfvVrBVLXoxZn/pN5TXqaDbq7ju94viiQ=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pkg/profile v1.7.0 h1:hnbDkaNWPCLMO9wGLdBFTIZvzDrDfBM2072E1S9gJkA=
github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rymdport/portal v0.4.1 h1:2dnZhjf5uEaeDjeF/yBIeeRo6pNI2QAKm7kq1w/kbnA=
github.com/rymdport/portal v0.4.1/go.mod h1:kFF4jslnJ8pD5uCi17brj/ODlfIidOxlgUDTO5ncnC4=
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c h1:km8GpoQut05eY3GiYWEedbTT0qnSxrCjsVbb7yKY1KE=
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c/go.mod h1:cNQ3dwVJtS5Hmnjxy6AgTPd0Inb3pW05ftPSX7NZO7Q=
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef h1:Ch6Q+AZUxDBCVqdkI8FSpFyZDtCVBc2VmejdNrm5rRQ=
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef/go.mod h1:nXTWP6+gD5+LUJ8krVhhoeHjvHTutPxMYl5SvkcnJNE=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic=
github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8=
golang.org/x/image v0.24.0 h1:AN7zRgVsbvmTfNyqIbbOraYL8mSwcKncEj8ofjgzcMQ=
golang.org/x/image v0.24.0/go.mod h1:4b/ITuLfqYq1hqZcjofwctIhi7sZh2WaCjvsBNjjya8=
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
modernc.org/cc/v4 v4.26.1 h1:+X5NtzVBn0KgsBCBe+xkDC7twLb/jNVj9FPgiwSQO3s=
modernc.org/cc/v4 v4.26.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU=
modernc.org/ccgo/v4 v4.28.0/go.mod h1:JygV3+9AV6SmPhDasu4JgquwU81XAKLd3OKTUDNOiKE=
modernc.org/fileutil v1.3.3 h1:3qaU+7f7xxTUmvU1pJTZiDLAIoJVdUSSauJNHg9yXoA=
modernc.org/fileutil v1.3.3/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
modernc.org/libc v1.65.10 h1:ZwEk8+jhW7qBjHIT+wd0d9VjitRyQef9BnzlzGwMODc=
modernc.org/libc v1.65.10/go.mod h1:StFvYpx7i/mXtBAfVOjaU0PWZOvIRoZSgXhrwXzr8Po=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.38.0 h1:+4OrfPQ8pxHKuWG4md1JpR/EYAh3Md7TdejuuzE7EUI=
modernc.org/sqlite v1.38.0/go.mod h1:1Bj+yES4SVvBZ4cBOpVZ6QgesMCKpJZDq0nxYzOpmNE=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=

117
internal/data/database.go Normal file
View file

@ -0,0 +1,117 @@
package data
import (
"database/sql"
"log"
"time"
_ "modernc.org/sqlite" // Importiert den SQLite-Treiber
)
type DatabaseService struct {
DB *sql.DB
}
func NewDatabaseService(dbPath string) (*DatabaseService, error) {
db, err := sql.Open("sqlite", dbPath)
if err != nil {
return nil, err
}
if err = db.Ping(); err != nil {
return nil, err
}
createTableSQL := `
CREATE TABLE IF NOT EXISTS training (
id INTEGER PRIMARY KEY AUTOINCREMENT,
date TEXT NOT NULL,
sets INTEGER,
weightLeft REAL,
weightRight REAL,
repsPerSet INTEGER,
duration INTEGER,
program TEXT,
blockDay INTEGER
);`
_, err = db.Exec(createTableSQL)
if err != nil {
log.Printf("Fehler beim Erstellen der Tabelle: %v", 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.")
return &DatabaseService{DB: db}, nil
}
func (s *DatabaseService) SaveTraining(session *TrainingSession) error {
dateStr := session.Date.Format(time.RFC3339)
query := `
INSERT INTO training (id, date, sets, weightLeft, weightRight, repsPerSet, duration, program, blockDay)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(id) DO UPDATE SET
date = excluded.date,
sets = excluded.sets,
weightLeft = excluded.weightLeft,
weightRight = excluded.weightRight,
repsPerSet = excluded.repsPerSet,
duration = excluded.duration,
program = excluded.program,
blockDay = excluded.blockDay;
`
var id any
if session.ID != 0 {
id = session.ID
}
_, err := s.DB.Exec(query, id, dateStr, session.Sets, session.WeightLeft, session.WeightRight, session.RepsPerSet, session.Duration, session.Program, session.BlockDay)
return err
}
func (s *DatabaseService) GetTrainingCount() (int, error) {
var count int
query := "SELECT COUNT(*) FROM training;"
err := s.DB.QueryRow(query).Scan(&count)
if err != nil {
if err == sql.ErrNoRows {
return 0, nil
}
return 0, err
}
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
}

17
internal/data/models.go Normal file
View file

@ -0,0 +1,17 @@
package data
import "time"
// TrainingSession repräsentiert eine einzelne Trainingseinheit.
// Die `db`-Tags werden verwendet, um die Struct-Felder den Datenbankspalten zuzuordnen.
type TrainingSession struct {
ID int64 `db:"id"`
Date time.Time `db:"date"`
Sets int64 `db:"sets"`
WeightLeft float64 `db:"weightLeft"`
WeightRight float64 `db:"weightRight"`
RepsPerSet int64 `db:"repsPerSet"`
Duration int64 `db:"duration"` // in Sekunden
Program string `db:"program"`
BlockDay int64 `db:"blockDay"`
}

90
internal/services/api.go Normal file
View 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))
}
}

View 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)
}

View 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)...")
}

92
internal/ui/history.go Normal file
View 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
View 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
View 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
View 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,
)
}

34
ios/.gitignore vendored
View file

@ -1,34 +0,0 @@
**/dgph
*.mode1v3
*.mode2v3
*.moved-aside
*.pbxuser
*.perspectivev3
**/*sync/
.sconsign.dblite
.tags*
**/.vagrant/
**/DerivedData/
Icon?
**/Pods/
**/.symlinks/
profile
xcuserdata
**/.generated/
Flutter/App.framework
Flutter/Flutter.framework
Flutter/Flutter.podspec
Flutter/Generated.xcconfig
Flutter/ephemeral/
Flutter/app.flx
Flutter/app.zip
Flutter/flutter_assets/
Flutter/flutter_export_environment.sh
ServiceDefinitions.json
Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!default.mode1v3
!default.mode2v3
!default.pbxuser
!default.perspectivev3

View file

@ -1,26 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>App</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>12.0</string>
</dict>
</plist>

View file

@ -1 +0,0 @@
#include "Generated.xcconfig"

View file

@ -1 +0,0 @@
#include "Generated.xcconfig"

View file

@ -1,616 +0,0 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
proxyType = 1;
remoteGlobalIDString = 97C146ED1CF9000F007C117D;
remoteInfo = Runner;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
331C8082294A63A400263BE5 /* RunnerTests */ = {
isa = PBXGroup;
children = (
331C807B294A618700263BE5 /* RunnerTests.swift */,
);
path = RunnerTests;
sourceTree = "<group>";
};
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */,
);
name = Flutter;
sourceTree = "<group>";
};
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */,
);
sourceTree = "<group>";
};
97C146EF1CF9000F007C117D /* Products */ = {
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
331C8081294A63A400263BE5 /* RunnerTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
);
path = Runner;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
331C8080294A63A400263BE5 /* RunnerTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
331C807D294A63A400263BE5 /* Sources */,
331C807F294A63A400263BE5 /* Resources */,
);
buildRules = (
);
dependencies = (
331C8086294A63A400263BE5 /* PBXTargetDependency */,
);
name = RunnerTests;
productName = RunnerTests;
productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
97C146ED1CF9000F007C117D /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
);
buildRules = (
);
dependencies = (
);
name = Runner;
productName = Runner;
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
331C8080294A63A400263BE5 = {
CreatedOnToolsVersion = 14.0;
TestTargetID = 97C146ED1CF9000F007C117D;
};
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
LastSwiftMigration = 1100;
};
};
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
97C146ED1CF9000F007C117D /* Runner */,
331C8080294A63A400263BE5 /* RunnerTests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
331C807F294A63A400263BE5 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EC1CF9000F007C117D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
);
name = "Thin Binary";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Run Script";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
331C807D294A63A400263BE5 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EA1CF9000F007C117D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
331C8086294A63A400263BE5 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 97C146ED1CF9000F007C117D /* Runner */;
targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C146FB1CF9000F007C117D /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C147001CF9000F007C117D /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
249021D3217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Profile;
};
249021D4217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.kettlebellTracker;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Profile;
};
331C8088294A63A400263BE5 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.kettlebellTracker.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Debug;
};
331C8089294A63A400263BE5 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.kettlebellTracker.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Release;
};
331C808A294A63A400263BE5 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.kettlebellTracker.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Profile;
};
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
97C147041CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
97C147061CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.kettlebellTracker;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
};
97C147071CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.kettlebellTracker;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
331C8088294A63A400263BE5 /* Debug */,
331C8089294A63A400263BE5 /* Release */,
331C808A294A63A400263BE5 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147031CF9000F007C117D /* Debug */,
97C147041CF9000F007C117D /* Release */,
249021D3217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147061CF9000F007C117D /* Debug */,
97C147071CF9000F007C117D /* Release */,
249021D4217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}

View file

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

View file

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View file

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

View file

@ -1,99 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1510"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "331C8080294A63A400263BE5"
BuildableName = "RunnerTests.xctest"
BlueprintName = "RunnerTests"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
enableGPUValidationMode = "1"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View file

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
</Workspace>

View file

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View file

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

View file

@ -1,13 +0,0 @@
import Flutter
import UIKit
@main
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}

View file

@ -1,122 +0,0 @@
{
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@3x.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@3x.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@3x.png",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@1x.png",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@1x.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@1x.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-App-83.5x83.5@2x.png",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "Icon-App-1024x1024@1x.png",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 295 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 450 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 282 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 462 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 704 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 586 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 862 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 862 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 762 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -1,23 +0,0 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "LaunchImage.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 B

View file

@ -1,5 +0,0 @@
# Launch Screen Assets
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.

View file

@ -1,37 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
</imageView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
</constraints>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
<resources>
<image name="LaunchImage" width="168" height="185"/>
</resources>
</document>

View file

@ -1,26 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
</dependencies>
<scenes>
<!--Flutter View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>

View file

@ -1,49 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Kettlebell Tracker</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>kettlebell_tracker</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
</dict>
</plist>

View file

@ -1 +0,0 @@
#import "GeneratedPluginRegistrant.h"

View file

@ -1,12 +0,0 @@
import Flutter
import UIKit
import XCTest
class RunnerTests: XCTestCase {
func testExample() {
// If you add code to the Runner application, consider adding tests here.
// See https://developer.apple.com/documentation/xctest for more information about using XCTest.
}
}

View file

@ -1,97 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:kettlebell_tracker/providers/settings_provider.dart';
import 'package:kettlebell_tracker/screens/home_screen.dart';
import 'package:kettlebell_tracker/screens/settings_screen.dart';
import 'package:kettlebell_tracker/screens/training_screen.dart';
import 'package:kettlebell_tracker/screens/history_screen.dart';
import 'package:kettlebell_tracker/theme/app_theme.dart';
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
import 'dart:io';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) {
sqfliteFfiInit();
databaseFactory = databaseFactoryFfi;
}
runApp(
const ProviderScope(child: MyApp()),
);
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Giant Programm Tracker',
theme: AppTheme.darkTheme,
home: const MainScreen(),
debugShowCheckedModeBanner: false,
);
}
}
class MainScreen extends ConsumerStatefulWidget {
const MainScreen({super.key});
@override
ConsumerState<MainScreen> createState() => _MainScreenState();
}
class _MainScreenState extends ConsumerState<MainScreen> {
int _selectedIndex = 0;
static const List<Widget> _widgetOptions = <Widget>[
HomeScreen(),
SettingsScreen(),
TrainingScreen(),
HistoryScreen(),
];
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
@override
void initState() {
super.initState();
Future.microtask(() {
ref.read(settingsProvider.notifier).loadSettings();
});
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Scaffold(
body: Center(child: _widgetOptions.elementAt(_selectedIndex)),
bottomNavigationBar: BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
BottomNavigationBarItem(
icon: Icon(Icons.settings),
label: 'Einstellungen',
),
BottomNavigationBarItem(
icon: Icon(Icons.sports_gymnastics),
label: 'Training',
),
BottomNavigationBarItem(icon: Icon(Icons.history), label: 'Historie'),
],
currentIndex: _selectedIndex,
onTap: _onItemTapped,
type: BottomNavigationBarType.fixed, // Important for more than 3 items
backgroundColor: theme.colorScheme.surface,
selectedItemColor: theme.colorScheme.primary,
unselectedItemColor: AppTheme.oneDarkTextWeak,
),
);
}
}

View file

@ -1,83 +0,0 @@
class TrainingSession {
final int? id;
final DateTime date;
final int sets;
final double weightLeft;
final double weightRight;
final int repsPerSet;
final int duration; // in seconds
final String program;
final int blockDay;
TrainingSession({
this.id,
required this.date,
required this.sets,
required this.weightLeft,
required this.weightRight,
required this.repsPerSet,
required this.duration,
required this.program,
required this.blockDay,
});
// Convert a TrainingSession into a Map. The keys must correspond to the names of the
// columns in the database.
Map<String, dynamic> toMap() {
return {
'id': id,
'date': date.toIso8601String(),
'sets': sets,
'weightLeft': weightLeft,
'weightRight': weightRight,
'repsPerSet': repsPerSet,
'duration': duration,
'program': program,
'blockDay': blockDay,
};
}
// Implement fromMap
factory TrainingSession.fromMap(Map<String, dynamic> map) {
return TrainingSession(
id: map['id'],
date: DateTime.parse(map['date']),
sets: map['sets'],
weightLeft: map['weightLeft'],
weightRight: map['weightRight'],
repsPerSet: map['repsPerSet'],
duration: map['duration'],
program: map['program'] ?? 'giant_1.0',
blockDay: map['blockDay'] ?? 1,
);
}
TrainingSession copyWith({
int? id,
DateTime? date,
int? sets,
double? weightLeft,
double? weightRight,
int? repsPerSet,
int? duration,
String? program,
int? blockDay,
}) {
return TrainingSession(
id: id ?? this.id,
date: date ?? this.date,
sets: sets ?? this.sets,
weightLeft: weightLeft ?? this.weightLeft,
weightRight: weightRight ?? this.weightRight,
repsPerSet: repsPerSet ?? this.repsPerSet,
duration: duration ?? this.duration,
program: program ?? this.program,
blockDay: blockDay ?? this.blockDay,
);
}
@override
String toString() {
return 'TrainingSession{id: $id, date: $date, sets: $sets}';
}
}

View file

@ -1,69 +0,0 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:shared_preferences/shared_preferences.dart';
class SettingsState {
final int trainingTimeMinutes;
final double weightLeft;
final double weightRight;
final int goalSets;
final String initialProgram;
SettingsState({
this.trainingTimeMinutes = 20,
this.weightLeft = 16.0,
this.weightRight = 16.0,
this.goalSets = 5,
this.initialProgram = 'giant_1.0',
});
SettingsState copyWith({
int? trainingTimeMinutes,
double? weightLeft,
double? weightRight,
int? goalSets,
String? initialProgram,
}) {
return SettingsState(
trainingTimeMinutes: trainingTimeMinutes ?? this.trainingTimeMinutes,
weightLeft: weightLeft ?? this.weightLeft,
weightRight: weightRight ?? this.weightRight,
goalSets: goalSets ?? this.goalSets,
initialProgram: initialProgram ?? this.initialProgram,
);
}
}
class SettingsNotifier extends StateNotifier<SettingsState> {
SettingsNotifier() : super(SettingsState());
Future<void> loadSettings() async {
final prefs = await SharedPreferences.getInstance();
state = SettingsState(
trainingTimeMinutes: prefs.getInt('trainingTimeMinutes') ?? 20,
weightLeft: prefs.getDouble('weightLeft') ?? 16.0,
weightRight: prefs.getDouble('weightRight') ?? 16.0,
goalSets: prefs.getInt('goalSets') ?? 5,
initialProgram: prefs.getString('initialProgram') ?? 'giant_1.0',
);
}
Future<void> saveSettings(SettingsState newSettings) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setInt('trainingTimeMinutes', newSettings.trainingTimeMinutes);
await prefs.setDouble('weightLeft', newSettings.weightLeft);
await prefs.setDouble('weightRight', newSettings.weightRight);
await prefs.setInt('goalSets', newSettings.goalSets);
await prefs.setString('initialProgram', newSettings.initialProgram);
state = newSettings;
}
Future<void> updateSettings(SettingsState newSettings) async {
await saveSettings(newSettings);
}
}
final settingsProvider = StateNotifierProvider<SettingsNotifier, SettingsState>(
(ref) {
return SettingsNotifier();
},
);

View file

@ -1,213 +0,0 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:kettlebell_tracker/models/training_session.dart';
import 'package:kettlebell_tracker/services/api_service.dart';
import 'package:kettlebell_tracker/services/database_helper.dart';
import 'dart:async';
// Training State
class TrainingState {
final bool isTrainingRunning;
final int remainingSeconds;
final int initialDurationSeconds;
final int setsDone;
final int goalSets;
final int repsPerSet;
final List<DateTime> setTimes;
final double progress;
final int secondsSinceLastSet;
final DateTime? lastSetTimestamp;
final String currentProgram;
final int currentBlockDay;
final int currentReps;
final int totalTrainingDays;
TrainingState({
this.isTrainingRunning = false,
this.remainingSeconds = 0,
this.initialDurationSeconds = 0,
this.setsDone = 0,
this.goalSets = 5,
this.repsPerSet = 5,
this.setTimes = const [],
this.progress = 0.0,
this.secondsSinceLastSet = 0,
this.lastSetTimestamp,
this.currentProgram = 'giant_1.0',
this.currentBlockDay = 1,
this.currentReps = 5,
this.totalTrainingDays = 0,
});
TrainingState copyWith({
bool? isTrainingRunning,
int? remainingSeconds,
int? initialDurationSeconds,
int? setsDone,
int? goalSets,
int? repsPerSet,
List<DateTime>? setTimes,
double? progress,
int? secondsSinceLastSet,
DateTime? lastSetTimestamp,
bool clearLastSetTimestamp = false,
String? currentProgram,
int? currentBlockDay,
int? currentReps,
int? totalTrainingDays,
}) {
return TrainingState(
isTrainingRunning: isTrainingRunning ?? this.isTrainingRunning,
remainingSeconds: remainingSeconds ?? this.remainingSeconds,
initialDurationSeconds:
initialDurationSeconds ?? this.initialDurationSeconds,
setsDone: setsDone ?? this.setsDone,
goalSets: goalSets ?? this.goalSets,
repsPerSet: repsPerSet ?? this.repsPerSet,
setTimes: setTimes ?? this.setTimes,
progress: progress ?? this.progress,
secondsSinceLastSet: secondsSinceLastSet ?? this.secondsSinceLastSet,
lastSetTimestamp: clearLastSetTimestamp
? null
: lastSetTimestamp ?? this.lastSetTimestamp,
currentProgram: currentProgram ?? this.currentProgram,
currentBlockDay: currentBlockDay ?? this.currentBlockDay,
currentReps: currentReps ?? this.currentReps,
totalTrainingDays: totalTrainingDays ?? this.totalTrainingDays,
);
}
}
class TrainingNotifier extends StateNotifier<TrainingState> {
final Ref ref;
TrainingNotifier(this.ref) : super(TrainingState());
void _updateProgram() {
int newTotalDays = state.totalTrainingDays + 1;
String newProgram = state.currentProgram;
int newDay = (state.currentBlockDay % 3) + 1;
int newReps = state.currentReps;
if (newTotalDays > 0 && newTotalDays % 12 == 0) {
switch (state.currentProgram) {
case 'giant_1.0':
newProgram = 'giant_1.1';
break;
case 'giant_1.1':
newProgram = 'giant_1.2';
break;
case 'giant_1.2':
newProgram = 'ksk_1.0';
break;
case 'ksk_1.0':
newProgram = 'ksk_1.1';
break;
case 'ksk_1.1':
newProgram = 'ksk_1.2';
break;
case 'ksk_1.2':
newProgram = 'giant_1.0';
break;
default:
newProgram = 'giant_1.0';
}
newDay = 1;
}
if (newProgram == 'giant_1.0') {
newReps = [5, 6, 4][newDay - 1];
} else if (newProgram == 'giant_1.1') {
newReps = [6, 8, 7][newDay - 1];
} else if (newProgram == 'giant_1.2') {
newReps = [7, 9, 8][newDay - 1];
} else if (newProgram == 'ksk_1.0') {
newReps = [5, 6, 4][newDay - 1];
} else if (newProgram == 'ksk_1.1') {
newReps = [6, 8, 7][newDay - 1];
} else if (newProgram == 'ksk_1.2') {
newReps = [7, 9, 8][newDay - 1];
} else {
newReps = 5;
}
state = state.copyWith(
currentProgram: newProgram,
currentBlockDay: newDay,
currentReps: newReps,
totalTrainingDays: newTotalDays,
);
}
void startTraining(int minutes, int goal) {
_updateProgram();
final duration = minutes * 60;
state = state.copyWith(
isTrainingRunning: true,
initialDurationSeconds: duration,
remainingSeconds: duration,
goalSets: goal,
repsPerSet: state.currentReps,
);
}
void tick() {
if (state.remainingSeconds > 0) {
state = state.copyWith(remainingSeconds: state.remainingSeconds - 1);
}
}
void tickLastSetTimer() {
if (state.isTrainingRunning && state.lastSetTimestamp != null) {
state = state.copyWith(
secondsSinceLastSet:
DateTime.now().difference(state.lastSetTimestamp!).inSeconds,
);
}
}
void completeSet() {
final newSetsDone = state.setsDone + 1;
final newSetTimes = List<DateTime>.from(state.setTimes)
..add(DateTime.now());
final newProgress = state.goalSets > 0 ? newSetsDone / state.goalSets : 0.0;
state = state.copyWith(
setsDone: newSetsDone,
setTimes: newSetTimes,
progress: newProgress > 1.0 ? 1.0 : newProgress,
lastSetTimestamp: DateTime.now(),
secondsSinceLastSet: 0,
);
}
Future<void> finishTraining(TrainingSession session) async {
final updatedSession = session.copyWith(
program: state.currentProgram,
blockDay: state.currentBlockDay,
);
await DatabaseHelper().saveTraining(updatedSession);
try {
await sendTrainingToBackend(
reps: session.repsPerSet,
rest: session.duration / session.sets,
sets: session.sets);
} catch (e) {
print("Error sending information to backend");
}
ref.refresh(historyProvider);
resetTraining();
}
void resetTraining() {
state = TrainingState();
}
}
final historyProvider = FutureProvider<List<TrainingSession>>((ref) async {
return DatabaseHelper().getHistory();
});
final trainingProvider = StateNotifierProvider<TrainingNotifier, TrainingState>(
(ref) {
return TrainingNotifier(ref);
},
);

View file

@ -1,115 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:kettlebell_tracker/models/training_session.dart';
import 'package:kettlebell_tracker/providers/training_provider.dart';
import 'package:kettlebell_tracker/theme/app_theme.dart';
import 'package:kettlebell_tracker/widgets/custom_card.dart';
import 'package:intl/intl.dart';
class HistoryScreen extends ConsumerWidget {
const HistoryScreen({super.key});
String _formatDuration(int totalSeconds) {
final duration = Duration(seconds: totalSeconds);
final minutes = duration.inMinutes.remainder(60).toString().padLeft(2, '0');
final seconds = duration.inSeconds.remainder(60).toString().padLeft(2, '0');
return '$minutes:$seconds';
}
@override
Widget build(BuildContext context, WidgetRef ref) {
final historyAsyncValue = ref.watch(historyProvider);
return Scaffold(
appBar: AppBar(
title: const Text('Trainingshistorie'),
actions: [
IconButton(
icon: const Icon(Icons.refresh),
onPressed: () => ref.refresh(historyProvider),
),
],
),
body: historyAsyncValue.when(
data: (history) {
if (history.isEmpty) {
return const Center(
child: Text(
'Noch keine Trainingsdaten vorhanden.',
style: TextStyle(color: AppTheme.oneDarkTextWeak, fontSize: 16),
),
);
}
return ListView.builder(
padding: const EdgeInsets.all(8.0),
itemCount: history.length,
itemBuilder: (context, index) {
final session = history[index];
return HistoryItemCard(
session: session, formatDuration: _formatDuration);
},
);
},
loading: () => const Center(child: CircularProgressIndicator()),
error: (err, stack) => Center(child: Text('Fehler beim Laden: $err')),
),
);
}
}
class HistoryItemCard extends StatelessWidget {
const HistoryItemCard({
super.key,
required this.session,
required this.formatDuration,
});
final TrainingSession session;
final String Function(int) formatDuration;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return CustomCard(
margin: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(DateFormat('dd.MM.yyyy HH:mm').format(session.date),
style: theme.textTheme.titleMedium?.copyWith(
color: AppTheme.oneDarkPrimary, fontWeight: FontWeight.bold)),
const Divider(color: AppTheme.oneDarkTextWeak, height: 20),
_buildInfoRow(Icons.repeat, 'Sätze: ${session.sets}'),
_buildInfoRow(Icons.fitness_center,
'Kettlebells: ${session.weightLeft}kg / ${session.weightRight}kg'),
_buildInfoRow(Icons.format_list_numbered,
'Reps pro Satz: ${session.repsPerSet}'),
_buildInfoRow(
Icons.timer, 'Dauer: ${formatDuration(session.duration)}'),
],
),
);
}
Widget _buildInfoRow(IconData icon, String text, {bool isNote = false}) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4.0),
child: Row(
children: [
Icon(icon, size: 18, color: AppTheme.oneDarkTextWeak),
const SizedBox(width: 8),
Expanded(
child: Text(
text,
style: TextStyle(
fontSize: 15,
color: isNote ? AppTheme.oneDarkTextWeak : AppTheme.oneDarkText,
fontStyle: isNote ? FontStyle.italic : FontStyle.normal,
),
),
),
],
),
);
}
}

View file

@ -1,55 +0,0 @@
import 'package:flutter/material.dart';
import 'package:kettlebell_tracker/theme/app_theme.dart';
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Scaffold(
appBar: AppBar(
title: const Text('Giant Programm Tracker'),
leading: const Icon(
Icons.fitness_center,
color: AppTheme.oneDarkPrimary,
),
),
body: Center(
child: SingleChildScrollView(
padding: const EdgeInsets.all(24.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Text(
"Willkommen beim Giant Programm Tracker!",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: AppTheme.oneDarkPrimary,
),
),
const SizedBox(height: 16),
const Text(
"Verwalte deine Kettlebell-Trainings effizient.",
textAlign: TextAlign.center,
style: TextStyle(fontSize: 16, color: AppTheme.oneDarkText),
),
const SizedBox(height: 32),
Icon(
Icons.fitness_center,
size: 150,
color: AppTheme.oneDarkText.withOpacity(0.5),
),
const SizedBox(height: 32),
// The main navigation is the BottomNavigationBar now.
// Buttons could be added here for quick access if desired.
],
),
),
),
);
}
}

View file

@ -1,143 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:kettlebell_tracker/providers/settings_provider.dart';
import 'package:kettlebell_tracker/theme/app_theme.dart';
import 'package:kettlebell_tracker/widgets/custom_card.dart';
import 'package:kettlebell_tracker/widgets/custom_text_field.dart';
class SettingsScreen extends ConsumerStatefulWidget {
const SettingsScreen({super.key});
@override
ConsumerState<SettingsScreen> createState() => _SettingsScreenState();
}
class _SettingsScreenState extends ConsumerState<SettingsScreen> {
final _formKey = GlobalKey<FormState>();
late TextEditingController _trainingTimeController;
late TextEditingController _weightLeftController;
late TextEditingController _weightRightController;
late TextEditingController _goalSetsController;
@override
void initState() {
super.initState();
final settings = ref.read(settingsProvider);
_trainingTimeController = TextEditingController(
text: settings.trainingTimeMinutes.toString(),
);
_weightLeftController = TextEditingController(
text: settings.weightLeft.toString(),
);
_weightRightController = TextEditingController(
text: settings.weightRight.toString(),
);
_goalSetsController = TextEditingController(
text: settings.goalSets.toString(),
);
}
@override
void dispose() {
_trainingTimeController.dispose();
_weightLeftController.dispose();
_weightRightController.dispose();
_goalSetsController.dispose();
super.dispose();
}
void _saveSettings() {
if (_formKey.currentState!.validate()) {
final newSettings = SettingsState(
trainingTimeMinutes: int.parse(_trainingTimeController.text),
weightLeft: double.parse(_weightLeftController.text),
weightRight: double.parse(_weightRightController.text),
goalSets: int.parse(_goalSetsController.text),
);
ref.read(settingsProvider.notifier).updateSettings(newSettings);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Einstellungen gespeichert!'),
backgroundColor: AppTheme.oneDarkGreen,
),
);
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Bitte korrigiere die Fehler.'),
backgroundColor: AppTheme.oneDarkRed,
),
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Einstellungen')),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
CustomCard(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Trainingsparameter",
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: 20),
CustomTextField(
controller: _trainingTimeController,
labelText: "Trainingszeit (Minuten)",
icon: Icons.timer,
keyboardType: TextInputType.number,
),
const SizedBox(height: 16),
CustomTextField(
controller: _weightLeftController,
labelText: "Linke Kettlebell (kg)",
icon: Icons.fitness_center,
keyboardType: TextInputType.number,
),
const SizedBox(height: 16),
CustomTextField(
controller: _weightRightController,
labelText: "Rechte Kettlebell (kg)",
icon: Icons.fitness_center,
keyboardType: TextInputType.number,
),
const SizedBox(height: 16),
const SizedBox(height: 16),
CustomTextField(
controller: _goalSetsController,
labelText: "Ziel-Sätze",
icon: Icons.flag,
keyboardType: TextInputType.number,
),
],
),
),
const SizedBox(height: 24),
ElevatedButton.icon(
onPressed: _saveSettings,
icon: const Icon(Icons.save),
label: const Text('Einstellungen speichern'),
style: ElevatedButton.styleFrom(
backgroundColor: AppTheme.oneDarkPrimary,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 16),
),
),
],
),
),
),
);
}
}

View file

@ -1,448 +0,0 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:kettlebell_tracker/models/training_session.dart';
import 'package:kettlebell_tracker/providers/settings_provider.dart';
import 'package:kettlebell_tracker/providers/training_provider.dart';
import 'package:kettlebell_tracker/theme/app_theme.dart';
import 'package:kettlebell_tracker/widgets/custom_card.dart';
import 'package:kettlebell_tracker/services/api_service.dart';
import 'package:audioplayers/audioplayers.dart';
import 'package:intl/intl.dart';
class TrainingScreen extends ConsumerStatefulWidget {
const TrainingScreen({super.key});
@override
ConsumerState<TrainingScreen> createState() => _TrainingScreenState();
}
class _TrainingScreenState extends ConsumerState<TrainingScreen> {
Timer? _mainTimer;
Timer? _lastSetTimer;
final _audioPlayer = AudioPlayer();
@override
void dispose() {
_stopTimer();
_audioPlayer.dispose();
super.dispose();
}
void _startTraining() {
_audioPlayer.stop();
final settings = ref.read(settingsProvider);
ref.read(trainingProvider.notifier).startTraining(
settings.trainingTimeMinutes,
settings.goalSets,
);
_mainTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
final notifier = ref.read(trainingProvider.notifier);
if (notifier.state.remainingSeconds > 0) {
notifier.tick();
} else {
_stopTimer();
_autoFinishTraining();
}
});
_lastSetTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
ref.read(trainingProvider.notifier).tickLastSetTimer();
});
}
void _stopTimer() {
_mainTimer?.cancel();
_lastSetTimer?.cancel();
_mainTimer = null;
_lastSetTimer = null;
}
void _completeSet() {
ref.read(trainingProvider.notifier).completeSet();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Satz gespeichert!'),
backgroundColor: AppTheme.oneDarkGreen,
duration: Duration(seconds: 1),
),
);
}
void _finishTraining() {
_stopTimer();
_audioPlayer.stop();
final state = ref.read(trainingProvider);
final settings = ref.read(settingsProvider);
final session = TrainingSession(
date: DateTime.now(),
sets: state.setsDone,
weightLeft: settings.weightLeft,
weightRight: settings.weightRight,
repsPerSet: state.currentReps,
duration: state.initialDurationSeconds - state.remainingSeconds,
program: state.currentProgram,
blockDay: state.currentBlockDay,
);
ref.read(trainingProvider.notifier).finishTraining(session);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Training gespeichert!'),
backgroundColor: AppTheme.oneDarkPrimary,
),
);
}
Future<void> _autoFinishTraining() async {
await _audioPlayer.play(AssetSource('sound/notification.mp3'));
_finishTraining();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
'Zeit abgelaufen! Training automatisch beendet und gespeichert.'),
backgroundColor: AppTheme.oneDarkRed,
duration: Duration(seconds: 4),
),
);
}
String _formatDuration(int totalSeconds) {
final duration = Duration(seconds: totalSeconds);
final minutes = duration.inMinutes.remainder(60).toString().padLeft(2, '0');
final seconds = duration.inSeconds.remainder(60).toString().padLeft(2, '0');
return '$minutes:$seconds';
}
@override
Widget build(BuildContext context) {
final trainingState = ref.watch(trainingProvider);
final bool isTrainingRunning = trainingState.isTrainingRunning;
return Scaffold(
appBar: AppBar(title: const Text('Aktuelles Training')),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Header-Bereich mit Programm-Infos
CustomCard(
child: Column(
children: [
Text(
'${trainingState.currentProgram.toUpperCase()}',
style: const TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
color: AppTheme.oneDarkPrimary,
),
),
Text(
'Block Tag: ${trainingState.currentBlockDay}',
style: const TextStyle(
fontSize: 20,
color: AppTheme.oneDarkAccent,
),
),
Text(
'Reps pro Satz: ${trainingState.currentReps}',
style: const TextStyle(
fontSize: 22,
fontWeight: FontWeight.w500,
color: AppTheme.oneDarkYellow,
),
),
],
),
),
const SizedBox(height: 18),
// Timer und Fortschritt
CustomCard(
child: Column(
children: [
const Text(
'Verbleibende Zeit',
style: TextStyle(
fontSize: 16,
color: AppTheme.oneDarkTextWeak,
),
),
Text(
_formatDuration(trainingState.remainingSeconds),
style: const TextStyle(
fontSize: 40,
fontWeight: FontWeight.bold,
color: AppTheme.oneDarkPrimary,
),
),
const SizedBox(height: 10),
LinearProgressIndicator(
value: trainingState.progress,
minHeight: 12,
borderRadius: BorderRadius.circular(6),
color: AppTheme.oneDarkGreen,
backgroundColor: AppTheme.oneDarkTextWeak,
),
const SizedBox(height: 8),
Text(
'Fortschritt: ${(trainingState.progress * 100).toStringAsFixed(0)}%',
style: const TextStyle(
fontSize: 16,
color: AppTheme.oneDarkText,
),
),
],
),
),
const SizedBox(height: 18),
// Aktions-Buttons
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton.icon(
onPressed: isTrainingRunning ? null : _startTraining,
icon: const Icon(Icons.play_arrow, size: 28),
label: const Text('Start', style: TextStyle(fontSize: 18)),
style: ElevatedButton.styleFrom(
backgroundColor: AppTheme.oneDarkGreen,
foregroundColor: Colors.white,
minimumSize: const Size(110, 48),
),
),
ElevatedButton.icon(
onPressed: !isTrainingRunning ? null : _completeSet,
icon: const Icon(Icons.check_circle, size: 28),
label: const Text('Satz', style: TextStyle(fontSize: 18)),
style: ElevatedButton.styleFrom(
backgroundColor: AppTheme.oneDarkPrimary,
foregroundColor: Colors.white,
minimumSize: const Size(110, 48),
),
),
ElevatedButton.icon(
onPressed: !isTrainingRunning ? null : _finishTraining,
icon: const Icon(Icons.stop, size: 28),
label: const Text('Beenden', style: TextStyle(fontSize: 18)),
style: ElevatedButton.styleFrom(
backgroundColor: AppTheme.oneDarkRed,
foregroundColor: Colors.white,
minimumSize: const Size(110, 48),
),
),
],
),
const SizedBox(height: 22),
// Satz-Historie
Text(
"Satz-Historie",
style: Theme.of(context).textTheme.titleLarge?.copyWith(
color: AppTheme.oneDarkAccent,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 10),
CustomCard(
child: SizedBox(
height: 180,
child: trainingState.setTimes.isEmpty
? const Center(
child: Text(
"Noch keine Sätze in dieser Einheit.",
style: TextStyle(color: AppTheme.oneDarkTextWeak),
),
)
: ListView.separated(
itemCount: trainingState.setTimes.length,
separatorBuilder: (_, __) => const Divider(height: 1),
itemBuilder: (context, index) {
final setTime = trainingState.setTimes[index];
return ListTile(
leading: CircleAvatar(
backgroundColor: AppTheme.oneDarkGreen,
child: Text(
'${index + 1}',
style: const TextStyle(color: Colors.white),
),
),
title: Text(
'um ${DateFormat.Hms().format(setTime)}',
style: const TextStyle(fontSize: 16),
),
trailing: Text(
'${trainingState.currentReps} Reps',
style: const TextStyle(
fontSize: 16,
color: AppTheme.oneDarkGreen,
),
),
);
},
),
),
),
],
),
),
);
}
// @override
// Widget build(BuildContext context) {
// final trainingState = ref.watch(trainingProvider);
// final settings = ref.watch(settingsProvider);
// final bool isTrainingRunning = trainingState.isTrainingRunning;
//
// return Scaffold(
// appBar: AppBar(title: const Text('Aktuelles Training')),
// body: SingleChildScrollView(
// padding: const EdgeInsets.all(16.0),
// child: Column(
// children: [
// CustomCard(
// child: Column(
// children: [
// Text(
// 'Programm: ${trainingState.currentProgram.toUpperCase()}',
// style: const TextStyle(
// fontSize: 20,
// color: AppTheme.oneDarkYellow,
// ),
// ),
// Text(
// 'Block Tag: ${trainingState.currentBlockDay}',
// style: const TextStyle(
// fontSize: 18,
// color: AppTheme.oneDarkAccent,
// ),
// ),
// Text(
// 'Verbleibende Zeit: ${_formatDuration(trainingState.remainingSeconds)}',
// style: const TextStyle(
// fontSize: 26,
// fontWeight: FontWeight.bold,
// color: AppTheme.oneDarkPrimary,
// ),
// ),
// const SizedBox(height: 12),
// Text(
// 'Sätze: ${trainingState.setsDone}',
// style: const TextStyle(
// fontSize: 24,
// color: AppTheme.oneDarkYellow,
// ),
// ),
// const SizedBox(height: 8),
// Text(
// 'Reps pro Satz: ${trainingState.currentReps}',
// style: const TextStyle(
// fontSize: 20,
// color: AppTheme.oneDarkAccent,
// ),
// ),
// const SizedBox(height: 12),
// Text(
// 'Zeit seit letztem Satz: ${_formatDuration(trainingState.secondsSinceLastSet)}',
// style: const TextStyle(
// fontSize: 18,
// color: AppTheme.oneDarkTextWeak,
// ),
// ),
// const SizedBox(height: 20),
// LinearProgressIndicator(
// value: trainingState.progress,
// minHeight: 10,
// borderRadius: BorderRadius.circular(5),
// ),
// const SizedBox(height: 8),
// Text(
// 'Fortschritt: ${(trainingState.progress * 100).toStringAsFixed(0)}%',
// style: const TextStyle(
// fontSize: 16,
// color: AppTheme.oneDarkText,
// ),
// ),
// ],
// ),
// ),
// const SizedBox(height: 20),
// Row(
// mainAxisAlignment: MainAxisAlignment.spaceEvenly,
// children: [
// ElevatedButton.icon(
// onPressed: isTrainingRunning ? null : _startTraining,
// icon: const Icon(Icons.play_arrow),
// label: const Text('Start'),
// style: ElevatedButton.styleFrom(
// backgroundColor: AppTheme.oneDarkGreen,
// foregroundColor: AppTheme.oneDarkTextWeak,
// ),
// ),
// ElevatedButton.icon(
// onPressed: !isTrainingRunning ? null : _completeSet,
// icon: const Icon(Icons.check_circle),
// label: const Text('Satz'),
// style: ElevatedButton.styleFrom(
// backgroundColor: AppTheme.oneDarkPrimary,
// foregroundColor: AppTheme.oneDarkTextWeak,
// ),
// ),
// ElevatedButton.icon(
// onPressed: !isTrainingRunning ? null : _finishTraining,
// icon: const Icon(Icons.stop),
// label: const Text('Beenden'),
// style: ElevatedButton.styleFrom(
// backgroundColor: AppTheme.oneDarkRed,
// foregroundColor: AppTheme.oneDarkTextWeak,
// ),
// ),
// ],
// ),
// const SizedBox(height: 20),
// const Divider(),
// Text(
// "Satz-Historie",
// style: Theme.of(
// context,
// ).textTheme.titleLarge?.copyWith(color: AppTheme.oneDarkAccent),
// ),
// const SizedBox(height: 10),
// CustomCard(
// child: SizedBox(
// height: 150,
// child: trainingState.setTimes.isEmpty
// ? const Center(
// child: Text(
// "Noch keine Sätze in dieser Einheit.",
// style: TextStyle(color: AppTheme.oneDarkTextWeak),
// ),
// )
// : ListView.builder(
// itemCount: trainingState.setTimes.length,
// itemBuilder: (context, index) {
// final setTime = trainingState.setTimes[index];
// return Center(
// child: Text(
// '#${index + 1} um ${DateFormat.Hms().format(setTime)} (${trainingState.currentReps} Reps)',
// style: const TextStyle(
// fontSize: 16,
// color: AppTheme.oneDarkGreen,
// ),
// ),
// );
// },
// ),
// ),
// ),
// ],
// ),
// ),
// );
}

View file

@ -1,30 +0,0 @@
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'package:kettlebell_tracker/services/device_service.dart';
Future<void> sendTrainingToBackend({
required int reps,
required double rest,
required int sets,
}) async {
final uuid = await DeviceIdService.getOrCreateUUID();
final url = Uri.parse('http://192.169.178.43:8080/trainings/');
final response = await http
.post(
url,
headers: {'Content-Type': 'application/json'},
body: jsonEncode({
'reps': reps,
'rest': rest,
'sets': sets,
'uuid': uuid,
}),
)
.timeout(const Duration(seconds: 5));
if (response.statusCode == 200) {
print('Training erfolgreich an Backend gesendet.');
} else {
print('Fehler beim Senden: ${response.body}');
}
}

View file

@ -1,78 +0,0 @@
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
import 'package:kettlebell_tracker/models/training_session.dart';
class DatabaseHelper {
static final DatabaseHelper _instance = DatabaseHelper._internal();
factory DatabaseHelper() => _instance;
DatabaseHelper._internal();
static Database? _database;
Future<Database> get database async {
if (_database != null) return _database!;
_database = await _initDb();
return _database!;
}
// Initialize the database
Future<Database> _initDb() async {
final dbPath = await getDatabasesPath();
final path = join(dbPath, 'giant_training.db');
return await openDatabase(
path,
version: 2, // Version erhöhen!
onCreate: _onCreate,
onUpgrade: _onUpgrade,
);
}
void _onUpgrade(Database db, int oldVersion, int newVersion) async {
if (oldVersion < 2) {
await db.execute('ALTER TABLE training ADD COLUMN program TEXT');
await db.execute('ALTER TABLE training ADD COLUMN blockDay INTEGER');
}
}
// Create the database table
void _onCreate(Database db, int version) async {
await db.execute('''
CREATE TABLE training (
id INTEGER PRIMARY KEY AUTOINCREMENT,
date TEXT NOT NULL,
sets INTEGER,
weightLeft REAL,
weightRight REAL,
repsPerSet INTEGER,
duration INTEGER,
program TEXT,
blockDay INTEGER
)
''');
}
// Save a training session
Future<void> saveTraining(TrainingSession session) async {
final db = await database;
await db.insert(
'training',
session.toMap(),
conflictAlgorithm: ConflictAlgorithm.replace,
);
}
// Get training history (last 20 entries)
Future<List<TrainingSession>> getHistory() async {
final db = await database;
final List<Map<String, dynamic>> maps = await db.query(
'training',
orderBy: 'date DESC',
limit: 20,
);
return List.generate(maps.length, (i) {
return TrainingSession.fromMap(maps[i]);
});
}
}

View file

@ -1,16 +0,0 @@
import 'package:uuid/uuid.dart';
import 'package:shared_preferences/shared_preferences.dart';
class DeviceIdService {
static const _uuidKey = 'device_uuid';
static Future<String> getOrCreateUUID() async {
final prefs = await SharedPreferences.getInstance();
String? uuid = prefs.getString(_uuidKey);
if (uuid == null) {
uuid = const Uuid().v4();
await prefs.setString(_uuidKey, uuid);
}
return uuid;
}
}

View file

@ -1,63 +0,0 @@
import 'package:flutter/material.dart';
// --- OneDark Theme Colors, translated to Flutter ---
class AppTheme {
static const Color oneDarkBg = Color(0xFF282c34);
static const Color oneDarkSurface = Color(0xFF21252b);
static const Color oneDarkPrimary = Color(0xFF61afef);
static const Color oneDarkAccent = Color(0xFFc678dd);
static const Color oneDarkGreen = Color(0xFF98c379);
static const Color oneDarkRed = Color(0xFFe06c75);
static const Color oneDarkYellow = Color(0xFFe5c07b);
static const Color oneDarkText = Color(0xFFabb2bf);
static const Color oneDarkTextWeak = Color(0xFF5c6370);
static final ThemeData darkTheme = ThemeData.dark().copyWith(
primaryColor: oneDarkPrimary,
scaffoldBackgroundColor: oneDarkBg,
colorScheme: const ColorScheme.dark(
primary: oneDarkPrimary,
secondary: oneDarkAccent,
surface: oneDarkSurface,
onPrimary: Colors.black, // Text on primary color buttons
onSecondary: Colors.black,
onSurface: oneDarkText,
error: oneDarkRed,
onError: Colors.black,
),
appBarTheme: const AppBarTheme(
backgroundColor: oneDarkSurface,
foregroundColor: oneDarkText,
centerTitle: true,
elevation: 0,
),
inputDecorationTheme: const InputDecorationTheme(
labelStyle: TextStyle(color: oneDarkText),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: oneDarkAccent),
borderRadius: BorderRadius.all(Radius.circular(8.0)),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: oneDarkPrimary),
borderRadius: BorderRadius.all(Radius.circular(8.0)),
),
prefixIconColor: oneDarkTextWeak,
),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ButtonStyle(
shape: WidgetStateProperty.all<RoundedRectangleBorder>(
RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.0)),
),
),
),
progressIndicatorTheme: const ProgressIndicatorThemeData(
color: oneDarkPrimary,
linearTrackColor: oneDarkSurface,
),
dividerTheme: const DividerThemeData(color: oneDarkAccent, thickness: 1),
snackBarTheme: const SnackBarThemeData(
backgroundColor: oneDarkPrimary,
contentTextStyle: TextStyle(color: oneDarkTextWeak),
),
);
}

View file

@ -1,29 +0,0 @@
import 'package:flutter/material.dart';
import 'package:kettlebell_tracker/theme/app_theme.dart';
class CustomCard extends StatelessWidget {
final Widget child;
final EdgeInsets padding;
final EdgeInsets margin;
const CustomCard({
super.key,
required this.child,
this.padding = const EdgeInsets.all(16.0),
this.margin = const EdgeInsets.symmetric(vertical: 8.0),
});
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
margin: margin,
padding: padding,
decoration: BoxDecoration(
color: AppTheme.oneDarkSurface,
borderRadius: BorderRadius.circular(10),
),
child: child,
);
}
}

View file

@ -1,42 +0,0 @@
import 'package:flutter/material.dart';
class CustomTextField extends StatelessWidget {
final TextEditingController controller;
final String labelText;
final IconData icon;
final TextInputType keyboardType;
final int? maxLines;
const CustomTextField({
super.key,
required this.controller,
required this.labelText,
required this.icon,
this.keyboardType = TextInputType.text,
this.maxLines = 1,
});
@override
Widget build(BuildContext context) {
return TextFormField(
controller: controller,
keyboardType: keyboardType,
maxLines: maxLines,
decoration: InputDecoration(
labelText: labelText,
prefixIcon: Icon(icon),
border: OutlineInputBorder(borderRadius: BorderRadius.circular(8.0)),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Dieses Feld darf nicht leer sein.';
}
if (keyboardType == TextInputType.number &&
double.tryParse(value) == null) {
return 'Bitte eine gültige Zahl eingeben.';
}
return null;
},
);
}
}

1
linux/.gitignore vendored
View file

@ -1 +0,0 @@
flutter/ephemeral

View file

@ -1,128 +0,0 @@
# Project-level configuration.
cmake_minimum_required(VERSION 3.13)
project(runner LANGUAGES CXX)
# The name of the executable created for the application. Change this to change
# the on-disk name of your application.
set(BINARY_NAME "kettlebell_tracker")
# The unique GTK application identifier for this application. See:
# https://wiki.gnome.org/HowDoI/ChooseApplicationID
set(APPLICATION_ID "com.example.kettlebell_tracker")
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
# versions of CMake.
cmake_policy(SET CMP0063 NEW)
# Load bundled libraries from the lib/ directory relative to the binary.
set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
# Root filesystem for cross-building.
if(FLUTTER_TARGET_PLATFORM_SYSROOT)
set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT})
set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
endif()
# Define build configuration options.
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE "Debug" CACHE
STRING "Flutter build mode" FORCE)
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
"Debug" "Profile" "Release")
endif()
# Compilation settings that should be applied to most targets.
#
# Be cautious about adding new options here, as plugins use this function by
# default. In most cases, you should add new options to specific targets instead
# of modifying this function.
function(APPLY_STANDARD_SETTINGS TARGET)
target_compile_features(${TARGET} PUBLIC cxx_std_14)
target_compile_options(${TARGET} PRIVATE -Wall -Werror)
target_compile_options(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:-O3>")
target_compile_definitions(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:NDEBUG>")
endfunction()
# Flutter library and tool build rules.
set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
add_subdirectory(${FLUTTER_MANAGED_DIR})
# System-level dependencies.
find_package(PkgConfig REQUIRED)
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
# Application build; see runner/CMakeLists.txt.
add_subdirectory("runner")
# Run the Flutter tool portions of the build. This must not be removed.
add_dependencies(${BINARY_NAME} flutter_assemble)
# Only the install-generated bundle's copy of the executable will launch
# correctly, since the resources must in the right relative locations. To avoid
# people trying to run the unbundled copy, put it in a subdirectory instead of
# the default top-level location.
set_target_properties(${BINARY_NAME}
PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run"
)
# Generated plugin build rules, which manage building the plugins and adding
# them to the application.
include(flutter/generated_plugins.cmake)
# === Installation ===
# By default, "installing" just makes a relocatable bundle in the build
# directory.
set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle")
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
endif()
# Start with a clean build bundle directory every time.
install(CODE "
file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\")
" COMPONENT Runtime)
set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib")
install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
COMPONENT Runtime)
install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
COMPONENT Runtime)
install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES})
install(FILES "${bundled_library}"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
endforeach(bundled_library)
# Copy the native assets provided by the build.dart from all packages.
set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/")
install(DIRECTORY "${NATIVE_ASSETS_DIR}"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
# Fully re-copy the assets directory on each build to avoid having stale files
# from a previous install.
set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
install(CODE "
file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
" COMPONENT Runtime)
install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
# Install the AOT library on non-Debug builds only.
if(NOT CMAKE_BUILD_TYPE MATCHES "Debug")
install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
endif()

View file

@ -1,88 +0,0 @@
# This file controls Flutter-level build steps. It should not be edited.
cmake_minimum_required(VERSION 3.10)
set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
# Configuration provided via flutter tool.
include(${EPHEMERAL_DIR}/generated_config.cmake)
# TODO: Move the rest of this into files in ephemeral. See
# https://github.com/flutter/flutter/issues/57146.
# Serves the same purpose as list(TRANSFORM ... PREPEND ...),
# which isn't available in 3.10.
function(list_prepend LIST_NAME PREFIX)
set(NEW_LIST "")
foreach(element ${${LIST_NAME}})
list(APPEND NEW_LIST "${PREFIX}${element}")
endforeach(element)
set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE)
endfunction()
# === Flutter Library ===
# System-level dependencies.
find_package(PkgConfig REQUIRED)
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0)
pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0)
set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so")
# Published to parent scope for install step.
set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE)
list(APPEND FLUTTER_LIBRARY_HEADERS
"fl_basic_message_channel.h"
"fl_binary_codec.h"
"fl_binary_messenger.h"
"fl_dart_project.h"
"fl_engine.h"
"fl_json_message_codec.h"
"fl_json_method_codec.h"
"fl_message_codec.h"
"fl_method_call.h"
"fl_method_channel.h"
"fl_method_codec.h"
"fl_method_response.h"
"fl_plugin_registrar.h"
"fl_plugin_registry.h"
"fl_standard_message_codec.h"
"fl_standard_method_codec.h"
"fl_string_codec.h"
"fl_value.h"
"fl_view.h"
"flutter_linux.h"
)
list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/")
add_library(flutter INTERFACE)
target_include_directories(flutter INTERFACE
"${EPHEMERAL_DIR}"
)
target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}")
target_link_libraries(flutter INTERFACE
PkgConfig::GTK
PkgConfig::GLIB
PkgConfig::GIO
)
add_dependencies(flutter flutter_assemble)
# === Flutter tool backend ===
# _phony_ is a non-existent file to force this command to run every time,
# since currently there's no way to get a full input/output list from the
# flutter tool.
add_custom_command(
OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
${CMAKE_CURRENT_BINARY_DIR}/_phony_
COMMAND ${CMAKE_COMMAND} -E env
${FLUTTER_TOOL_ENVIRONMENT}
"${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh"
${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE}
VERBATIM
)
add_custom_target(flutter_assemble DEPENDS
"${FLUTTER_LIBRARY}"
${FLUTTER_LIBRARY_HEADERS}
)

View file

@ -1,15 +0,0 @@
//
// Generated file. Do not edit.
//
// clang-format off
#include "generated_plugin_registrant.h"
#include <audioplayers_linux/audioplayers_linux_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) audioplayers_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "AudioplayersLinuxPlugin");
audioplayers_linux_plugin_register_with_registrar(audioplayers_linux_registrar);
}

View file

@ -1,15 +0,0 @@
//
// Generated file. Do not edit.
//
// clang-format off
#ifndef GENERATED_PLUGIN_REGISTRANT_
#define GENERATED_PLUGIN_REGISTRANT_
#include <flutter_linux/flutter_linux.h>
// Registers Flutter plugins.
void fl_register_plugins(FlPluginRegistry* registry);
#endif // GENERATED_PLUGIN_REGISTRANT_

Some files were not shown because too many files have changed in this diff Show more