20 Commits

Author SHA1 Message Date
98077e7d57 add local notifications 2024-12-12 21:55:27 -05:00
54d47245ae Merge pull request 'drift-impl' (#3) from drift-impl into main
Reviewed-on: #3
2024-12-10 00:19:53 -08:00
7f2cf0b49f more seed data and passing data along 2024-12-10 02:26:34 -05:00
8fe60e7ae3 officially getting data from the dbgit add . -p! 2024-12-10 02:10:02 -05:00
b2e2eb67b0 seeded db, updated some db fields 2024-12-09 00:06:44 -05:00
0dc7c3ced0 upgraded kotlin, gradle, added drift 2024-12-08 22:25:23 -05:00
13fe7e2ef4 more prep work for local data storage 2024-12-08 14:40:12 -05:00
d6e62024d7 added dash for local db, ui prep work 2024-12-08 14:14:10 -05:00
4094f7edba Merge pull request 'activity counter' (#2) from activity-actions into main
Reviewed-on: #2
2024-12-07 20:45:35 -08:00
586d2355c9 moving list view 2024-12-07 23:32:56 -05:00
4e5eeec937 progress indicator 2024-12-07 16:25:04 -05:00
0c0f596fbb reset timer and activity when complete 2024-12-07 15:25:03 -05:00
f781001d3b upgraded dart and packages, analyze fixes, action type timer management 2024-12-07 13:13:45 -05:00
19f835d8f2 rework timer and how we manage actions 2024-12-06 17:33:33 -05:00
56b25a6963 allow for reps and times 2024-12-06 01:28:43 -05:00
5bae1aa416 alternating timer types 2024-12-05 00:44:13 -05:00
29479e8aba setting up timer and action management 2024-12-01 21:23:12 -05:00
9ffa0d178c added provider class for timer 2024-12-01 01:42:03 -05:00
932e9cd6a4 add provider package 2024-11-30 12:46:19 -05:00
d42696df61 show rep and set counts 2024-11-30 12:37:38 -05:00
38 changed files with 5731 additions and 618 deletions

144
.gitignore vendored
View File

@ -9,6 +9,7 @@
.history
.svn/
migrate_working_dir/
bkp
# IntelliJ related
*.iml
@ -32,6 +33,7 @@ migrate_working_dir/
.pub/
/build/
pubspec.lock
devtools_options.yaml
# Symbolication related
app.*.symbols
@ -43,3 +45,145 @@ app.*.map.json
/android/app/debug
/android/app/profile
/android/app/release
# Do not remove or rename entries in this file, only add new ones
# See https://github.com/flutter/flutter/issues/128635 for more context.
# Miscellaneous
*.class
*.lock
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# Visual Studio Code related
.classpath
.project
.settings/
.vscode/*
# Flutter repo-specific
/bin/cache/
/bin/internal/bootstrap.bat
/bin/internal/bootstrap.sh
/bin/mingit/
/dev/benchmarks/mega_gallery/
/dev/bots/.recipe_deps
/dev/bots/android_tools/
/dev/devicelab/ABresults*.json
/dev/docs/doc/
/dev/docs/api_docs.zip
/dev/docs/flutter.docs.zip
/dev/docs/lib/
/dev/docs/pubspec.yaml
/dev/integration_tests/**/xcuserdata
/dev/integration_tests/**/Pods
/packages/flutter/coverage/
version
analysis_benchmark.json
# packages file containing multi-root paths
.packages.generated
# Flutter/Dart/Pub related
**/doc/api/
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
**/generated_plugin_registrant.dart
.packages
.pub-preload-cache/
.pub-cache/
.pub/
build/
flutter_*.png
linked_*.ds
unlinked.ds
unlinked_spec.ds
# Android related
**/android/**/gradle-wrapper.jar
.gradle/
**/android/captures/
**/android/gradlew
**/android/gradlew.bat
**/android/local.properties
**/android/**/GeneratedPluginRegistrant.java
**/android/key.properties
*.jks
# iOS/XCode related
**/ios/**/*.mode1v3
**/ios/**/*.mode2v3
**/ios/**/*.moved-aside
**/ios/**/*.pbxuser
**/ios/**/*.perspectivev3
**/ios/**/*sync/
**/ios/**/.sconsign.dblite
**/ios/**/.tags*
**/ios/**/.vagrant/
**/ios/**/DerivedData/
**/ios/**/Icon?
**/ios/**/Pods/
**/ios/**/.symlinks/
**/ios/**/profile
**/ios/**/xcuserdata
**/ios/.generated/
**/ios/Flutter/.last_build_id
**/ios/Flutter/App.framework
**/ios/Flutter/Flutter.framework
**/ios/Flutter/Flutter.podspec
**/ios/Flutter/Generated.xcconfig
**/ios/Flutter/ephemeral
**/ios/Flutter/app.flx
**/ios/Flutter/app.zip
**/ios/Flutter/flutter_assets/
**/ios/Flutter/flutter_export_environment.sh
**/ios/ServiceDefinitions.json
**/ios/Runner/GeneratedPluginRegistrant.*
# macOS
**/Flutter/ephemeral/
**/Pods/
**/macos/Flutter/GeneratedPluginRegistrant.swift
**/macos/Flutter/ephemeral
**/xcuserdata/
# Windows
**/windows/flutter/ephemeral/
**/windows/flutter/generated_plugin_registrant.cc
**/windows/flutter/generated_plugin_registrant.h
**/windows/flutter/generated_plugins.cmake
# Linux
**/linux/flutter/ephemeral/
**/linux/flutter/generated_plugin_registrant.cc
**/linux/flutter/generated_plugin_registrant.h
**/linux/flutter/generated_plugins.cmake
# Coverage
coverage/
# Symbols
app.*.symbols
# Exceptions to above rules.
!**/ios/**/default.mode1v3
!**/ios/**/default.mode2v3
!**/ios/**/default.pbxuser
!**/ios/**/default.perspectivev3
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
!/dev/ci/**/Gemfile.lock
!.vscode/settings.json

View File

@ -1,11 +1,11 @@
# 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.
# This file should be version controlled and should not be manually edited.
version:
revision: 9944297138845a94256f1cf37beb88ff9a8e811a
channel: stable
revision: "dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668"
channel: "stable"
project_type: app
@ -13,26 +13,26 @@ project_type: app
migration:
platforms:
- platform: root
create_revision: 9944297138845a94256f1cf37beb88ff9a8e811a
base_revision: 9944297138845a94256f1cf37beb88ff9a8e811a
create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
- platform: android
create_revision: 9944297138845a94256f1cf37beb88ff9a8e811a
base_revision: 9944297138845a94256f1cf37beb88ff9a8e811a
create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
- platform: ios
create_revision: 9944297138845a94256f1cf37beb88ff9a8e811a
base_revision: 9944297138845a94256f1cf37beb88ff9a8e811a
create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
- platform: linux
create_revision: 9944297138845a94256f1cf37beb88ff9a8e811a
base_revision: 9944297138845a94256f1cf37beb88ff9a8e811a
create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
- platform: macos
create_revision: 9944297138845a94256f1cf37beb88ff9a8e811a
base_revision: 9944297138845a94256f1cf37beb88ff9a8e811a
create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
- platform: web
create_revision: 9944297138845a94256f1cf37beb88ff9a8e811a
base_revision: 9944297138845a94256f1cf37beb88ff9a8e811a
create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
- platform: windows
create_revision: 9944297138845a94256f1cf37beb88ff9a8e811a
base_revision: 9944297138845a94256f1cf37beb88ff9a8e811a
create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
# User provided section

View File

@ -1,74 +0,0 @@
{
// Place your snippets for dart here. Each snippet is defined under a snippet name and has a prefix, body and
// description. The prefix is what is used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
// $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders. Placeholders with the
// same ids are connected.
// Example:
// "Print to console": {
// "prefix": "log",
// "body": [
// "console.log('$1');",
// "$2"
// ],
// "description": "Log output to console"
// }
"Stateless Widget": {
"prefix": "stateless",
"body": [
"import 'package:flutter/material.dart';",
"\n",
"class ${1:MyClass} extends StatelessWidget {",
"@override",
"Widget build(BuildContext context) {",
"return Container($0);}}"
]
},
"Stateful Widget": {
"prefix": "stateful",
"body": [
"import 'package:flutter/material.dart';",
"\n",
"class ${1:MyClass} extends StatefulWidget {",
"const ${1:MyClass}({Key key, $2}) : super(key: key);",
"\n",
"@override",
"_${1:MyClass}State createState() => _${1:MyClass}State();",
"}",
"\n",
"class _${1:MyClass}State extends State<${1:MyClass}> {",
"\n",
"@override",
"void initState() {",
"super.initState();",
"}",
"\n",
"@override",
"void dispose() {",
"super.dispose();",
"}",
"\n",
"@override",
"Widget build(BuildContext context) {",
"return Container($0);}}"
]
},
"Widget Test": {
"prefix": "widgettest",
"body": [
"import 'package:flutter_test/flutter_test.dart';",
"import 'package:flutter/material.dart';",
"//import 'package:piota/${1:widgetfile}.dart';",
"\n",
"import 'ui_test_util.dart';",
"\n",
"void main() {",
"group('${2:groupname}', () {",
"final testableWidget = testWidget(${3:Container()},Size(375, 667));",
"testWidgets('${3:Container()} test', (WidgetTester tester) async {",
"final finder = find.byKey(Key('${5:keyname}'));",
"await tester.pumpWidget(testableWidget);",
"expect(finder, findsOneWidget);",
"});});}"
]
}
}

2
android/.gitignore vendored
View File

@ -7,7 +7,7 @@ gradle-wrapper.jar
GeneratedPluginRegistrant.java
# Remember to never publicly share your keystore.
# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
# See https://flutter.dev/to/reference-keystore
key.properties
**/*.keystore
**/*.jks

View File

@ -1,71 +1,44 @@
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
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"
}
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion flutter.compileSdkVersion
ndkVersion flutter.ndkVersion
namespace = "com.example.sendtrain"
compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
jvmTarget = JavaVersion.VERSION_1_8
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.sendtrain.sendtrain"
applicationId = "com.example.sendtrain"
// You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
minSdkVersion flutter.minSdkVersion
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
// 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.debug
signingConfig = signingConfigs.debug
}
}
}
flutter {
source '../..'
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
source = "../.."
}

View File

@ -1,5 +1,4 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.sendtrain.sendtrain">
<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.

View File

@ -1,13 +1,13 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.sendtrain.sendtrain">
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:label="SendTrain"
android:label="sendtrain"
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"
@ -31,4 +31,15 @@
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

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

View File

@ -1,6 +0,0 @@
package com.sendtrain.sendtrain
import io.flutter.embedding.android.FlutterActivity
class MainActivity: FlutterActivity() {
}

View File

@ -1,5 +1,4 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.sendtrain.sendtrain">
<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.

View File

@ -1,16 +1,3 @@
buildscript {
ext.kotlin_version = '1.7.10'
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.2.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
google()
@ -18,12 +5,12 @@ allprojects {
}
}
rootProject.buildDir = '../build'
rootProject.buildDir = "../build"
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(':app')
project.evaluationDependsOn(":app")
}
tasks.register("clean", Delete) {

View File

@ -1,3 +1,3 @@
org.gradle.jvmargs=-Xmx1536M
org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true
android.enableJetifier=true

View File

@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip

View File

@ -1,11 +1,25 @@
include ':app'
pluginManagement {
def flutterSdkPath = {
def properties = new Properties()
file("local.properties").withInputStream { properties.load(it) }
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
return flutterSdkPath
}()
def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
def properties = new Properties()
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
assert localPropertiesFile.exists()
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version "8.1.0" apply false
id "org.jetbrains.kotlin.android" version "2.1.0" apply false
}
include ":app"

View File

@ -1,33 +0,0 @@
PODS:
- Flutter (1.0.0)
- flutter_inappwebview (0.0.1):
- Flutter
- flutter_inappwebview/Core (= 0.0.1)
- OrderedSet (~> 5.0)
- flutter_inappwebview/Core (0.0.1):
- Flutter
- OrderedSet (~> 5.0)
- OrderedSet (5.0.0)
DEPENDENCIES:
- Flutter (from `Flutter`)
- flutter_inappwebview (from `.symlinks/plugins/flutter_inappwebview/ios`)
SPEC REPOS:
trunk:
- OrderedSet
EXTERNAL SOURCES:
Flutter:
:path: Flutter
flutter_inappwebview:
:path: ".symlinks/plugins/flutter_inappwebview/ios"
SPEC CHECKSUMS:
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
flutter_inappwebview: bfd58618f49dc62f2676de690fc6dcda1d6c3721
OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c
PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3
COCOAPODS: 1.12.0

View File

@ -0,0 +1,12 @@
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

@ -14,6 +14,76 @@ class ActivityAction {
required this.activityActionSet,
this.media,
});
List<List<Map<String, dynamic>>> items() {
List<List<Map<String, dynamic>>> sets = [];
Reps reps = activityActionSet.reps;
int totalActions = 0;
for (int i = 0; i < activityActionSet.total; i++) {
List<Map<String, dynamic>> actions = [];
int? weight = _setWeight(i);
actions.add({
'actionID': totalActions++,
'name': title,
'type': reps.type,
'amount': reps.amounts[i],
'weight': weight,
});
if (activityActionSet.type == 'alternating') {
if (reps.rest != null) {
actions.add({
'actionID': totalActions++,
'name': 'Rest',
'type': 'seconds',
'amount': reps.rest! ~/ 1000,
});
}
actions.add({
'actionID': totalActions++,
'name': title,
'type': reps.type,
'amount': reps.amounts[i],
'weights': weight,
});
}
actions.add({
'actionID': totalActions++,
'name': 'Rest',
'type': 'seconds',
'amount': activityActionSet.rest ~/ 1000,
});
sets.add(actions);
// sets.add([{
// 'actionID': totalActions++,
// 'name': 'Rest',
// 'type': 'seconds',
// 'amount': activityActionSet.rest ~/ 1000,
// }]);
// for (int j = 0; i < activityActionSet.reps.amounts; j++) {}
}
return sets;
}
int? _setWeight(setNum) {
Reps reps = activityActionSet.reps;
if (reps.weights.length == activityActionSet.total) {
return reps.weights[setNum];
} else if (reps.weights.length == 1) {
return reps.weights[0];
}
return null;
}
}
class Set {
@ -34,14 +104,14 @@ class Reps {
String type;
List<int> tempo;
List<int> amounts;
List<int> weights;
int rest;
List<int> weights = [];
int? rest;
Reps({
required this.type,
required this.tempo,
required this.amounts,
required this.weights,
required this.rest,
this.rest,
});
}

121
lib/database.dart Normal file
View File

@ -0,0 +1,121 @@
import 'package:drift/drift.dart';
import 'package:drift_flutter/drift_flutter.dart';
part 'database.g.dart';
enum SessionStatus {
pending,
started,
completed,
missed
}
class Sessions extends Table {
IntColumn get id => integer().autoIncrement()();
TextColumn get title => text().withLength(min: 3, max: 32)();
TextColumn get content => text().named('body')();
TextColumn get status => textEnum<SessionStatus>()();
DateTimeColumn get date => dateTime().nullable()();
DateTimeColumn get createdAt => dateTime().withDefault(Variable(DateTime.now()))();
}
class SessionActivities extends Table {
IntColumn get id => integer().autoIncrement()();
IntColumn get sessionId => integer().references(Sessions, #id)();
IntColumn get activityId => integer().references(Activities, #id)();
TextColumn get results => text().nullable()();
TextColumn get achievements => text().nullable()();
DateTimeColumn get createdAt => dateTime().withDefault(Variable(DateTime.now()))();
}
enum ActivityCategories {
fundamentals,
conditioning,
advanced,
custom,
pro
}
enum ActivityType {
strength,
power,
conditioning,
hypertrophy,
endurance,
stability,
mobility,
flexibility,
rehabilitation,
technical
}
class Activities extends Table {
IntColumn get id => integer().autoIncrement()();
TextColumn get title => text().withLength(min: 3, max: 32)();
TextColumn get type => textEnum<ActivityType>()();
TextColumn get description => text().named('body')();
TextColumn get category => textEnum<ActivityCategories>()();
DateTimeColumn get createdAt => dateTime().withDefault(Variable(DateTime.now()))();
}
class ActivityActions extends Table {
IntColumn get id => integer().autoIncrement()();
IntColumn get activityId => integer().references(Activities, #id)();
IntColumn get actionId => integer().references(Actions, #id)();
DateTimeColumn get createdAt => dateTime().withDefault(Variable(DateTime.now()))();
}
class Actions extends Table {
IntColumn get id => integer().autoIncrement()();
TextColumn get title => text().withLength(min: 3, max: 32)();
TextColumn get description => text().named('body')();
TextColumn get set => text()();
DateTimeColumn get createdAt => dateTime().withDefault(Variable(DateTime.now()))();
}
class ObjectMediaItems extends Table {
IntColumn get id => integer().autoIncrement()();
IntColumn get objectId => integer().references(Actions, #id)();
IntColumn get mediaId => integer().references(MediaItems, #id)();
DateTimeColumn get createdAt => dateTime().withDefault(Variable(DateTime.now()))();
}
enum MediaType {
youtube,
image
}
class MediaItems extends Table {
IntColumn get id => integer().autoIncrement()();
TextColumn get title => text().withLength(min: 3, max: 32)();
TextColumn get description => text().named('body')();
TextColumn get reference => text().withLength(min: 3, max: 32)();
TextColumn get type => textEnum<MediaType>()();
DateTimeColumn get createdAt => dateTime().withDefault(Variable(DateTime.now()))();
}
@DriftDatabase(tables: [
Sessions,
SessionActivities,
Activities,
ActivityActions,
Actions,
ObjectMediaItems,
MediaItems
])
class AppDatabase extends _$AppDatabase {
// After generating code, this class needs to define a `schemaVersion` getter
// and a constructor telling drift where the database should be stored.
// These are described in the getting started guide: https://drift.simonbinder.eu/setup/
AppDatabase() : super(_openConnection());
@override
int get schemaVersion => 1;
static QueryExecutor _openConnection() {
// `driftDatabase` from `package:drift_flutter` stores the database in
// `getApplicationDocumentsDirectory()`.
return driftDatabase(name: 'sendtrain');
}
}

4487
lib/database.g.dart Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,8 @@
import 'package:drift/drift.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:sendtrain/database.dart';
import 'package:sendtrain/models/activity_timer_model.dart';
import 'package:sendtrain/screens/activities_screen.dart';
import 'package:sendtrain/screens/sessions_screen.dart';
@ -42,7 +46,7 @@ class _AppState extends State<App> {
body: Padding(
padding: const EdgeInsets.fromLTRB(0, 50, 0, 0),
child: <Widget>[
const SessionsScreen(),
SessionsScreen(),
const ActivitiesScreen(),
Container(
alignment: Alignment.center,
@ -88,5 +92,86 @@ class _AppState extends State<App> {
}
void main() {
runApp(const SendTrain());
// final database = AppDatabase();
// database.into(database.sessions).insert(SessionsCompanion.insert(
// title: 'Projecting @ Climbers Rock',
// content: 'Beta pully beta beta pinch one arm crimpy. Futuristic pinch, dyno dynamic drop knee climb. Climbing ondra slopey onsight beta ondra power endurance.',
// status: SessionStatus.started,
// date: Value(DateTime.now())));
// database.into(database.sessions).insert(SessionsCompanion.insert(
// title: 'Moonboard @ Boardroom',
// content: 'Beta pully beta beta pinch one arm crimpy. Futuristic pinch, dyno dynamic drop knee climb. Climbing ondra slopey onsight beta ondra power endurance.',
// status: SessionStatus.pending,
// date: Value(DateTime.now())));
// database.into(database.sessions).insert(SessionsCompanion.insert(
// title: 'Moonboard @ Boardroom',
// content: 'Beta pully beta beta pinch one arm crimpy. Futuristic pinch, dyno dynamic drop knee climb. Climbing ondra slopey onsight beta ondra power endurance.',
// status: SessionStatus.completed,
// date: Value(DateTime.now())));
// database.into(database.sessions).insert(SessionsCompanion.insert(
// title: 'Projecting @ Climbers Rock',
// content: 'Beta pully beta beta pinch one arm crimpy. Futuristic pinch, dyno dynamic drop knee climb. Climbing ondra slopey onsight beta ondra power endurance.',
// status: SessionStatus.completed,
// date: Value(DateTime.now())));
// database.into(database.sessions).insert(SessionsCompanion.insert(
// title: 'Off-Wall Training',
// content: 'Beta pully beta beta pinch one arm crimpy. Futuristic pinch, dyno dynamic drop knee climb. Climbing ondra slopey onsight beta ondra power endurance.',
// status: SessionStatus.missed,
// date: Value(DateTime.now())));
// database.into(database.sessions).insert(SessionsCompanion.insert(
// title: 'Off-Wall Training',
// content: 'Beta pully beta beta pinch one arm crimpy. Futuristic pinch, dyno dynamic drop knee climb. Climbing ondra slopey onsight beta ondra power endurance.',
// status: SessionStatus.completed,
// date: Value(DateTime.now())));
// database.into(database.activities).insert(ActivitiesCompanion.insert(
// title: "test activity",
// type: ActivityType.technical,
// description: "test training activity",
// category: ActivityCategories.fundamentals));
// database
// .into(database.sessionActivities)
// .insert(SessionActivitiesCompanion.insert(
// sessionId: 1,
// activityId: 1,
// results: Value("results json, will need to test"),
// achievements: Value("comma, seperated, items"),
// ));
// database.into(database.actions).insert(ActionsCompanion.insert(
// title: "test action title",
// description: "teste action description",
// set: "not sure how the json will work yet",
// ));
// database
// .into(database.activityActions)
// .insert(ActivityActionsCompanion.insert(
// activityId: 1,
// actionId: 1,
// ));
// database.into(database.mediaItems).insert(MediaItemsCompanion.insert(
// title: "test youtube media item",
// description: "this is a test youtube item",
// reference: "sZVAEy9UmoY",
// type: MediaType.youtube));
// database
// .into(database.objectMediaItems)
// .insert(ObjectMediaItemsCompanion.insert(objectId: 1, mediaId: 1));
runApp(
ChangeNotifierProvider(
create: (context) => ActivityTimerModel(),
child: const SendTrain(),
),
);
}

View File

@ -0,0 +1,161 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
import 'package:sendtrain/models/activity_model.dart';
class ActivityTimerModel with ChangeNotifier {
int _actionCounter = 0;
ActivityModel? _activity;
List _sets = [];
int _currentActionNum = 0;
int _currentSetNum = 0;
Timer? _periodicTimer;
double _progress = 0;
ItemScrollController? _isc;
int _totalTime = 0;
int get actionCount => _actionCounter;
int get currentActionNum => _currentActionNum;
dynamic get currentAction => currentSet[_currentActionNum];
int get currentSetNum => _currentSetNum;
dynamic get currentSet => _sets[_currentSetNum];
ActivityModel? get activity => _activity;
List get sets => _sets;
Timer? get periodicTimer => _periodicTimer;
bool get isActive => _isActive();
double get progress => _progress;
int get totalTime => _totalTime;
void setup(ActivityModel activity) {
if (_activity == null || activity.id != _activity?.id) {
_periodicTimer?.cancel();
_progress = 0;
_isc = null;
_activity = activity;
_sets = activity.actions[0].items();
_currentActionNum = 0;
_currentSetNum = 0;
setActionCount();
getTotalTime();
}
moveToIndex(_currentSetNum);
}
void getTotalTime() {
int time = 0;
for(int setIndex = 0; _sets.length > setIndex; setIndex++) {
for (int actionIndex = 0; _sets[setIndex].length > actionIndex; actionIndex++) {
var action = _sets[setIndex][actionIndex];
if (action['type'] == 'seconds') {
time = time + action['amount'] as int;
}
}
}
_totalTime = time;
}
void reset() {
_progress = 0;
_currentActionNum = 0;
_currentSetNum = 0;
_periodicTimer!.cancel();
setActionCount();
moveToIndex(0);
}
void setScrollController(ItemScrollController isc) {
_isc = isc;
}
bool isCurrentItem(int setNum, int actionNum) {
if (setNum == _currentSetNum && actionNum == _currentActionNum) {
return true;
}
return false;
}
int totalActions() {
int count = 0;
for (int i = 0; i < _sets.length; i++) {
count = count + _sets[i].length as int;
}
return count;
}
void setActionCount() {
_actionCounter = currentAction['amount'];
}
void pause() {
_periodicTimer!.cancel();
notifyListeners();
}
void start() {
_periodicTimer = Timer.periodic(const Duration(seconds: 1), (Timer timer) {
switch (currentAction['type']) {
// we don't want to count down
// if its repititions
case 'repititions':
break;
case 'seconds':
if (_actionCounter > 0) {
_actionCounter--;
_totalTime--;
} else {
nextAction(_currentActionNum + 1);
setActionCount();
}
updateProgress();
}
notifyListeners();
});
}
void updateProgress() {
_progress = (currentAction['actionID'] +
(1.0 - _actionCounter / currentAction['amount'])) /
totalActions();
notifyListeners();
}
void setAction(int setNum, int actionNum, String type) {
_currentActionNum = actionNum;
_currentSetNum = setNum;
notifyListeners();
moveToIndex(_currentSetNum);
}
void nextAction(int nextActionIndex) {
if (currentSet.length > nextActionIndex) {
setAction(_currentSetNum, nextActionIndex, 'automatic');
} else if (_sets.length > _currentSetNum + 1) {
// if the item isn't in the set
// increment the set and reset action index
setAction(_currentSetNum + 1, 0, 'automatic');
} else {
// if we're done all the sets
// cancel timer and reset activity
reset();
}
}
void moveToIndex(int index) {
if (_isc != null && _isc!.isAttached) {
_isc?.scrollTo(
index: index,
duration: Duration(milliseconds: 500),
curve: Curves.easeInOutCubic);
}
}
bool _isActive() {
return (_periodicTimer != null && _periodicTimer!.isActive) ? true : false;
}
}

View File

@ -29,13 +29,13 @@ class _ActivitiesScreenState extends State<ActivitiesScreen> {
activityActionSet: Set(
type: 'drop_set',
total: 3,
rest: 3000,
rest: 300000,
reps: Reps(
type: 'count',
tempo: [2, 3, 5],
amounts: [5, 3, 2],
weights: [50, 70, 80],
rest: 200))),
rest: 20000))),
));
@override

View File

@ -1,15 +1,34 @@
import 'package:drift/drift.dart' hide Column;
import 'package:flutter/material.dart';
import 'package:sendtrain/database.dart';
import '../widgets/session_card.dart';
class SessionsScreen extends StatelessWidget {
const SessionsScreen({super.key});
final AppDatabase database = AppDatabase();
SessionsScreen({super.key});
Future<List<Session>> getSessions() async {
// database.managers.sessions.filter((session) => session.status(SessionStatus.pending));
return await database.managers.sessions.get();
}
@override
Widget build(BuildContext context) {
List<Widget> previousSessions =
List.generate(10, (i) => const SessionCard(state: 1, type: 1));
Widget upcomingSession = const SessionCard(state: 2);
Widget currentSession = const SessionCard();
return FutureBuilder<List<Session>>(
future: getSessions(),
builder: (context, snapshot) {
if (snapshot.hasData) {
final sessions = snapshot.data!;
final pending = sessions.where((session) => session.status == SessionStatus.completed || session.status == SessionStatus.missed);
final upcoming = sessions.firstWhere((session) => session.status == SessionStatus.pending);
final current = sessions.firstWhere((session) => session.status == SessionStatus.started);
List<Widget> previousSessions = List.generate(pending.length, (i) => SessionCard(type: 1, session: pending.elementAt(i)));
Widget upcomingSession =
SessionCard(session: upcoming);
Widget currentSession = SessionCard(session: current);
database.close();
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
@ -41,12 +60,54 @@ class SessionsScreen extends StatelessWidget {
mainAxisSpacing: 5,
crossAxisCount: 1,
children: previousSessions))
// Flexible(
// child: ListView(
// scrollDirection: Axis.vertical,
// children: previousSessions,
// )),
],
);
} else {
return const CircularProgressIndicator();
}
});
// List<Widget> previousSessions = List.generate(
// 10, (i) => SessionCard(state: 1, type: 1, session: _sessions.first));
// Widget upcomingSession = SessionCard(state: 2, session: _sessions.first);
// Widget currentSession = SessionCard(session: _sessions.first);
// return Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: <Widget>[
// const Padding(
// padding: EdgeInsets.fromLTRB(15, 5, 0, 0),
// child: Text(
// style: TextStyle(fontSize: 25, fontWeight: FontWeight.bold),
// 'Current:')),
// currentSession,
// const Padding(
// padding: EdgeInsets.fromLTRB(15, 30, 0, 0),
// child: Text(
// style: TextStyle(fontSize: 25, fontWeight: FontWeight.bold),
// 'Upcoming:')),
// upcomingSession,
// const Padding(
// padding: EdgeInsets.fromLTRB(15, 30, 0, 0),
// child: Text(
// style: TextStyle(fontSize: 25, fontWeight: FontWeight.bold),
// 'Previous:')),
// SizedBox(
// width: double.infinity,
// height: 160,
// child: GridView.count(
// padding: const EdgeInsets.fromLTRB(15, 10, 0, 0),
// scrollDirection: Axis.horizontal,
// crossAxisSpacing: 5,
// mainAxisSpacing: 5,
// crossAxisCount: 1,
// children: previousSessions))
// // Flexible(
// // child: ListView(
// // scrollDirection: Axis.vertical,
// // children: previousSessions,
// // )),
// ],
// );
}
}

View File

@ -5,10 +5,10 @@ class ActivitiesHeader extends StatefulWidget {
const ActivitiesHeader({super.key});
@override
_ActivitiesHeaderState createState() => _ActivitiesHeaderState();
State<ActivitiesHeader> createState() => ActivitiesHeaderState();
}
class _ActivitiesHeaderState extends State<ActivitiesHeader> {
class ActivitiesHeaderState extends State<ActivitiesHeader> {
@override
void initState() {
super.initState();

View File

@ -1,58 +1,107 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
import 'package:sendtrain/classes/activity_action.dart';
import 'package:sendtrain/widgets/action_card.dart';
import 'package:sendtrain/models/activity_timer_model.dart';
class ActivityActionView extends StatefulWidget {
ActivityActionView({super.key, required this.action});
ActivityAction action;
const ActivityActionView({super.key, required this.action});
final ActivityAction action;
@override
State<ActivityActionView> createState() => _ActivityActionViewState();
State<ActivityActionView> createState() => ActivityActionViewState();
}
class _ActivityActionViewState extends State<ActivityActionView> {
class ActivityActionViewState extends State<ActivityActionView> {
final ItemScrollController itemScrollController = ItemScrollController();
final ScrollOffsetController scrollOffsetController =
ScrollOffsetController();
final ItemPositionsListener itemPositionsListener =
ItemPositionsListener.create();
final ScrollOffsetListener scrollOffsetListener =
ScrollOffsetListener.create();
@override
Widget build(BuildContext context) {
ActivityTimerModel atm =
Provider.of<ActivityTimerModel>(context, listen: true);
List<List<Map<String, dynamic>>> sets = atm.activity!.actions[0].items();
// we need to set the scroll controller
// so we can update the selected item position
atm.setScrollController(itemScrollController);
return Expanded(
child: ListView.builder(
padding: const EdgeInsets.fromLTRB(10, 0, 10, 0),
child: ScrollablePositionedList.builder(
padding: const EdgeInsets.fromLTRB(10, 0, 10, 20),
itemCount: widget.action.activityActionSet.total,
itemBuilder: (BuildContext context, int index) {
String title = widget.action.title;
Set actionSet = widget.action.activityActionSet;
Reps setReps = widget.action.activityActionSet.reps;
List<Card> contents = [];
itemScrollController: itemScrollController,
scrollOffsetController: scrollOffsetController,
itemPositionsListener: itemPositionsListener,
scrollOffsetListener: scrollOffsetListener,
itemBuilder: (BuildContext context, int setNum) {
List<GestureDetector> content = [];
List<Map<String, dynamic>> set = sets[setNum];
// contents.add(Card(child: Text('Set ${index + 1}')));
for (int actionNum = 0; actionNum < set.length; actionNum++) {
Map<String, dynamic> setItem = set[actionNum];
for (int repCount = 0; repCount < setReps.amounts[index]; repCount++) {
contents.add(Card(
child: Padding(
padding: const EdgeInsets.fromLTRB(15, 15, 15, 15),
child: Column(
children: [
Row(children: [Text('Exercise: ${widget.action.title}')]),
Row(children: [Text('Type: ${actionSet.type}')]),
Row(children: [
Text(
'Set: ${index + 1} / ${widget.action.activityActionSet.total}')
]),
Row(children: [
Text('Rep: ${repCount + 1} / ${setReps.amounts[index]}')
]),
Row(children: [Text('Tempo: ${setReps.tempo[index]}')]),
Row(children: [
Text('Weight: ${setReps.weights[index]}')
]),
Row(children: [Text('Rest: ${setReps.rest}')])
],
))));
content.add(GestureDetector(
onTap: () {
atm.setAction(setNum, actionNum, 'manual');
atm.setActionCount();
itemScrollController.scrollTo(
index: setNum,
duration: Duration(milliseconds: 500),
curve: Curves.easeInOutCubic);
},
child: Row(children: [
Ink(
width: 70,
padding: const EdgeInsets.all(15),
color: atm.isCurrentItem(setNum, actionNum)
? Theme.of(context).colorScheme.primaryContainer
: Theme.of(context).colorScheme.onPrimary,
child: Text(
textAlign: TextAlign.center,
'${setNum + 1}.${actionNum + 1} ')),
Expanded(
child: Ink(
padding: const EdgeInsets.all(15),
color: atm.isCurrentItem(setNum, actionNum)
? Theme.of(context).colorScheme.surfaceBright
: Theme.of(context).colorScheme.surfaceContainerLow,
child: Text(
textAlign: TextAlign.center,
'${setItem['name']}: ${setItem['amount']} ${setItem['type']}')))
])));
}
return Column(children: contents);
if (setNum == 0) {
return Card(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(0),
topRight: Radius.circular(0),
bottomLeft: Radius.circular(10),
bottomRight: Radius.circular(10)),
),
clipBehavior: Clip.antiAlias,
child: Column(children: content));
} else {
return Card(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(10),
topRight: Radius.circular(10),
bottomLeft: Radius.circular(10),
bottomRight: Radius.circular(10)),
),
clipBehavior: Clip.antiAlias,
child: Column(children: content));
}
// return Column(children: contents);
},
));
}

View File

@ -1,21 +1,39 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:sendtrain/classes/media.dart';
import 'package:sendtrain/models/activity_model.dart';
import 'package:sendtrain/models/activity_timer_model.dart';
import 'package:sendtrain/widgets/activity_view.dart';
import 'package:sendtrain/widgets/media_card.dart';
class ActivityCard extends StatelessWidget {
class ActivityCard extends StatefulWidget {
final ActivityModel activity;
const ActivityCard({super.key, required this.activity});
final ActivityModel activity;
@override
State<ActivityCard> createState() => ActivityCardState();
}
class ActivityCardState extends State<ActivityCard> {
String formattedTime(int timeInSecond) {
int sec = timeInSecond % 60;
int min = (timeInSecond / 60).floor();
String minute = min.toString().length <= 1 ? "0$min" : "$min";
String second = sec.toString().length <= 1 ? "0$sec" : "$sec";
return "$minute:$second";
}
@override
Widget build(BuildContext context) {
final ActivityTimerModel atm =
Provider.of<ActivityTimerModel>(context, listen: false);
return Card(
color: const Color(0xff3A5FB6),
color: atm.activity?.id == widget.activity.id
? Theme.of(context).colorScheme.primaryContainer
: Theme.of(context).colorScheme.surfaceContainerLow,
clipBehavior: Clip.hardEdge,
child: InkWell(
splashColor: Colors.deepPurple,
onTap: () => showGeneralDialog(
barrierColor: Colors.black.withOpacity(0.5),
transitionDuration: const Duration(milliseconds: 220),
@ -30,7 +48,7 @@ class ActivityCard extends StatelessWidget {
return SlideTransition(
position: custom,
child: Dialog.fullscreen(
child: ActivityView(activity: activity)));
child: ActivityView(activity: widget.activity)));
},
barrierDismissible: true,
barrierLabel: '',
@ -50,24 +68,56 @@ class ActivityCard extends StatelessWidget {
image: DecorationImage(
fit: BoxFit.cover,
image: findMediaByType(
activity.actions[0].media, 'image')),
widget.activity.actions[0].media, 'image')),
// color: Colors.blue,
borderRadius:
const BorderRadius.all(Radius.elliptical(10, 10)),
),
)),
title: Text(maxLines: 1, activity.title),
subtitle: Text(maxLines: 2, activity.description),
title: Consumer<ActivityTimerModel>(
builder: (context, atm, child) {
if (atm.activity?.id == widget.activity.id) {
return Text(
maxLines: 1,
"${widget.activity.title} (${formattedTime(atm.totalTime)})");
} else {
return Text(maxLines: 1, widget.activity.title);
}
},
),
subtitle: Text(maxLines: 2, widget.activity.description),
trailing: IconButton(
visualDensity: VisualDensity.compact,
icon: Icon(Icons.close_rounded),
onPressed: () {
showAdaptiveDialog(
context: context,
builder: (BuildContext context) => AlertDialog(
title: const Text('Activity Removal'),
content: const Text('Would you like to permanently remove this activity from the current session?'),
actions: <Widget>[
TextButton(
onPressed: () => Navigator.pop(context, 'Cancel'),
child: const Text('Cancel'),
),
TextButton(
onPressed: () => Navigator.pop(context, 'OK'),
child: const Text('OK'),
),
],
),
);
},
)),
],
)),
);
}
ImageProvider findMediaByType(List<Media>? media, String type) {
var found = media!.where((m) => m.type == 'image');
var found = media?.where((m) => m.type == 'image');
if (found.isNotEmpty) {
if (found != null) {
return NetworkImage(found.first.reference);
} else {
// Element is not found

View File

@ -1,58 +1,73 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_expandable_fab/flutter_expandable_fab.dart';
import 'package:provider/provider.dart';
import 'package:sendtrain/classes/activity_action.dart';
import 'package:sendtrain/classes/media.dart';
import 'package:sendtrain/models/activity_model.dart';
import 'package:sendtrain/models/activity_timer_model.dart';
import 'package:sendtrain/widgets/activity_action_view.dart';
import 'package:sendtrain/widgets/media_card.dart';
class ActivityView extends StatefulWidget {
ActivityView({super.key, required this.activity});
ActivityModel activity;
const ActivityView({super.key, required this.activity});
final ActivityModel activity;
@override
State<ActivityView> createState() => _ActivityViewState();
}
class _ActivityViewState extends State<ActivityView> {
Timer? _periodicTimer;
int _tickCount = 0;
int currentSet = 0;
int currentRep = 0;
void _startPeriodicTimer() {
const oneSecond = Duration(seconds: 1);
_periodicTimer = Timer.periodic(oneSecond, (Timer timer) {
setState(() {
_tickCount++;
});
});
}
@override
Widget build(BuildContext context) {
ActivityModel activity = widget.activity;
ActivityTimerModel atm =
Provider.of<ActivityTimerModel>(context, listen: false);
var content = [
atm.setup(activity);
return Scaffold(
floatingActionButtonLocation: ExpandableFab.location,
floatingActionButton: ExpandableFab(
distance: 70,
type: ExpandableFabType.up,
overlayStyle: ExpandableFabOverlayStyle(
color: Colors.black.withOpacity(0.5),
blur: 10,
),
children: [
FloatingActionButton.extended(
icon: const Icon(Icons.history_outlined),
label: Text('Restart'),
onPressed: () {},
),
FloatingActionButton.extended(
icon: const Icon(Icons.done_all_outlined),
label: Text('Done'),
onPressed: () {},
),
FloatingActionButton.extended(
icon: const Icon(Icons.edit_outlined),
label: Text('Edit'),
onPressed: () {},
),
]),
body: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
AppBar(
// surfaceTintColor: ThemeData.dark(useMaterial3: true).colorScheme.primary,
// backgroundColor: ThemeData.dark(useMaterial3: true).colorScheme.primaryContainer,
centerTitle: true,
title: const Text('Activity', style: TextStyle(fontSize: 15)),
),
Padding(
padding:
const EdgeInsets.only(left: 15, right: 20, top: 15, bottom: 10),
padding: const EdgeInsets.only(
left: 15, right: 20, top: 15, bottom: 10),
child: Text(
maxLines: 1,
style: const TextStyle(fontSize: 25, fontWeight: FontWeight.bold),
style: const TextStyle(
fontSize: 25, fontWeight: FontWeight.bold),
activity.title)),
ActivityViewCategories(categories: activity.categories),
Padding(
padding:
const EdgeInsets.only(top: 0, bottom: 20, left: 15, right: 15),
padding: const EdgeInsets.only(
top: 0, bottom: 20, left: 15, right: 15),
child: Text(
textAlign: TextAlign.left,
style: const TextStyle(fontSize: 15),
@ -63,48 +78,69 @@ class _ActivityViewState extends State<ActivityView> {
child: Text(
textAlign: TextAlign.left,
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
'Actions:'))
];
for (var action in activity.actions) {
content.add(ActivityActionView(action: action));
}
// add bottom bar to manage activity
content.add(Container(
height: MediaQuery.sizeOf(context).height * .07,
color: ThemeData.dark(useMaterial3: true).colorScheme.primaryContainer,
child: Row(children: [
Expanded(
flex: 1,
child: Flex(direction: Axis.horizontal, children: [
IconButton(
iconSize: 30,
icon: const Icon(Icons.play_arrow_rounded),
onPressed: _startPeriodicTimer),
IconButton(
iconSize: 30,
icon: const Icon(Icons.skip_next_rounded),
onPressed: () {})
])),
Expanded(
flex: 1,
child: Text(
style: const TextStyle(fontSize: 25),
textAlign: TextAlign.center,
'$_tickCount'),
'Actions')),
Padding(
padding: const EdgeInsets.only(left: 10, right: 10),
child: Card(
clipBehavior: Clip.antiAlias,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(10),
topRight: Radius.circular(10)),
),
const Expanded(
color: Theme.of(context).colorScheme.onPrimary,
child: Row(children: [
Ink(
width: 70,
color: Theme.of(context).colorScheme.primaryContainer,
child: Consumer<ActivityTimerModel>(
builder: (context, atm, child) {
return IconButton(
alignment: AlignmentDirectional.center,
icon: atm.isActive
? const Icon(Icons.pause_rounded)
: const Icon(Icons.play_arrow_rounded),
onPressed: () =>
{atm.isActive ? atm.pause() : atm.start()});
},
)),
Expanded(
flex: 1,
child: Padding(
padding: EdgeInsets.only(right: 10),
child: Text(
style: TextStyle(fontSize: 15),
child: Stack(alignment: Alignment.center, children: [
Container(
alignment: Alignment.center,
child: Consumer<ActivityTimerModel>(
builder: (context, atm, child) {
return Text(
style: const TextStyle(fontSize: 20),
textAlign: TextAlign.center,
'${atm.actionCount} ${atm.currentAction['type']}');
},
),
),
Container(
alignment: Alignment.centerRight,
padding: EdgeInsets.only(right: 15),
child: Consumer<ActivityTimerModel>(
builder: (context, atm, child) {
return Text(
style: const TextStyle(fontSize: 12),
textAlign: TextAlign.right,
'Set: 1/3 \nRep: 1/5'))),
])));
return Column(
crossAxisAlignment: CrossAxisAlignment.start, children: content);
'${atm.currentAction['actionID'] + 1} of ${atm.totalActions()}');
})),
])),
]))),
Padding(
padding: EdgeInsets.only(left: 14, right: 14),
child:
Consumer<ActivityTimerModel>(builder: (context, atm, child) {
return LinearProgressIndicator(
value: atm.progress,
semanticsLabel: 'Activity Progress',
);
})),
ActivityActionView(action: activity.actions[0]),
]));
}
}

View File

@ -3,23 +3,24 @@ import 'package:intl/intl.dart';
import 'package:intl/date_symbol_data_local.dart';
import 'package:sendtrain/classes/activity_action.dart';
import 'package:sendtrain/classes/media.dart';
import 'package:sendtrain/database.dart' hide ActivityAction;
import 'package:sendtrain/models/activity_model.dart';
import 'package:sendtrain/models/session_model.dart';
import 'package:sendtrain/widgets/session_view.dart';
class SessionCard extends StatelessWidget {
final int state;
final int type;
const SessionCard({super.key, this.state = 0, this.type = 0});
final Session session;
const SessionCard({super.key, this.type = 0, required this.session});
@override
Widget build(BuildContext context) {
initializeDateFormatting('en');
final DateFormat dateFormat = DateFormat('yyyy-MM-dd');
Color color = (state == 0)
? const Color(0xff3A5FB6)
: ThemeData.dark(useMaterial3: true).colorScheme.surfaceBright;
Color color = (session.status == SessionStatus.started)
? Theme.of(context).colorScheme.primaryContainer
: Theme.of(context).colorScheme.surfaceContainerLow;
// place holder until we can retrieve real data
final data = SessionModel(
@ -39,8 +40,9 @@ class SessionCard extends StatelessWidget {
actions: [
ActivityAction(
id: 1,
title: 'test action',
description: 'test description',
title: '1, 3, 5',
description:
'Move between the first, third, and fifth rungs, alternating hands. Rest and alternate sides, to start',
media: [
Media(
id: 1,
@ -57,27 +59,28 @@ class SessionCard extends StatelessWidget {
activityActionSet: Set(
type: 'drop_set',
total: 3,
rest: 3000,
rest: 300000,
reps: Reps(
type: 'count',
tempo: [2, 3, 5],
amounts: [5, 3, 2],
weights: [50, 70, 80],
rest: 200))),
type: 'repititions',
tempo: [0],
amounts: [1, 1, 1],
weights: [0],
rest: 20000))),
],
resources: ['https://www.youtube.com/watch?v=bLz0xp1PEm4']),
ActivityModel(
id: 1,
id: 2,
title: 'Projecting',
type: 'fundamental',
categories: ['technique', 'conditioning'],
description:
"Session focussed on attempting a climb at or beyond your perceived limit.",
"Session focused on attempting a climb at or beyond your perceived limit.",
actions: [
ActivityAction(
id: 1,
title: 'test action',
description: 'test description',
title: 'Attempt Climb',
description:
'Attempt your selected climb, if you fall off early in the climb, attempt again. 1 repitition equals roughly doing all the moves, not necessarily in 1 attempt.',
media: [
Media(
id: 1,
@ -92,52 +95,54 @@ class SessionCard extends StatelessWidget {
description: 'How to project climbs')
],
activityActionSet: Set(
type: 'drop_set',
total: 3,
rest: 3000,
type: 'standard',
total: 10,
rest: 300000,
reps: Reps(
type: 'count',
tempo: [2, 3, 5],
amounts: [5, 3, 2],
weights: [50, 70, 80],
rest: 200))),
type: 'repititions',
tempo: [0],
amounts: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
weights: [0],
rest: 0))),
],
resources: ['https://www.youtube.com/watch?v=dyAvbUvY_PU']),
ActivityModel(
id: 1,
title: 'Weighted Pull Ups',
type: 'fundamental',
id: 3,
title: 'Long Block Pulls',
type: 'Hypertrophy',
categories: ['Strength', 'Power'],
description:
"Weight pullups to increase strength and maximal pulling force.",
"Block pull on a edge of a specific size and time to induce a hypertrophic effect on the formarms.",
actions: [
ActivityAction(
id: 1,
title: 'test action',
description: 'test description',
title: 'Long Pulls',
description:
'Select your desired weight to pull, add it to the block. You should aim for an effort level of 8-9 when reaching the end of the set time, going to failure will result in significantly extended recovery time.',
media: [
Media(
id: 1,
reference:
'https://trainingforclimbing.com/wp-content/uploads/2016/03/hypergravity_pull-up-compress3-966x1024.jpg',
'https://trailandcrag.com/sites/default/files/inline-images/05-min_3.jpg',
type: 'image',
description: 'Weighted Pullups'),
description: 'Block pull example'),
Media(
id: 1,
reference: '7TLG1mHQHgw',
reference: 'sZVAEy9UmoY',
type: 'youtube',
description: 'How to do weighted pullups')
description:
'Principals of Grip gains, and related protocols')
],
activityActionSet: Set(
type: 'drop_set',
total: 3,
rest: 3000,
type: 'alternating',
total: 5,
rest: 5000,
reps: Reps(
type: 'count',
tempo: [2, 3, 5],
amounts: [5, 3, 2],
weights: [50, 70, 80],
rest: 200))),
type: 'seconds',
tempo: [0],
amounts: [5, 5, 5, 5, 5],
weights: [80, 80, 80, 80, 80],
rest: 5000))),
],
resources: ['https://www.youtube.com/watch?v=dyAvbUvY_PU']),
],
@ -186,7 +191,7 @@ class SessionCard extends StatelessWidget {
.animate(animation);
return SlideTransition(
position: custom,
child: Dialog.fullscreen(child: SessionView(data: data)));
child: Dialog.fullscreen(child: SessionView(data: data, session: session)));
},
barrierDismissible: true,
barrierLabel: '',
@ -212,8 +217,32 @@ class SessionCard extends StatelessWidget {
BorderRadius.all(Radius.elliptical(10, 10)),
),
)),
title: Text(maxLines: 1, data.title),
subtitle: Text(maxLines: 1, dateFormat.format(data.date)),
title: Text(maxLines: 1, session.title),
subtitle: Text(maxLines: 1, dateFormat.format(session.date as DateTime)),
trailing: IconButton(
visualDensity: VisualDensity.compact,
icon: Icon(Icons.close_rounded),
onPressed: () {
showAdaptiveDialog(
context: context,
builder: (BuildContext context) => AlertDialog(
title: const Text('Session Removal'),
content: const Text(
'Would you like to permanently remove this session?'),
actions: <Widget>[
TextButton(
onPressed: () => Navigator.pop(context, 'Cancel'),
child: const Text('Cancel'),
),
TextButton(
onPressed: () => Navigator.pop(context, 'OK'),
child: const Text('OK'),
),
],
),
);
},
),
),
ListTile(
contentPadding: const EdgeInsets.fromLTRB(15, 0, 15, 15),
@ -221,14 +250,14 @@ class SessionCard extends StatelessWidget {
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontWeight: FontWeight.w300),
data.content),
session.content),
),
],
)),
);
} else {
return Card(
color: const Color.fromARGB(125, 0, 0, 0),
color: color,
child: InkWell(
// overlayColor: MaterialStateColor(Colors.deepPurple as int),
splashColor: Colors.deepPurple,
@ -247,7 +276,7 @@ class SessionCard extends StatelessWidget {
return SlideTransition(
position: custom,
child:
Dialog.fullscreen(child: SessionView(data: data)));
Dialog.fullscreen(child: SessionView(data: data, session: session)));
},
barrierDismissible: true,
barrierLabel: '',
@ -274,11 +303,11 @@ class SessionCard extends StatelessWidget {
ListTile(
title: Text(
maxLines: 3,
data.title,
session.title,
textAlign: TextAlign.center),
subtitle: Text(
maxLines: 1,
dateFormat.format(data.date),
dateFormat.format(session.date as DateTime),
textAlign: TextAlign.center),
),
])))));

View File

@ -1,45 +1,73 @@
import 'package:flutter/material.dart';
import 'package:flutter_expandable_fab/flutter_expandable_fab.dart';
import 'package:intl/intl.dart';
import 'package:intl/date_symbol_data_local.dart';
import 'package:sendtrain/classes/media.dart';
import 'package:sendtrain/database.dart';
import 'package:sendtrain/models/activity_model.dart';
import 'package:sendtrain/models/session_model.dart';
import 'package:sendtrain/widgets/activity_card.dart';
import 'package:sendtrain/widgets/media_card.dart';
class SessionView extends StatelessWidget {
const SessionView({super.key, required this.data});
const SessionView({super.key, required this.data, required this.session});
final SessionModel data;
final Session session;
@override
Widget build(BuildContext context) {
initializeDateFormatting('en');
final DateFormat dateFormat = DateFormat('yyyy-MM-dd');
return Column(
return Scaffold(
floatingActionButtonLocation: ExpandableFab.location,
floatingActionButton: ExpandableFab(
distance: 70,
type: ExpandableFabType.up,
overlayStyle: ExpandableFabOverlayStyle(
color: Colors.black.withOpacity(0.5),
blur: 10,
),
children: [
FloatingActionButton.extended(
icon: const Icon(Icons.history_outlined),
label: Text('Restart'),
onPressed: () {},
),
FloatingActionButton.extended(
icon: const Icon(Icons.done_all_outlined),
label: Text('Done'),
onPressed: () {},
),
FloatingActionButton.extended(
icon: const Icon(Icons.edit_outlined),
label: Text('Edit'),
onPressed: () {},
),
]),
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
AppBar(
centerTitle: true,
title: Text('Session @ ${dateFormat.format(data.date)}',
title: Text('Session @ ${dateFormat.format(session.date as DateTime)}',
style: const TextStyle(fontSize: 15)),
),
Padding(
padding:
const EdgeInsets.only(left: 15, right: 20, top: 15, bottom: 10),
padding: const EdgeInsets.only(
left: 15, right: 20, top: 15, bottom: 10),
child: Text(
maxLines: 1,
style:
const TextStyle(fontSize: 25, fontWeight: FontWeight.bold),
data.title)),
style: const TextStyle(
fontSize: 25, fontWeight: FontWeight.bold),
session.title)),
SessionViewAchievements(achievements: data.achievements),
Padding(
padding: const EdgeInsets.only(left: 15, right: 15),
child: Text(
style: const TextStyle(fontSize: 15),
data.content)),
child:
Text(style: const TextStyle(fontSize: 15), session.content)),
const Padding(
padding: EdgeInsets.fromLTRB(15, 30, 0, 10),
child: Text(
@ -52,14 +80,8 @@ class SessionView extends StatelessWidget {
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
'Activites:')),
SessionViewActivities(activities: data.activities),
// TextButton(
// onPressed: () {
// Navigator.pop(context);
// },
// child: const Text('Close'),
// ),
],
);
));
}
}

View File

@ -1,11 +0,0 @@
//
// Generated file. Do not edit.
//
// clang-format off
#include "generated_plugin_registrant.h"
void fl_register_plugins(FlPluginRegistry* registry) {
}

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_

View File

@ -1,23 +0,0 @@
#
# Generated file, do not edit.
#
list(APPEND FLUTTER_PLUGIN_LIST
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST
)
set(PLUGIN_BUNDLED_LIBRARIES)
foreach(plugin ${FLUTTER_PLUGIN_LIST})
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin})
target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
endforeach(plugin)
foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin})
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
endforeach(ffi_plugin)

View File

@ -1,10 +0,0 @@
//
// Generated file. Do not edit.
//
import FlutterMacOS
import Foundation
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
}

View File

@ -0,0 +1,12 @@
import Cocoa
import FlutterMacOS
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

@ -19,7 +19,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
version: 0.2.1
environment:
sdk: '>=2.19.2 <3.0.0'
sdk: '>=3.0.0 <4.0.0'
# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions
@ -36,9 +36,15 @@ dependencies:
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2
scaler: ^1.1.2+1
intl: ^0.18.0
youtube_player_flutter: ^8.1.2
intl: ^0.20.1
youtube_player_flutter: ^9.1.1
json_annotation: ^4.9.0
provider: ^6.1.2
scrollable_positioned_list: ^0.3.8
drift: ^2.22.1
flutter_expandable_fab: ^2.3.0
drift_flutter: ^0.2.2
flutter_local_notifications: ^18.0.1
flutter_launcher_name:
name: "SendTrain"
@ -52,9 +58,10 @@ dev_dependencies:
# activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint
# rules and activating additional ones.
flutter_lints: ^2.0.0
flutter_lints: ^5.0.0
build_runner: ^2.4.13
json_serializable: ^6.9.0
drift_dev: ^2.22.1
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec

View File

@ -1,11 +0,0 @@
//
// Generated file. Do not edit.
//
// clang-format off
#include "generated_plugin_registrant.h"
void RegisterPlugins(flutter::PluginRegistry* registry) {
}

View File

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

View File

@ -1,23 +0,0 @@
#
# Generated file, do not edit.
#
list(APPEND FLUTTER_PLUGIN_LIST
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST
)
set(PLUGIN_BUNDLED_LIBRARIES)
foreach(plugin ${FLUTTER_PLUGIN_LIST})
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin})
target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
endforeach(plugin)
foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin})
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
endforeach(ffi_plugin)