action-things #7
BIN
assets/audio/count_finish.mp3
Normal file
BIN
assets/audio/count_finish.mp3
Normal file
Binary file not shown.
BIN
assets/audio/count_tone.wav
Normal file
BIN
assets/audio/count_tone.wav
Normal file
Binary file not shown.
@ -15,7 +15,7 @@ class ActionsDao extends DatabaseAccessor<AppDatabase> with _$ActionsDaoMixin {
|
||||
return await (select(actions)..where((action) => action.id.equals(id) )).getSingle();
|
||||
}
|
||||
|
||||
Future<List<Action>> fromActivity(Activity activity) async {
|
||||
Future<List<Action>> fromActivity(Activity activity, Session session) async {
|
||||
final result = select(db.activityActions).join(
|
||||
[
|
||||
innerJoin(
|
||||
@ -24,7 +24,8 @@ class ActionsDao extends DatabaseAccessor<AppDatabase> with _$ActionsDaoMixin {
|
||||
),
|
||||
],
|
||||
)
|
||||
..where(db.activityActions.activityId.equals(activity.id));
|
||||
..where(db.activityActions.activityId.equals(activity.id))
|
||||
..where(db.activityActions.sessionId.equals(session.id));
|
||||
|
||||
final actions = (await result.get())
|
||||
.map((e) => e.readTable(db.actions))
|
||||
@ -32,4 +33,32 @@ class ActionsDao extends DatabaseAccessor<AppDatabase> with _$ActionsDaoMixin {
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
Stream<List<Action>> watchActivityActions(Activity activity, Session session) {
|
||||
final result = select(db.activityActions).join(
|
||||
[
|
||||
innerJoin(
|
||||
db.actions,
|
||||
db.actions.id.equalsExp(db.activityActions.actionId),
|
||||
),
|
||||
],
|
||||
)
|
||||
..where(db.activityActions.activityId.equals(activity.id))
|
||||
..where(db.activityActions.sessionId.equals(session.id));
|
||||
|
||||
// final actions = result.watch().map((rows) {
|
||||
// return rows.map((row) {
|
||||
// row.readTable(db.actions);
|
||||
// }).toList();
|
||||
// });
|
||||
|
||||
final actions = (result.watch()).map((rows) {
|
||||
return rows.map((row) => row.readTable(db.actions)).toList();
|
||||
});
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
Future createOrUpdate(ActionsCompanion action) => into(actions).insertOnConflictUpdate(action);
|
||||
Future replace(Action action) => update(actions).replace(action);
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ class ActivityActionsDao extends DatabaseAccessor<AppDatabase> with _$ActivityAc
|
||||
Future insert(ActivityAction activityAction) => into(activityActions).insert(activityAction);
|
||||
Future replace(ActivityAction activityAction) => update(activityActions).replace(activityAction);
|
||||
Future remove(ActivityAction activityAction) => delete(activityActions).delete(activityAction);
|
||||
Future createOrUpdate(ActivityActionsCompanion activityAction) => into(activityActions).insertOnConflictUpdate(activityAction);
|
||||
|
||||
// Future<List<ActivityAction>> all() async {
|
||||
// return await select(activityActions).get();
|
||||
|
@ -6,5 +6,6 @@ part of 'activity_actions_dao.dart';
|
||||
mixin _$ActivityActionsDaoMixin on DatabaseAccessor<AppDatabase> {
|
||||
$ActivitiesTable get activities => attachedDatabase.activities;
|
||||
$ActionsTable get actions => attachedDatabase.actions;
|
||||
$SessionsTable get sessions => attachedDatabase.sessions;
|
||||
$ActivityActionsTable get activityActions => attachedDatabase.activityActions;
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ class AppDatabase extends _$AppDatabase {
|
||||
AppDatabase() : super(_openConnection());
|
||||
|
||||
@override
|
||||
int get schemaVersion => 20;
|
||||
int get schemaVersion => 35;
|
||||
|
||||
@override
|
||||
MigrationStrategy get migration {
|
||||
@ -155,19 +155,39 @@ class Activities extends Table {
|
||||
|
||||
class ActivityActions extends Table {
|
||||
IntColumn get id => integer().autoIncrement()();
|
||||
IntColumn get activityId =>
|
||||
integer().references(Activities, #id, onDelete: KeyAction.cascade)();
|
||||
IntColumn get activityId => integer().references(Activities, #id, onDelete: KeyAction.cascade)();
|
||||
IntColumn get actionId =>
|
||||
integer().references(Actions, #id, onDelete: KeyAction.cascade)();
|
||||
IntColumn get sessionId => integer().references(Sessions, #id, onDelete: KeyAction.cascade)();
|
||||
IntColumn get position => integer()();
|
||||
DateTimeColumn get createdAt =>
|
||||
dateTime().withDefault(Variable(DateTime.now()))();
|
||||
}
|
||||
|
||||
enum RepType { time, count }
|
||||
|
||||
enum ActionStatus { pending, started, paused, complete }
|
||||
|
||||
class Actions extends Table {
|
||||
IntColumn get id => integer().autoIncrement()();
|
||||
TextColumn get title => text().withLength(min: 3, max: 32)();
|
||||
TextColumn get title => text().withLength(min: 3, max: 64)();
|
||||
TextColumn get description => text().named('body')();
|
||||
IntColumn get totalSets => integer()();
|
||||
TextColumn get totalReps => text().withLength(min: 1, max: 32)();
|
||||
IntColumn get restBeforeSets => integer().nullable()();
|
||||
IntColumn get restBetweenSets => integer().nullable()();
|
||||
IntColumn get restBetweenReps => integer().nullable()();
|
||||
IntColumn get restAfterSets => integer().nullable()();
|
||||
TextColumn get repType => textEnum<RepType>()();
|
||||
IntColumn get repLength => integer().nullable()();
|
||||
TextColumn get repWeights => text().nullable()();
|
||||
TextColumn get setWeights => text().nullable()();
|
||||
BoolColumn get isAlternating => boolean().withDefault(Variable(false))();
|
||||
TextColumn get tempo => text().withLength(min: 6, max: 36).nullable()();
|
||||
TextColumn get status =>
|
||||
textEnum<ActionStatus>().withDefault(Variable('pending'))();
|
||||
TextColumn get state => text().withDefault(Variable(
|
||||
"{\"currentSet\": 0, \"currentRep\": 0, \"currentActionType\": 0, \"currentTime\": 0, \"currentAction\": 0}"))();
|
||||
TextColumn get set => text()();
|
||||
DateTimeColumn get createdAt =>
|
||||
dateTime().withDefault(Variable(DateTime.now()))();
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -43,14 +43,14 @@ Future<void> seedDb(AppDatabase database) async {
|
||||
['BgheYcxhrsw', MediaType.youtube]
|
||||
];
|
||||
|
||||
final List<String> actionTypes = [
|
||||
"[[{\"actionID\": 0, \"name\": \"1, 3, 5\", \"type\": \"repititions\", \"amount\": 1, \"weight\": 0}, {\"actionID\": 1, \"name\": \"Rest\", \"type\": \"seconds\", \"amount\": 300}], [{\"actionID\": 2, \"name\": \"1, 3, 5\", \"type\": \"repititions\", \"amount\": 1, \"weight\": 0}, {\"actionID\": 3, \"name\": \"Rest\", \"type\": \"seconds\", \"amount\": 300}], [{\"actionID\": 4, \"name\": \"1, 3, 5\", \"type\": \"repititions\", \"amount\": 1, \"weight\": 0}, {\"actionID\": 5, \"name\": \"Rest\", \"type\": \"seconds\", \"amount\": 300}]]",
|
||||
"[[{\"actionID\": 0, \"name\": \"Long Pulls\", \"type\": \"seconds\", \"amount\": 5, \"weight\": 80}, {\"actionID\": 1, \"name\": \"Rest\", \"type\": \"seconds\", \"amount\": 5}, {\"actionID\": 2, \"name\": \"Long Pulls\", \"type\": \"seconds\", \"amount\": 5, \"weights\": 80}, {\"actionID\": 3, \"name\": \"Rest\", \"type\": \"seconds\", \"amount\": 300}], [{\"actionID\": 4, \"name\": \"Long Pulls\", \"type\": \"seconds\", \"amount\": 5, \"weight\": 80}, {\"actionID\": 5, \"name\": \"Rest\", \"type\": \"seconds\", \"amount\": 5}, {\"actionID\": 6, \"name\": \"Long Pulls\", \"type\": \"seconds\", \"amount\": 5, \"weights\": 80}, {\"actionID\": 7, \"name\": \"Rest\", \"type\": \"seconds\", \"amount\": 300}], [{\"actionID\": 8, \"name\": \"Long Pulls\", \"type\": \"seconds\", \"amount\": 5, \"weight\": 80}, {\"actionID\": 9, \"name\": \"Rest\", \"type\": \"seconds\", \"amount\": 5}, {\"actionID\": 10, \"name\": \"Long Pulls\", \"type\": \"seconds\", \"amount\": 5, \"weights\": 80}, {\"actionID\": 11, \"name\": \"Rest\", \"type\": \"seconds\", \"amount\": 300}], [{\"actionID\": 12, \"name\": \"Long Pulls\", \"type\": \"seconds\", \"amount\": 5, \"weight\": 80}, {\"actionID\": 13, \"name\": \"Rest\", \"type\": \"seconds\", \"amount\": 5}, {\"actionID\": 14, \"name\": \"Long Pulls\", \"type\": \"seconds\", \"amount\": 5, \"weights\": 80}, {\"actionID\": 15, \"name\": \"Rest\", \"type\": \"seconds\", \"amount\": 300}], [{\"actionID\": 16, \"name\": \"Long Pulls\", \"type\": \"seconds\", \"amount\": 5, \"weight\": 80}, {\"actionID\": 17, \"name\": \"Rest\", \"type\": \"seconds\", \"amount\": 5}, {\"actionID\": 18, \"name\": \"Long Pulls\", \"type\": \"seconds\", \"amount\": 5, \"weights\": 80}, {\"actionID\": 19, \"name\": \"Rest\", \"type\": \"seconds\", \"amount\": 300}]]"
|
||||
];
|
||||
// final List<String> actionTypes = [
|
||||
// "[[{\"actionID\": 0, \"name\": \"1, 3, 5\", \"type\": \"repititions\", \"amount\": 1, \"weight\": 0}, {\"actionID\": 1, \"name\": \"Rest\", \"type\": \"seconds\", \"amount\": 300}], [{\"actionID\": 2, \"name\": \"1, 3, 5\", \"type\": \"repititions\", \"amount\": 1, \"weight\": 0}, {\"actionID\": 3, \"name\": \"Rest\", \"type\": \"seconds\", \"amount\": 300}], [{\"actionID\": 4, \"name\": \"1, 3, 5\", \"type\": \"repititions\", \"amount\": 1, \"weight\": 0}, {\"actionID\": 5, \"name\": \"Rest\", \"type\": \"seconds\", \"amount\": 300}]]",
|
||||
// "[[{\"actionID\": 0, \"name\": \"Long Pulls\", \"type\": \"seconds\", \"amount\": 5, \"weight\": 80}, {\"actionID\": 1, \"name\": \"Rest\", \"type\": \"seconds\", \"amount\": 5}, {\"actionID\": 2, \"name\": \"Long Pulls\", \"type\": \"seconds\", \"amount\": 5, \"weights\": 80}, {\"actionID\": 3, \"name\": \"Rest\", \"type\": \"seconds\", \"amount\": 300}], [{\"actionID\": 4, \"name\": \"Long Pulls\", \"type\": \"seconds\", \"amount\": 5, \"weight\": 80}, {\"actionID\": 5, \"name\": \"Rest\", \"type\": \"seconds\", \"amount\": 5}, {\"actionID\": 6, \"name\": \"Long Pulls\", \"type\": \"seconds\", \"amount\": 5, \"weights\": 80}, {\"actionID\": 7, \"name\": \"Rest\", \"type\": \"seconds\", \"amount\": 300}], [{\"actionID\": 8, \"name\": \"Long Pulls\", \"type\": \"seconds\", \"amount\": 5, \"weight\": 80}, {\"actionID\": 9, \"name\": \"Rest\", \"type\": \"seconds\", \"amount\": 5}, {\"actionID\": 10, \"name\": \"Long Pulls\", \"type\": \"seconds\", \"amount\": 5, \"weights\": 80}, {\"actionID\": 11, \"name\": \"Rest\", \"type\": \"seconds\", \"amount\": 300}], [{\"actionID\": 12, \"name\": \"Long Pulls\", \"type\": \"seconds\", \"amount\": 5, \"weight\": 80}, {\"actionID\": 13, \"name\": \"Rest\", \"type\": \"seconds\", \"amount\": 5}, {\"actionID\": 14, \"name\": \"Long Pulls\", \"type\": \"seconds\", \"amount\": 5, \"weights\": 80}, {\"actionID\": 15, \"name\": \"Rest\", \"type\": \"seconds\", \"amount\": 300}], [{\"actionID\": 16, \"name\": \"Long Pulls\", \"type\": \"seconds\", \"amount\": 5, \"weight\": 80}, {\"actionID\": 17, \"name\": \"Rest\", \"type\": \"seconds\", \"amount\": 5}, {\"actionID\": 18, \"name\": \"Long Pulls\", \"type\": \"seconds\", \"amount\": 5, \"weights\": 80}, {\"actionID\": 19, \"name\": \"Rest\", \"type\": \"seconds\", \"amount\": 300}]]"
|
||||
// ];
|
||||
|
||||
final int totalSessions = 15;
|
||||
final int totalActivities = 6;
|
||||
final int totalActions = 5;
|
||||
// final int totalActions = 5;
|
||||
final int totalMedia = 5;
|
||||
final random = Random();
|
||||
|
||||
@ -73,7 +73,8 @@ Future<void> seedDb(AppDatabase database) async {
|
||||
|
||||
Map<Symbol, Value> payload = {
|
||||
Symbol('title'): Value<String>(exercise['name']),
|
||||
Symbol('description'): Value<String>(json.encode(exercise['instructions'])),
|
||||
Symbol('description'):
|
||||
Value<String>(json.encode(exercise['instructions'])),
|
||||
Symbol('force'): Value<String>(exercise['force'] ?? "")
|
||||
};
|
||||
|
||||
@ -125,8 +126,7 @@ Future<void> seedDb(AppDatabase database) async {
|
||||
.into(database.mediaItems)
|
||||
.insert(MediaItemsCompanion.insert(
|
||||
title: exercise['name'],
|
||||
description:
|
||||
exercise['name'],
|
||||
description: exercise['name'],
|
||||
reference: mediaItem,
|
||||
type: MediaType.image))
|
||||
.then((mediaId) async {
|
||||
@ -175,21 +175,56 @@ Future<void> seedDb(AppDatabase database) async {
|
||||
));
|
||||
|
||||
// actions
|
||||
for (int k = 0; k <= random.nextInt(totalActions); k++) {
|
||||
await database
|
||||
.into(database.actions)
|
||||
.insert(ActionsCompanion.insert(
|
||||
title: 'Test action $k',
|
||||
description:
|
||||
'$k Beta pully beta beta pinch one arm crimpy. Futuristic pinch, dyno dynamic drop knee climb. Climbing ondra slopey onsight beta ondra power endurance.',
|
||||
set: actionTypes[random.nextInt(actionTypes.length)]))
|
||||
.then((actionId) async {
|
||||
// add activity action association
|
||||
await database.into(database.activityActions).insert(
|
||||
ActivityActionsCompanion.insert(
|
||||
activityId: activityId, actionId: actionId, position: k));
|
||||
});
|
||||
}
|
||||
// await database
|
||||
// .into(database.actions)
|
||||
// .insert(ActionsCompanion.insert(
|
||||
// title: 'Test action',
|
||||
// description:
|
||||
// 'Beta pully beta beta pinch one arm crimpy. Futuristic pinch, dyno dynamic drop knee climb. Climbing ondra slopey onsight beta ondra power endurance.',
|
||||
// totalSets: 5,
|
||||
// totalReps: "[1]",
|
||||
// restBeforeSets: Value(30000),
|
||||
// restBetweenSets: Value(300000),
|
||||
// restBetweenReps: Value(15000),
|
||||
// restAfterSets: Value(300000),
|
||||
// repType: RepType.time,
|
||||
// repLength: Value(10000),
|
||||
// repWeights: Value("[110]"),
|
||||
// setWeights: Value("[1]"),
|
||||
// isAlternating: Value(true),
|
||||
// set: actionTypes[random.nextInt(actionTypes.length)]))
|
||||
// .then((actionId) async {
|
||||
// // add activity action association
|
||||
// await database.into(database.activityActions).insert(
|
||||
// ActivityActionsCompanion.insert(
|
||||
// activityId: activityId, actionId: actionId, sessionId: sessionId, position: 0));
|
||||
// });
|
||||
// for (int k = 0; k <= random.nextInt(totalActions); k++) {
|
||||
// await database
|
||||
// .into(database.actions)
|
||||
// .insert(ActionsCompanion.insert(
|
||||
// title: 'Test action $k',
|
||||
// description:
|
||||
// '$k Beta pully beta beta pinch one arm crimpy. Futuristic pinch, dyno dynamic drop knee climb. Climbing ondra slopey onsight beta ondra power endurance.',
|
||||
// totalSets: 5,
|
||||
// totalReps: "[1]",
|
||||
// restBeforeSets: Value(30000),
|
||||
// restBetweenSets: Value(300000),
|
||||
// restBetweenReps: Value(15000),
|
||||
// restAfterSets: Value(300000),
|
||||
// repType: RepType.time,
|
||||
// repLength: Value(10000),
|
||||
// repWeights: Value("[110]"),
|
||||
// setWeights: Value("[1]"),
|
||||
// isAlternating: Value(true),
|
||||
// set: actionTypes[random.nextInt(actionTypes.length)]))
|
||||
// .then((actionId) async {
|
||||
// // add activity action association
|
||||
// await database.into(database.activityActions).insert(
|
||||
// ActivityActionsCompanion.insert(
|
||||
// activityId: activityId, actionId: actionId, position: k));
|
||||
// });
|
||||
// }
|
||||
}
|
||||
|
||||
for (int n = 0; n <= random.nextInt(totalMedia); n++) {
|
||||
|
@ -9,3 +9,8 @@ String formattedTime(int timeInSecond) {
|
||||
String second = sec.toString().length <= 1 ? "0$sec" : "$sec";
|
||||
return "$minute:$second";
|
||||
}
|
||||
|
||||
int toSeconds(int milliseconds) {
|
||||
int sec = (milliseconds / 1000).floor();
|
||||
return sec;
|
||||
}
|
||||
|
@ -6,8 +6,12 @@ showMediaDetailWidget(BuildContext context, MediaItem media) {
|
||||
showEditorSheet(context, MediaDetails(media: media));
|
||||
}
|
||||
|
||||
showGenericSheet(BuildContext context, Widget widget) {
|
||||
showGenericSheet(BuildContext context, Widget widget,
|
||||
[Color? backgroundColor]) {
|
||||
backgroundColor ??= Theme.of(context).colorScheme.surfaceBright;
|
||||
|
||||
showModalBottomSheet<void>(
|
||||
backgroundColor: backgroundColor,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(10.0), topRight: Radius.circular(10.0)),
|
||||
@ -26,15 +30,33 @@ showEditorSheet(BuildContext context, Widget widget) {
|
||||
}
|
||||
|
||||
String jsonToDescription(List text) {
|
||||
String content = '';
|
||||
String content = '';
|
||||
|
||||
for (int i = 0; i < text.length; i++) {
|
||||
if (content.isEmpty) {
|
||||
content = text[i];
|
||||
} else {
|
||||
content = "$content\n\n${text[i]}";
|
||||
}
|
||||
for (int i = 0; i < text.length; i++) {
|
||||
if (content.isEmpty) {
|
||||
content = text[i];
|
||||
} else {
|
||||
content = "$content\n\n${text[i]}";
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
Widget formItemWrapper(Widget content,
|
||||
[EdgeInsets padding = const EdgeInsets.fromLTRB(0, 0, 0, 0)]) {
|
||||
return Expanded(child: Padding(padding: padding, child: content));
|
||||
}
|
||||
|
||||
List<DropdownMenuEntry> numericDropDownItems(String type, int itemLimit) {
|
||||
final List<DropdownMenuEntry> items = [];
|
||||
|
||||
// String entryName = type;
|
||||
|
||||
for (int i = 0; i < itemLimit; i++) {
|
||||
// if (i != 0) entryName = "${type}s";
|
||||
items.add(DropdownMenuEntry(value: i + 1, label: "${i + 1}"));
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ import 'package:provider/provider.dart';
|
||||
import 'package:sendtrain/database/database.dart';
|
||||
import 'package:sendtrain/helpers/widget_helpers.dart';
|
||||
import 'package:sendtrain/models/activity_timer_model.dart';
|
||||
import 'package:sendtrain/providers/action_timer.dart';
|
||||
import 'package:sendtrain/widgets/screens/activities_screen.dart';
|
||||
import 'package:sendtrain/widgets/screens/sessions_screen.dart';
|
||||
// ignore: unused_import
|
||||
@ -78,6 +79,10 @@ class _AppState extends State<App> {
|
||||
alignment: Alignment.center,
|
||||
child: const Text('In Progress...'),
|
||||
),
|
||||
Container(
|
||||
alignment: Alignment.center,
|
||||
child: const Text('Profile in Progress...'),
|
||||
),
|
||||
][currentPageIndex]),
|
||||
bottomNavigationBar: NavigationBar(
|
||||
onDestinationSelected: (int index) {
|
||||
@ -97,7 +102,9 @@ class _AppState extends State<App> {
|
||||
NavigationDestination(
|
||||
icon: Icon(Icons.group), label: "Team Send"),
|
||||
NavigationDestination(
|
||||
icon: Icon(Icons.analytics), label: "Progress")
|
||||
icon: Icon(Icons.analytics), label: "Progress"),
|
||||
NavigationDestination(
|
||||
icon: Icon(Icons.account_circle_rounded), label: "Profile"),
|
||||
]),
|
||||
floatingActionButton: FloatingActionButton.extended(
|
||||
onPressed: () {
|
||||
@ -111,12 +118,13 @@ class _AppState extends State<App> {
|
||||
}
|
||||
|
||||
void main() {
|
||||
var db = AppDatabase();
|
||||
runApp(MultiProvider(
|
||||
providers: [
|
||||
ChangeNotifierProvider(create: (context) => ActivityTimerModel()),
|
||||
Provider<AppDatabase>(
|
||||
create: (context) => AppDatabase(),
|
||||
dispose: (context, db) => db.close()),
|
||||
create: (context) => db, dispose: (context, db) => db.close()),
|
||||
ChangeNotifierProvider(create: (context) => ActivityTimerModel()),
|
||||
ChangeNotifierProvider(create: (context) => ActionTimer()),
|
||||
],
|
||||
child: const SendTrain(),
|
||||
));
|
||||
|
260
lib/models/action_model.dart
Normal file
260
lib/models/action_model.dart
Normal file
@ -0,0 +1,260 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:sendtrain/daos/actions_dao.dart';
|
||||
import 'package:sendtrain/database/database.dart';
|
||||
import 'package:sendtrain/helpers/date_time_helpers.dart';
|
||||
|
||||
class ActionModel {
|
||||
final ActionsDao dao;
|
||||
List<Item> items;
|
||||
Action action;
|
||||
|
||||
ActionModel({required this.action, required AppDatabase db})
|
||||
: dao = ActionsDao(db),
|
||||
items = _generateItems(action);
|
||||
|
||||
int get id => action.id;
|
||||
ActionStatus get status => action.status;
|
||||
Map get state => json.decode(action.state);
|
||||
List<Set> get sets => items.whereType<Set>().toList();
|
||||
List<Item> get allItems => _flattenedItems();
|
||||
int get totalTime {
|
||||
int time = 0;
|
||||
for (int i = 0; i < allItems.length; i++) {
|
||||
Item item = allItems[i];
|
||||
time += item.time ?? 0;
|
||||
}
|
||||
|
||||
return toSeconds(time);
|
||||
}
|
||||
|
||||
List<Item> _flattenedItems() {
|
||||
List<Item> items = [];
|
||||
|
||||
for (int i = 0; i < this.items.length; i++) {
|
||||
Item item = this.items[i];
|
||||
if (item.runtimeType == Set) {
|
||||
Set setItem = item as Set;
|
||||
for (int j = 0; j < setItem.items.length; j++) {
|
||||
items.add(setItem.items[j]);
|
||||
}
|
||||
} else {
|
||||
items.add(item);
|
||||
}
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
static List<Item> _generateItems(Action action) {
|
||||
int totalItems = 0;
|
||||
int setItems = 0;
|
||||
List<Item> items = [];
|
||||
final List setReps = json.decode(action.totalReps);
|
||||
|
||||
if (action.restBeforeSets != null) {
|
||||
items.add(Rest(
|
||||
id: totalItems,
|
||||
position: totalItems,
|
||||
action: action,
|
||||
time: action.restBeforeSets!,
|
||||
name: 'prepare'));
|
||||
}
|
||||
|
||||
for (int i = 0; i < action.totalSets; i++) {
|
||||
final int totalReps;
|
||||
|
||||
if (setReps.length == 1) {
|
||||
totalReps = setReps.first;
|
||||
} else {
|
||||
totalReps = setReps[i];
|
||||
}
|
||||
|
||||
totalItems += 1;
|
||||
items.add(Set(
|
||||
id: totalItems,
|
||||
setOrder: setItems++,
|
||||
position: totalItems,
|
||||
action: action,
|
||||
totalReps: totalReps));
|
||||
|
||||
if (action.restBetweenSets != null && i < action.totalSets - 1) {
|
||||
totalItems += 1;
|
||||
items.add(Rest(
|
||||
id: totalItems,
|
||||
position: totalItems,
|
||||
action: action,
|
||||
time: action.restBetweenSets!,
|
||||
name: 'rest'));
|
||||
}
|
||||
}
|
||||
|
||||
if (action.restAfterSets != null && totalItems != items.length) {
|
||||
totalItems += 1;
|
||||
items.add(Rest(
|
||||
id: totalItems,
|
||||
position: totalItems,
|
||||
action: action,
|
||||
time: action.restAfterSets!,
|
||||
name: 'cooldown'));
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
Future<Action> updateStatus(ActionStatus status) async {
|
||||
Action newAction = action.copyWith(id: action.id, status: status);
|
||||
await dao.createOrUpdate(newAction.toCompanion(true));
|
||||
action = newAction;
|
||||
return newAction;
|
||||
}
|
||||
|
||||
Future<Action> updateState(String state) async {
|
||||
Action newAction = action.copyWith(id: action.id, state: state);
|
||||
await dao.createOrUpdate(newAction.toCompanion(true));
|
||||
action = newAction;
|
||||
return newAction;
|
||||
}
|
||||
}
|
||||
|
||||
class Item {
|
||||
final int id;
|
||||
final Action action;
|
||||
int position;
|
||||
List<Item> items = [];
|
||||
dynamic value;
|
||||
final String name;
|
||||
int? parentId;
|
||||
int? time;
|
||||
|
||||
Item(
|
||||
{required this.id,
|
||||
required this.position,
|
||||
required this.action,
|
||||
this.parentId,
|
||||
this.time})
|
||||
: name = action.title;
|
||||
|
||||
RepType get valueType => action.repType;
|
||||
String get humanValueType => valueType == RepType.time ? 'seconds' : 'reps';
|
||||
}
|
||||
|
||||
class Set extends Item {
|
||||
final int totalReps;
|
||||
int? setOrder;
|
||||
|
||||
Set(
|
||||
{required super.id,
|
||||
required super.action,
|
||||
required super.position,
|
||||
required this.totalReps,
|
||||
this.setOrder}) {
|
||||
items = _generateItems(action, id, totalReps);
|
||||
}
|
||||
|
||||
int? get weightMultiplyer =>
|
||||
action.setWeights != null ? json.decode(action.setWeights!)[id] : null;
|
||||
List<Reps> get reps => items.whereType<Reps>().toList();
|
||||
|
||||
static List<Item> _generateItems(action, id, totalReps) {
|
||||
List<Item> items = [];
|
||||
// add item for exercise
|
||||
int position = 0;
|
||||
|
||||
if (action.repType == RepType.time) {
|
||||
for (int i = 0; i < totalReps; i++) {
|
||||
position = position > 0 ? position + 1 : position;
|
||||
|
||||
// don't show a rest before first rep
|
||||
if (i > 0) {
|
||||
items.add(Rest(
|
||||
id: position,
|
||||
position: position,
|
||||
parentId: id,
|
||||
action: action,
|
||||
time: action.restBetweenReps,
|
||||
name: 'rest'));
|
||||
}
|
||||
|
||||
items.add(Reps(
|
||||
id: ++position, position: position, parentId: id, action: action));
|
||||
|
||||
if (action.isAlternating) {
|
||||
items.add(Rest(
|
||||
id: ++position,
|
||||
position: position,
|
||||
parentId: id,
|
||||
action: action,
|
||||
time: action.restBetweenReps,
|
||||
name: 'alternate'));
|
||||
items.add(Reps(
|
||||
id: ++position,
|
||||
position: position,
|
||||
parentId: id,
|
||||
action: action));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
items.add(Reps(id: id, position: position, action: action));
|
||||
|
||||
if (action.isAlternating) {
|
||||
items.add(Rest(
|
||||
id: ++position,
|
||||
position: position,
|
||||
parentId: id,
|
||||
action: action,
|
||||
time: action.restBetweenReps,
|
||||
name: 'alternate'));
|
||||
items.add(Reps(id: id, position: ++position, action: action));
|
||||
}
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
}
|
||||
|
||||
class Reps extends Item {
|
||||
Reps(
|
||||
{required super.id,
|
||||
required super.position,
|
||||
required super.action,
|
||||
super.parentId});
|
||||
|
||||
@override
|
||||
dynamic get value => type == RepType.time ? time : count;
|
||||
|
||||
RepType get type => action.repType;
|
||||
@override
|
||||
int? get time => toSeconds(action.repLength!);
|
||||
int? get count => getReps(id, json.decode(action.totalReps));
|
||||
int? get weight =>
|
||||
action.repWeights != null ? json.decode(action.repWeights!)[id] : null;
|
||||
|
||||
static int getReps(setId, reps) {
|
||||
if (reps.length > 1) {
|
||||
return reps[setId];
|
||||
} else {
|
||||
return reps.first;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Rest extends Item {
|
||||
@override
|
||||
String name;
|
||||
|
||||
Rest(
|
||||
{required super.id,
|
||||
required super.position,
|
||||
required super.action,
|
||||
super.parentId,
|
||||
required super.time,
|
||||
required this.name});
|
||||
|
||||
// @override
|
||||
// String get name => 'Rest';
|
||||
@override
|
||||
int get value => toSeconds(time ?? 0);
|
||||
@override
|
||||
RepType get valueType => RepType.time;
|
||||
}
|
211
lib/providers/action_timer.dart
Normal file
211
lib/providers/action_timer.dart
Normal file
@ -0,0 +1,211 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_sound/public/flutter_sound_player.dart';
|
||||
import 'package:flutter_sound/flutter_sound.dart';
|
||||
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
|
||||
import 'package:sendtrain/database/database.dart';
|
||||
import 'package:sendtrain/models/action_model.dart';
|
||||
|
||||
class ActionTimer with ChangeNotifier {
|
||||
ActionModel? actionModel;
|
||||
double _progress = 0;
|
||||
int _currentTime = 0;
|
||||
final List<ItemScrollController> _scrollControllers = [];
|
||||
final FlutterSoundPlayer _mPlayer = FlutterSoundPlayer();
|
||||
|
||||
ActionTimer();
|
||||
|
||||
Map get state => actionModel?.state ?? _stateConstructor();
|
||||
ActionStatus get status => actionModel?.status ?? ActionStatus.pending;
|
||||
bool get started => status == ActionStatus.started;
|
||||
bool get paused => status == ActionStatus.paused;
|
||||
bool get pending => status == ActionStatus.pending;
|
||||
bool get complete => status == ActionStatus.complete;
|
||||
bool get available => paused | pending;
|
||||
List<Set> get sets => actionModel!.sets;
|
||||
List<Item> get items => actionModel!.items;
|
||||
Set get currentSet => sets[state['currentSet']];
|
||||
Reps get currentRep => currentSet.reps[state['currentRep']];
|
||||
Item get currentAction => allActions[state['currentAction']];
|
||||
int get currentTime => _currentTime;
|
||||
dynamic get currentValue => currentAction.valueType == RepType.time
|
||||
? currentTime
|
||||
: currentAction.value;
|
||||
List<Item> get allActions => actionModel?.allItems ?? [];
|
||||
String get repType =>
|
||||
actionModel!.action.repType == RepType.time ? 'Seconds' : 'Reps';
|
||||
int? get repLength => currentRep.value;
|
||||
int? get repCount => currentRep.count;
|
||||
dynamic get repValue =>
|
||||
actionModel!.action.repType == RepType.time ? repLength : repCount;
|
||||
double get progress => _progress;
|
||||
int get totalTime => actionModel!.totalTime;
|
||||
Timer? _periodicTimer;
|
||||
|
||||
Map _stateConstructor() {
|
||||
return {
|
||||
'currentSet': 0,
|
||||
'currentRep': 0,
|
||||
'currentTime': 0,
|
||||
'currentAction': 0
|
||||
};
|
||||
}
|
||||
|
||||
void setup(ActionModel actionModel, ItemScrollController scrollController,
|
||||
[bool resetOnLoad = true]) async {
|
||||
_scrollControllers.add(scrollController);
|
||||
|
||||
if (resetOnLoad) {
|
||||
if (this.actionModel == actionModel) {
|
||||
reset();
|
||||
_scrollControllers.add(scrollController);
|
||||
}
|
||||
|
||||
this.actionModel = actionModel;
|
||||
setAction(currentAction.id);
|
||||
}
|
||||
}
|
||||
|
||||
Future pause() async =>
|
||||
await actionModel?.updateStatus(ActionStatus.paused).whenComplete(() {
|
||||
_periodicTimer?.cancel();
|
||||
notifyListeners();
|
||||
|
||||
// _mPlayer.stopPlayer();
|
||||
// Be careful : you must `close` the audio session when you have finished with it.
|
||||
});
|
||||
|
||||
Future start() async {
|
||||
await actionModel!.updateStatus(ActionStatus.started);
|
||||
await _mPlayer.openPlayer();
|
||||
|
||||
Uint8List? countTone;
|
||||
Uint8List? finishTone;
|
||||
await rootBundle
|
||||
.load('assets/audio/count_tone.wav')
|
||||
.then((data) => countTone = data.buffer.asUint8List());
|
||||
await rootBundle
|
||||
.load('assets/audio/count_finish.mp3')
|
||||
.then((data) => finishTone = data.buffer.asUint8List());
|
||||
|
||||
// start timer
|
||||
if (_periodicTimer == null || _periodicTimer!.isActive == false) {
|
||||
_periodicTimer =
|
||||
Timer.periodic(const Duration(seconds: 1), (Timer timer) async {
|
||||
switch (currentAction.valueType) {
|
||||
case RepType.count:
|
||||
break;
|
||||
case RepType.time:
|
||||
_currentTime--;
|
||||
|
||||
if (_currentTime <= 3 && _currentTime != 0) {
|
||||
await _mPlayer.startPlayer(
|
||||
fromDataBuffer: countTone, codec: Codec.pcm16WAV);
|
||||
}
|
||||
|
||||
if (_currentTime == 0) {
|
||||
// move to next action
|
||||
await _mPlayer.startPlayer(
|
||||
fromDataBuffer: finishTone, codec: Codec.mp3);
|
||||
await setAction(state['currentAction'] + 1);
|
||||
}
|
||||
|
||||
await updateProgress();
|
||||
notifyListeners();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future close() async => await actionModel!
|
||||
.updateStatus(ActionStatus.complete)
|
||||
.whenComplete(() async {
|
||||
_periodicTimer!.cancel();
|
||||
_mPlayer.closePlayer();
|
||||
notifyListeners();
|
||||
});
|
||||
|
||||
Future reset() async {
|
||||
await actionModel?.updateStatus(ActionStatus.pending);
|
||||
await actionModel?.updateState(json.encode(_stateConstructor()));
|
||||
_periodicTimer?.cancel();
|
||||
_progress = 0;
|
||||
_scrollControllers.clear();
|
||||
_mPlayer.closePlayer();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future clear() async {
|
||||
await reset();
|
||||
}
|
||||
|
||||
double timeUsed() {
|
||||
Iterable<Item> usedItems = allActions.getRange(0, state['currentAction']);
|
||||
return usedItems.fold(0.0, (p, c) => p + c.value!);
|
||||
}
|
||||
|
||||
double totalComplete() {
|
||||
Iterable<Item> usedItems = allActions.getRange(0, state['currentAction']);
|
||||
return usedItems.length / allActions.length;
|
||||
}
|
||||
|
||||
updateProgress() {
|
||||
double repUsed = (currentAction.value - currentTime) / currentAction.value;
|
||||
_progress =
|
||||
totalComplete() + ((repUsed < 0 ? 0 : repUsed) / allActions.length);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
setAction(int actionNum, [bool isManual = false]) async {
|
||||
if (actionNum < allActions.length) {
|
||||
Item item = allActions[actionNum];
|
||||
Map newState = state;
|
||||
|
||||
newState['currentAction'] = actionNum;
|
||||
newState['currentSet'] = item.parentId;
|
||||
newState['currentRep'] = item.id;
|
||||
newState['currentTime'] = _currentTime = item.value!;
|
||||
|
||||
await actionModel!
|
||||
.updateState(json.encode(newState))
|
||||
.whenComplete(() async {
|
||||
// if manual select, pause next action
|
||||
if (isManual) {
|
||||
await pause();
|
||||
await updateProgress();
|
||||
}
|
||||
|
||||
int index = currentAction.parentId != null
|
||||
? currentAction.parentId!
|
||||
: currentAction.id;
|
||||
|
||||
if (_scrollControllers.isNotEmpty) {
|
||||
for (int i = 0; i < _scrollControllers.length; i++) {
|
||||
ItemScrollController sc = _scrollControllers[i];
|
||||
|
||||
sc.scrollTo(
|
||||
index: index,
|
||||
duration: Duration(milliseconds: 500),
|
||||
curve: Curves.easeInOutCubic);
|
||||
}
|
||||
}
|
||||
// _scrollController?.scrollTo(
|
||||
// index: index,
|
||||
// duration: Duration(milliseconds: 500),
|
||||
// curve: Curves.easeInOutCubic);
|
||||
});
|
||||
} else {
|
||||
await actionModel?.updateStatus(ActionStatus.complete).whenComplete(() {
|
||||
_periodicTimer?.cancel();
|
||||
notifyListeners();
|
||||
});
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
305
lib/widgets/activities/activity_action_editor.dart
Normal file
305
lib/widgets/activities/activity_action_editor.dart
Normal file
@ -0,0 +1,305 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:drift/drift.dart' hide Column;
|
||||
import 'package:flutter/material.dart' hide Action;
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:sendtrain/daos/actions_dao.dart';
|
||||
import 'package:sendtrain/daos/activity_actions_dao.dart';
|
||||
import 'package:sendtrain/database/database.dart';
|
||||
import 'package:sendtrain/helpers/widget_helpers.dart';
|
||||
import 'package:sendtrain/widgets/generic/elements/form_drop_down.dart';
|
||||
import 'package:sendtrain/widgets/generic/elements/form_text_input.dart';
|
||||
|
||||
class ActivityActionEditor extends StatefulWidget {
|
||||
const ActivityActionEditor(
|
||||
{super.key,
|
||||
required this.session,
|
||||
required this.activity,
|
||||
this.action,
|
||||
this.callback});
|
||||
|
||||
final Session session;
|
||||
final Activity activity;
|
||||
final Action? action;
|
||||
final Function? callback;
|
||||
|
||||
@override
|
||||
State<ActivityActionEditor> createState() => _ActivityActionEditorState();
|
||||
}
|
||||
|
||||
class _ActivityActionEditorState extends State<ActivityActionEditor> {
|
||||
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
|
||||
|
||||
final Map<String, TextEditingController> actionEditController = {
|
||||
'sets': TextEditingController(),
|
||||
'reps': TextEditingController(),
|
||||
'weight': TextEditingController(),
|
||||
'repLength': TextEditingController(),
|
||||
'preparation': TextEditingController(),
|
||||
'setRest': TextEditingController(),
|
||||
'repRest': TextEditingController(),
|
||||
'cooldown': TextEditingController(),
|
||||
'type': TextEditingController(),
|
||||
'alternating': TextEditingController(),
|
||||
};
|
||||
|
||||
late final AppDatabase db;
|
||||
|
||||
bool isAlternating = false;
|
||||
bool isTimed = false;
|
||||
String editorType = 'Create';
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
db = Provider.of<AppDatabase>(context, listen: false);
|
||||
|
||||
// if we're editing a session, we'll want to populate it with the appropriate values
|
||||
if (widget.action != null) {
|
||||
final Action action = widget.action!;
|
||||
editorType = 'Edit';
|
||||
isAlternating = action.isAlternating;
|
||||
isTimed = action.repType == RepType.time ? true : false;
|
||||
|
||||
actionEditController['sets']?.text = action.totalSets.toString();
|
||||
actionEditController['reps']?.text =
|
||||
json.decode(action.totalReps)[0].toString();
|
||||
actionEditController['weight']?.text =
|
||||
json.decode(action.repWeights ?? "")[0].toString();
|
||||
actionEditController['repLength']?.text =
|
||||
((action.repLength ?? 0) ~/ 1000).toString();
|
||||
actionEditController['preparation']?.text =
|
||||
((action.restBeforeSets ?? 0) ~/ 1000).toString();
|
||||
actionEditController['setRest']?.text =
|
||||
((action.restBetweenSets ?? 0) ~/ 1000).toString();
|
||||
actionEditController['repRest']?.text =
|
||||
((action.restBetweenReps ?? 0) ~/ 1000).toString();
|
||||
actionEditController['cooldown']?.text =
|
||||
((action.restAfterSets ?? 0) ~/ 1000).toString();
|
||||
actionEditController['isTimed']?.text = isTimed.toString();
|
||||
actionEditController['alternating']?.text = isAlternating.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (widget.action != null) {
|
||||
editorType = 'Edit';
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: EdgeInsets.fromLTRB(15, 0, 15, 15),
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 10, bottom: 10),
|
||||
child: Text('$editorType Action',
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.titleLarge)),
|
||||
Row(children: [
|
||||
formItemWrapper(
|
||||
CheckboxListTile(
|
||||
title: Text("Reps alternate? (eg. Left/Right Hand)"),
|
||||
value: isAlternating,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius:
|
||||
BorderRadius.all(Radius.circular(10.0)),
|
||||
),
|
||||
onChanged: (bool? value) {
|
||||
setState(() {
|
||||
isAlternating = value!;
|
||||
});
|
||||
},
|
||||
),
|
||||
EdgeInsets.fromLTRB(10, 10, 10, 10)),
|
||||
]),
|
||||
Row(children: [
|
||||
formItemWrapper(
|
||||
CheckboxListTile(
|
||||
title: Text("Are reps timed?"),
|
||||
value: isTimed,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius:
|
||||
BorderRadius.all(Radius.circular(10.0)),
|
||||
),
|
||||
onChanged: (bool? value) {
|
||||
setState(() {
|
||||
isTimed = value!;
|
||||
});
|
||||
},
|
||||
),
|
||||
EdgeInsets.fromLTRB(10, 10, 10, 15))
|
||||
]),
|
||||
Row(children: [
|
||||
FormDropDown(
|
||||
title: 'Sets',
|
||||
entries: numericDropDownItems('Set', 50),
|
||||
controller: actionEditController['sets']!),
|
||||
FormDropDown(
|
||||
title: 'Reps',
|
||||
entries: numericDropDownItems('Rep', 100),
|
||||
controller: actionEditController['reps']!,
|
||||
)
|
||||
]),
|
||||
Row(children: [
|
||||
formItemWrapper(
|
||||
FormTextInput(
|
||||
type: InputTypes.number,
|
||||
controller: actionEditController['preparation']!,
|
||||
title: 'Preparation (sec)',
|
||||
hint: 'time before start',
|
||||
requiresValidation: false),
|
||||
EdgeInsets.fromLTRB(10, 5, 10, 0)),
|
||||
formItemWrapper(
|
||||
FormTextInput(
|
||||
type: InputTypes.number,
|
||||
controller: actionEditController['cooldown']!,
|
||||
title: 'Cooldown (sec)',
|
||||
hint: 'rest after completion',
|
||||
requiresValidation: false),
|
||||
EdgeInsets.fromLTRB(10, 5, 10, 0)),
|
||||
]),
|
||||
Row(children: [
|
||||
formItemWrapper(
|
||||
FormTextInput(
|
||||
type: InputTypes.number,
|
||||
controller: actionEditController['setRest']!,
|
||||
title: 'Set Rest (sec)',
|
||||
hint: 'Rest between sets',
|
||||
requiresValidation: false),
|
||||
EdgeInsets.only(left: 10, right: 10)),
|
||||
formItemWrapper(
|
||||
FormTextInput(
|
||||
type: InputTypes.number,
|
||||
controller: actionEditController['repRest']!,
|
||||
title: 'Rep Rest (sec)',
|
||||
hint: 'Rest between reps',
|
||||
requiresValidation: false),
|
||||
EdgeInsets.only(left: 10, right: 10)),
|
||||
]),
|
||||
Row(children: [
|
||||
formItemWrapper(
|
||||
FormTextInput(
|
||||
type: InputTypes.number,
|
||||
controller: actionEditController['repLength']!,
|
||||
title: 'Rep Length (sec)',
|
||||
hint: 'Total rep time (not required)',
|
||||
requiresValidation: false),
|
||||
EdgeInsets.only(left: 10, right: 10)),
|
||||
formItemWrapper(
|
||||
FormTextInput(
|
||||
type: InputTypes.number,
|
||||
controller: actionEditController['weight']!,
|
||||
title: 'Weight',
|
||||
hint: 'Weight for reps',
|
||||
requiresValidation: false),
|
||||
EdgeInsets.only(left: 10, right: 10)),
|
||||
]),
|
||||
Row(mainAxisAlignment: MainAxisAlignment.end, children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 10, right: 10),
|
||||
child: FilledButton(
|
||||
onPressed: () async {
|
||||
if (_formKey.currentState!.validate()) {
|
||||
if (widget.action != null) {
|
||||
Action newAction = widget.action!.copyWith(
|
||||
totalSets: int.parse(
|
||||
actionEditController['sets']!.text),
|
||||
totalReps: json.encode([
|
||||
int.parse(
|
||||
actionEditController['reps']!.text)
|
||||
]),
|
||||
repLength: Value<int>(int.parse(
|
||||
actionEditController['repLength']!
|
||||
.text) *
|
||||
1000),
|
||||
restBeforeSets: Value<int>(int.parse(
|
||||
actionEditController['preparation']!
|
||||
.text) *
|
||||
1000),
|
||||
restBetweenSets: Value<int>(int.parse(
|
||||
actionEditController['setRest']!
|
||||
.text) *
|
||||
1000),
|
||||
restBetweenReps: Value<int>(int.parse(
|
||||
actionEditController['repRest']!
|
||||
.text) *
|
||||
1000),
|
||||
restAfterSets: Value<int>(int.parse(
|
||||
actionEditController['cooldown']!
|
||||
.text) *
|
||||
1000),
|
||||
repType: int.parse(actionEditController[
|
||||
'repLength']!
|
||||
.text) >
|
||||
0
|
||||
? RepType.time
|
||||
: RepType.count,
|
||||
repWeights: Value<String>(json.encode([
|
||||
int.parse(
|
||||
actionEditController['weight']!.text)
|
||||
])),
|
||||
// setWeights: Value<String>(json.encode([actionEditController['setWeights']!.text])),
|
||||
isAlternating: isAlternating,
|
||||
);
|
||||
|
||||
// var result = await ActionsDao(db).createOrUpdate(
|
||||
// newAction.toCompanion(true));
|
||||
await ActionsDao(db).replace(newAction);
|
||||
} else {
|
||||
// create action
|
||||
await ActionsDao(db)
|
||||
.createOrUpdate(ActionsCompanion(
|
||||
title: Value('title'),
|
||||
description: Value('description'),
|
||||
totalSets: Value(int.parse(
|
||||
actionEditController['sets']!
|
||||
.text)),
|
||||
totalReps: Value(json.encode(
|
||||
[int.parse(actionEditController['reps']!.text)])),
|
||||
repLength: Value<int>(
|
||||
int.parse(actionEditController['repLength']!.text) *
|
||||
1000),
|
||||
restBeforeSets: Value<int>(
|
||||
int.parse(actionEditController['preparation']!.text) *
|
||||
1000),
|
||||
restBetweenSets: Value<int>(
|
||||
int.parse(actionEditController['setRest']!.text) *
|
||||
1000),
|
||||
restBetweenReps:
|
||||
Value<int>(int.parse(actionEditController['repRest']!.text) * 1000),
|
||||
restAfterSets: Value<int>(int.parse(actionEditController['cooldown']!.text) * 1000),
|
||||
repType: Value(int.parse(actionEditController['repLength']!.text) > 0 ? RepType.time : RepType.count),
|
||||
repWeights: Value<String>(json.encode([int.parse(actionEditController['weight']!.text)])),
|
||||
// setWeights: Value<String>(json.encode([actionEditController['setWeights']!.text])),
|
||||
isAlternating: Value<bool>(isAlternating),
|
||||
// repType: RepType.values.firstWhere((e) => e.toString() == "RepType.${actionEditController['repType']!.text}"),
|
||||
set: Value("")))
|
||||
.then((actionId) {
|
||||
ActivityActionsDao(db).createOrUpdate(
|
||||
ActivityActionsCompanion(
|
||||
activityId:
|
||||
Value(widget.activity.id),
|
||||
sessionId: Value(widget.session.id),
|
||||
actionId: Value(actionId),
|
||||
position: Value(0)));
|
||||
});
|
||||
}
|
||||
|
||||
Navigator.pop(
|
||||
_formKey.currentContext!, 'Submit');
|
||||
|
||||
if (widget.callback != null) {
|
||||
await widget.callback!();
|
||||
}
|
||||
}
|
||||
},
|
||||
child: Text('Submit')))
|
||||
])
|
||||
])));
|
||||
}
|
||||
}
|
@ -1,20 +1,38 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
|
||||
import 'package:sendtrain/database/database.dart';
|
||||
import 'package:sendtrain/extensions/string_extensions.dart';
|
||||
import 'package:sendtrain/models/activity_timer_model.dart';
|
||||
import 'package:sendtrain/helpers/widget_helpers.dart';
|
||||
import 'package:sendtrain/models/action_model.dart';
|
||||
import 'package:sendtrain/providers/action_timer.dart';
|
||||
import 'package:sendtrain/widgets/activities/activity_action_editor.dart';
|
||||
import 'package:sendtrain/widgets/generic/elements/add_card_generic.dart';
|
||||
|
||||
class ActivityActionView extends StatefulWidget {
|
||||
const ActivityActionView({super.key, required this.actions});
|
||||
// class ActivityActionView extends StatefulWidget {
|
||||
class ActivityActionView extends StatelessWidget {
|
||||
ActivityActionView(
|
||||
{super.key,
|
||||
required this.session,
|
||||
required this.activity,
|
||||
required this.actions,
|
||||
this.callback,
|
||||
this.resetOnLoad = true});
|
||||
final Session session;
|
||||
final Activity activity;
|
||||
final List actions;
|
||||
final Function? callback;
|
||||
final bool resetOnLoad;
|
||||
|
||||
@override
|
||||
State<ActivityActionView> createState() => ActivityActionViewState();
|
||||
}
|
||||
// @override
|
||||
// State<ActivityActionView> createState() => ActivityActionViewState();
|
||||
// }
|
||||
|
||||
class ActivityActionViewState extends State<ActivityActionView> {
|
||||
// class ActivityActionViewState extends State<ActivityActionView> {
|
||||
// class ActivityActionView extends StatelessWidget {
|
||||
// ActivityActionView({super.key, required this.actions});
|
||||
|
||||
// final List actions;
|
||||
final ItemScrollController itemScrollController = ItemScrollController();
|
||||
final ScrollOffsetController scrollOffsetController =
|
||||
ScrollOffsetController();
|
||||
@ -23,88 +41,210 @@ class ActivityActionViewState extends State<ActivityActionView> {
|
||||
final ScrollOffsetListener scrollOffsetListener =
|
||||
ScrollOffsetListener.create();
|
||||
|
||||
late final ActionTimer at;
|
||||
// int actionCount = 0;
|
||||
|
||||
GestureDetector gtBuild(
|
||||
ActionTimer at, Item item, int actionNum, int selectedIndex,
|
||||
{int? order}) {
|
||||
// default, for rests
|
||||
String setItemRef = '-';
|
||||
|
||||
// non rests decimal reference to item
|
||||
if (order != null) {
|
||||
setItemRef = '${order + 1}.${item.position + 1}';
|
||||
}
|
||||
|
||||
return GestureDetector(onTap: () {
|
||||
at.setAction(actionNum, true);
|
||||
}, child: Consumer<ActionTimer>(builder: (context, at, child) {
|
||||
return Row(children: [
|
||||
Ink(
|
||||
width: 70,
|
||||
padding: const EdgeInsets.all(15),
|
||||
color: item == at.currentAction
|
||||
? Theme.of(context).colorScheme.primaryContainer
|
||||
: Theme.of(context).colorScheme.onPrimary,
|
||||
child: Text(textAlign: TextAlign.center, setItemRef)),
|
||||
Expanded(
|
||||
child: Ink(
|
||||
padding: const EdgeInsets.all(15),
|
||||
color: item == at.currentAction
|
||||
? Theme.of(context).colorScheme.surfaceBright
|
||||
: Theme.of(context).colorScheme.surfaceContainerLow,
|
||||
child: Text(
|
||||
textAlign: TextAlign.center,
|
||||
'${item.name}: ${item.value} ${item.humanValueType}'
|
||||
.toTitleCase())))
|
||||
]);
|
||||
}));
|
||||
}
|
||||
|
||||
// @override
|
||||
// void initState() {
|
||||
// super.initState();
|
||||
// at = Provider.of<ActionTimer>(context, listen: false);
|
||||
// }
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
ActivityTimerModel atm =
|
||||
Provider.of<ActivityTimerModel>(context, listen: true);
|
||||
List sets = json.decode(widget.actions[0].set);
|
||||
at = Provider.of<ActionTimer>(context, listen: false);
|
||||
int actionCount = 0;
|
||||
if (actions.isNotEmpty) {
|
||||
at.setup(
|
||||
ActionModel(
|
||||
action: actions.first,
|
||||
db: Provider.of<AppDatabase>(context)),
|
||||
itemScrollController,
|
||||
resetOnLoad);
|
||||
|
||||
// we need to set the scroll controller
|
||||
// so we can update the selected item position
|
||||
atm.setScrollController(itemScrollController);
|
||||
// WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
// if (itemScrollController.isAttached) {
|
||||
// itemScrollController.scrollTo(
|
||||
// index: at.currentAction.parentId != null
|
||||
// ? at.currentAction.parentId!
|
||||
// : at.currentAction.id,
|
||||
// duration: Duration(milliseconds: 500),
|
||||
// curve: Curves.easeInOutCubic);
|
||||
// }
|
||||
// });
|
||||
|
||||
return Expanded(
|
||||
child: ScrollablePositionedList.builder(
|
||||
padding: const EdgeInsets.fromLTRB(10, 0, 10, 20),
|
||||
itemCount: sets.length,
|
||||
itemScrollController: itemScrollController,
|
||||
scrollOffsetController: scrollOffsetController,
|
||||
itemPositionsListener: itemPositionsListener,
|
||||
scrollOffsetListener: scrollOffsetListener,
|
||||
itemBuilder: (BuildContext context, int setNum) {
|
||||
List<GestureDetector> content = [];
|
||||
List set = sets[setNum];
|
||||
return Expanded(
|
||||
child: Column(children: [
|
||||
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)),
|
||||
),
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
child: Row(children: [
|
||||
Ink(
|
||||
width: 70,
|
||||
color: Theme.of(context).colorScheme.primaryContainer,
|
||||
child: Consumer<ActionTimer>(
|
||||
builder: (context, at, child) {
|
||||
return IconButton(
|
||||
alignment: AlignmentDirectional.center,
|
||||
icon: at.available
|
||||
? const Icon(Icons.play_arrow_rounded)
|
||||
: const Icon(Icons.pause_rounded),
|
||||
onPressed: () => {
|
||||
if (at.started)
|
||||
{at.pause()}
|
||||
else if (at.available || at.complete)
|
||||
{at.start()}
|
||||
});
|
||||
},
|
||||
)),
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: Stack(alignment: Alignment.center, children: [
|
||||
Container(
|
||||
alignment: Alignment.center,
|
||||
child: Consumer<ActionTimer>(
|
||||
builder: (context, at, child) {
|
||||
return Text(
|
||||
style: const TextStyle(fontSize: 20),
|
||||
textAlign: TextAlign.center,
|
||||
'${at.currentValue} ${at.currentAction.humanValueType}'
|
||||
.toTitleCase());
|
||||
},
|
||||
),
|
||||
),
|
||||
Container(
|
||||
alignment: Alignment.centerRight,
|
||||
padding: EdgeInsets.only(right: 15),
|
||||
child: Consumer<ActionTimer>(
|
||||
builder: (context, at, child) {
|
||||
return Text(
|
||||
style: const TextStyle(fontSize: 12),
|
||||
textAlign: TextAlign.right,
|
||||
'${at.state['currentAction'] + 1} of ${at.allActions.length}');
|
||||
})),
|
||||
])),
|
||||
]))),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 14, right: 14),
|
||||
child: Consumer<ActionTimer>(builder: (context, at, child) {
|
||||
return LinearProgressIndicator(
|
||||
value: at.progress,
|
||||
semanticsLabel: 'Activity Progress',
|
||||
);
|
||||
})),
|
||||
Expanded(
|
||||
child: ScrollablePositionedList.builder(
|
||||
padding: const EdgeInsets.fromLTRB(10, 0, 10, 20),
|
||||
itemCount: at.items.length,
|
||||
// initialScrollIndex: at.currentAction.parentId != null
|
||||
// ? at.currentAction.parentId!
|
||||
// : at.currentAction.id,
|
||||
itemScrollController: itemScrollController,
|
||||
scrollOffsetController: scrollOffsetController,
|
||||
itemPositionsListener: itemPositionsListener,
|
||||
scrollOffsetListener: scrollOffsetListener,
|
||||
itemBuilder: (BuildContext context, int itemNum) {
|
||||
if (itemNum == 0) {
|
||||
actionCount = 0;
|
||||
}
|
||||
|
||||
for (int actionNum = 0; actionNum < set.length; actionNum++) {
|
||||
Map<String, dynamic> setItem = set[actionNum];
|
||||
List<GestureDetector> content = [];
|
||||
Item item = at.items[itemNum];
|
||||
if (item.runtimeType == Rest) {
|
||||
content.add(gtBuild(at, item, actionCount++, itemNum));
|
||||
} else if (item.runtimeType == Set) {
|
||||
List<Item> setItems = item.items;
|
||||
|
||||
content.add(GestureDetector(
|
||||
onTap: () {
|
||||
atm.setAction(setNum, actionNum, 'manual');
|
||||
atm.setActionCount();
|
||||
for (int setItemNum = 0;
|
||||
setItemNum < setItems.length;
|
||||
setItemNum++) {
|
||||
Item setItem = setItems[setItemNum];
|
||||
content.add(gtBuild(at, setItem, actionCount++, itemNum,
|
||||
order: (item as Set).setOrder));
|
||||
}
|
||||
}
|
||||
|
||||
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']}'.toTitleCase())))
|
||||
])));
|
||||
}
|
||||
|
||||
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);
|
||||
},
|
||||
));
|
||||
if (itemNum == 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));
|
||||
}
|
||||
}))
|
||||
]));
|
||||
} else {
|
||||
return AddCardGeneric(
|
||||
title: 'Add an Action!',
|
||||
description:
|
||||
'Click here to create an exercise template (sets and reps, etc) for your activity!',
|
||||
action: () {
|
||||
showEditorSheet(
|
||||
context,
|
||||
ActivityActionEditor(
|
||||
session: session,
|
||||
activity: activity,
|
||||
callback: callback));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ class ActivityCardState extends State<ActivityCard> {
|
||||
clipBehavior: Clip.hardEdge,
|
||||
child: InkWell(
|
||||
onTap: () => showGenericDialog(
|
||||
ActivityView(activity: widget.activity), context),
|
||||
ActivityView(session: widget.session, activity: widget.activity), context),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
|
@ -1,20 +1,23 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/material.dart' hide Action;
|
||||
import 'package:flutter_expandable_fab/flutter_expandable_fab.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:sendtrain/daos/actions_dao.dart';
|
||||
import 'package:sendtrain/database/database.dart';
|
||||
import 'package:sendtrain/extensions/string_extensions.dart';
|
||||
import 'package:sendtrain/helpers/widget_helpers.dart';
|
||||
import 'package:sendtrain/models/activity_timer_model.dart';
|
||||
import 'package:sendtrain/providers/action_timer.dart';
|
||||
import 'package:sendtrain/widgets/activities/activity_action_editor.dart';
|
||||
import 'package:sendtrain/widgets/activities/activity_action_view.dart';
|
||||
import 'package:sendtrain/widgets/activities/activity_view_categories.dart';
|
||||
import 'package:sendtrain/widgets/activities/activity_view_media.dart';
|
||||
import 'package:sendtrain/widgets/generic/elements/add_card_generic.dart';
|
||||
import 'package:sendtrain/widgets/builders/dialogs.dart';
|
||||
|
||||
class ActivityView extends StatefulWidget {
|
||||
const ActivityView({super.key, required this.activity});
|
||||
const ActivityView(
|
||||
{super.key, required this.session, required this.activity});
|
||||
final Session session;
|
||||
final Activity activity;
|
||||
|
||||
@override
|
||||
@ -22,7 +25,18 @@ class ActivityView extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _ActivityViewState extends State<ActivityView> {
|
||||
List<ActivityMuscle> activity_muscle(Activity activity) {
|
||||
final _fabKey = GlobalKey<ExpandableFabState>();
|
||||
|
||||
void resetState() async {
|
||||
final state = _fabKey.currentState;
|
||||
if (state != null && state.isOpen) {
|
||||
state.toggle();
|
||||
}
|
||||
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
List<ActivityMuscle> activityMuscle(Activity activity) {
|
||||
List<ActivityMuscle> muscles = [];
|
||||
|
||||
if (activity.primaryMuscles != null) {
|
||||
@ -36,132 +50,83 @@ class _ActivityViewState extends State<ActivityView> {
|
||||
return muscles;
|
||||
}
|
||||
|
||||
List<Widget> action(actions) {
|
||||
if (actions.isNotEmpty) {
|
||||
return [
|
||||
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)),
|
||||
),
|
||||
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: 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']}'
|
||||
.toTitleCase());
|
||||
},
|
||||
),
|
||||
),
|
||||
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,
|
||||
'${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(actions: actions)
|
||||
];
|
||||
} else {
|
||||
return [
|
||||
AddCardGeneric(
|
||||
title: 'Add an Action!',
|
||||
description:
|
||||
'Click here to create an exercise template (sets and reps, etc) for your activity!',
|
||||
action: () {
|
||||
print('teset');
|
||||
})
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final Activity activity = widget.activity;
|
||||
ActivityTimerModel atm =
|
||||
Provider.of<ActivityTimerModel>(context, listen: false);
|
||||
final Session session = widget.session;
|
||||
|
||||
return FutureBuilder<List>(
|
||||
future: ActionsDao(Provider.of<AppDatabase>(context))
|
||||
.fromActivity(activity),
|
||||
.fromActivity(activity, session),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
List actions = snapshot.data!;
|
||||
atm.setup(activity, actions);
|
||||
List<Action> actions = snapshot.data! as List<Action>;
|
||||
|
||||
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.upload_outlined),
|
||||
// label: Text('Upload Media'),
|
||||
// onPressed: () {},
|
||||
// ),
|
||||
FloatingActionButton.extended(
|
||||
icon: const Icon(Icons.note_add_outlined),
|
||||
label: Text('Add Note'),
|
||||
onPressed: () {},
|
||||
),
|
||||
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: () {},
|
||||
),
|
||||
]),
|
||||
body: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
return PopScope(
|
||||
canPop: false,
|
||||
onPopInvokedWithResult: (didPop, result) async {
|
||||
if (didPop) {
|
||||
return;
|
||||
}
|
||||
final bool shouldPop = await showBackDialog(context) ?? false;
|
||||
if (context.mounted && shouldPop) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
},
|
||||
child: Scaffold(
|
||||
floatingActionButtonLocation: ExpandableFab.location,
|
||||
floatingActionButton: ExpandableFab(
|
||||
key: _fabKey,
|
||||
distance: 70,
|
||||
type: ExpandableFabType.up,
|
||||
overlayStyle: ExpandableFabOverlayStyle(
|
||||
color: Colors.black.withOpacity(0.5),
|
||||
blur: 10,
|
||||
),
|
||||
onOpen: () {
|
||||
// pause the activity on open
|
||||
ActionTimer at =
|
||||
Provider.of<ActionTimer>(context, listen: false);
|
||||
if (at.started) at.pause();
|
||||
},
|
||||
children: [
|
||||
// FloatingActionButton.extended(
|
||||
// icon: const Icon(Icons.upload_outlined),
|
||||
// label: Text('Upload Media'),
|
||||
// onPressed: () {},
|
||||
// ),
|
||||
FloatingActionButton.extended(
|
||||
icon: const Icon(Icons.done_all_outlined),
|
||||
label: Text('Edit Action'),
|
||||
onPressed: () {
|
||||
showEditorSheet(
|
||||
context,
|
||||
ActivityActionEditor(
|
||||
session: session,
|
||||
activity: activity,
|
||||
action: actions.first,
|
||||
callback: resetState));
|
||||
},
|
||||
),
|
||||
FloatingActionButton.extended(
|
||||
icon: const Icon(Icons.note_add_outlined),
|
||||
label: Text('Add Note'),
|
||||
onPressed: () {},
|
||||
),
|
||||
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: () {},
|
||||
),
|
||||
]),
|
||||
body: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
AppBar(
|
||||
titleSpacing: 0,
|
||||
centerTitle: true,
|
||||
@ -215,7 +180,7 @@ class _ActivityViewState extends State<ActivityView> {
|
||||
List<ActivityMuscle>>(
|
||||
icon: Icon(Icons.person),
|
||||
text: 'Muscles used',
|
||||
object: activity_muscle(activity))
|
||||
object: activityMuscle(activity))
|
||||
])),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
@ -272,94 +237,42 @@ class _ActivityViewState extends State<ActivityView> {
|
||||
fontWeight: FontWeight.bold),
|
||||
'Media:')),
|
||||
ActivityViewMedia(activity: activity),
|
||||
const Padding(
|
||||
padding: EdgeInsets.fromLTRB(15, 30, 0, 10),
|
||||
child: Text(
|
||||
textAlign: TextAlign.left,
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold),
|
||||
'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)),
|
||||
// ),
|
||||
// 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: 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']}'
|
||||
// .toTitleCase());
|
||||
// },
|
||||
// ),
|
||||
// ),
|
||||
// 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,
|
||||
// '${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(actions: actions),
|
||||
] +
|
||||
action(actions)));
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(15, 20, 5, 0),
|
||||
child: Row(children: [
|
||||
Expanded(
|
||||
child: const Text(
|
||||
textAlign: TextAlign.left,
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold),
|
||||
'Actions')),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
showGenericSheet(
|
||||
context,
|
||||
Column(children: [
|
||||
ActivityActionView(
|
||||
session: session,
|
||||
activity: activity,
|
||||
actions: actions,
|
||||
callback: resetState,
|
||||
resetOnLoad: false)
|
||||
]),
|
||||
Theme.of(context).colorScheme.surface);
|
||||
},
|
||||
icon: Icon(Icons.expand),
|
||||
alignment: Alignment.bottomCenter,
|
||||
)
|
||||
])),
|
||||
ActivityActionView(
|
||||
session: session,
|
||||
activity: activity,
|
||||
actions: actions,
|
||||
callback: resetState)
|
||||
])));
|
||||
// ] +
|
||||
// action(actions, context)));
|
||||
} else {
|
||||
return Container(
|
||||
alignment: Alignment.center,
|
||||
|
@ -21,7 +21,7 @@ class ActivityViewCategories<T extends List<Enum>> extends StatelessWidget {
|
||||
itemCount: object.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(right: 10),
|
||||
padding: EdgeInsets.only(right: 5),
|
||||
child: ActionChip(
|
||||
visualDensity: VisualDensity.compact,
|
||||
avatar: icon,
|
||||
|
@ -1,4 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:sendtrain/providers/action_timer.dart';
|
||||
|
||||
Future showGenericDialog(dynamic object, BuildContext parentContext) {
|
||||
return showGeneralDialog(
|
||||
@ -55,3 +57,51 @@ Future showUpdateDialog(String title, String content, BuildContext context,
|
||||
[Function? callback]) {
|
||||
return showCrudDialog(title, content, context, callback);
|
||||
}
|
||||
|
||||
// TODO - factor out, this should be more generic
|
||||
Future<bool?> showBackDialog(BuildContext context) async {
|
||||
ActionTimer at = Provider.of<ActionTimer>(context, listen: false);
|
||||
|
||||
if (at.pending || at.complete) {
|
||||
await at.clear();
|
||||
return true;
|
||||
} else {
|
||||
return await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: const Text('Are you sure?'),
|
||||
content: const Text(
|
||||
'Leaving will stop the current activity. Are you sure you want to leave?',
|
||||
),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
style: TextButton.styleFrom(
|
||||
textStyle: Theme.of(context).textTheme.labelLarge,
|
||||
),
|
||||
child: const Text('Nevermind'),
|
||||
onPressed: () async {
|
||||
Navigator.pop(context, false);
|
||||
},
|
||||
),
|
||||
TextButton(
|
||||
style: TextButton.styleFrom(
|
||||
textStyle: Theme.of(context).textTheme.labelLarge,
|
||||
),
|
||||
child: const Text('Leave'),
|
||||
onPressed: () async {
|
||||
ActionTimer at =
|
||||
Provider.of<ActionTimer>(context, listen: false);
|
||||
await at.clear();
|
||||
|
||||
if (context.mounted) {
|
||||
Navigator.pop(context, true);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
32
lib/widgets/generic/elements/form_drop_down.dart
Normal file
32
lib/widgets/generic/elements/form_drop_down.dart
Normal file
@ -0,0 +1,32 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:sendtrain/helpers/widget_helpers.dart';
|
||||
|
||||
class FormDropDown extends StatelessWidget {
|
||||
const FormDropDown(
|
||||
{super.key,
|
||||
required this.title,
|
||||
required this.entries,
|
||||
required this.controller});
|
||||
|
||||
final List<DropdownMenuEntry> entries;
|
||||
final String title;
|
||||
final TextEditingController controller;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return formItemWrapper(
|
||||
DropdownMenu(
|
||||
leadingIcon: Icon(Icons.select_all_rounded),
|
||||
initialSelection: controller.text,
|
||||
controller: controller,
|
||||
expandedInsets: EdgeInsets.zero,
|
||||
inputDecorationTheme: InputDecorationTheme(
|
||||
filled: true,
|
||||
border: OutlineInputBorder(
|
||||
borderSide: BorderSide.none,
|
||||
borderRadius: BorderRadius.circular(12))),
|
||||
label: Text(title),
|
||||
dropdownMenuEntries: entries),
|
||||
EdgeInsets.fromLTRB(10, 5, 10, 5));
|
||||
}
|
||||
}
|
@ -82,33 +82,35 @@ class _FormSearchInputState extends State<FormSearchInput> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SearchAnchor(
|
||||
isFullScreen: false,
|
||||
builder: (BuildContext context, SearchController controller) {
|
||||
return FormTextInput(
|
||||
controller: widget.controller,
|
||||
title: widget.title ?? "",
|
||||
icon: Icon(Icons.search_rounded),
|
||||
maxLines: 2,
|
||||
requiresValidation: false,
|
||||
onTap: () {
|
||||
controller.openView();
|
||||
});
|
||||
}, suggestionsBuilder:
|
||||
return FormTextInput(
|
||||
controller: widget.controller,
|
||||
title: widget.title ?? "",
|
||||
icon: Icon(Icons.search_rounded),
|
||||
maxLines: 2,
|
||||
requiresValidation: false,
|
||||
onTap: () {
|
||||
controller.openView();
|
||||
});
|
||||
},
|
||||
suggestionsBuilder:
|
||||
(BuildContext context, SearchController controller) async {
|
||||
final List<Suggestion>? options =
|
||||
(await debouncer.process(controller.text))?.toList();
|
||||
if (options == null) {
|
||||
return _lastOptions;
|
||||
}
|
||||
_lastOptions = List<ListTile>.generate(options.length, (int index) {
|
||||
final Suggestion item = options[index];
|
||||
final dynamic content = item.content;
|
||||
return service.resultWidget(content, () {
|
||||
resultHandler(content, service);
|
||||
controller.closeView(null);
|
||||
});
|
||||
});
|
||||
final List<Suggestion>? options =
|
||||
(await debouncer.process(controller.text))?.toList();
|
||||
if (options == null) {
|
||||
return _lastOptions;
|
||||
}
|
||||
_lastOptions = List<ListTile>.generate(options.length, (int index) {
|
||||
final Suggestion item = options[index];
|
||||
final dynamic content = item.content;
|
||||
return service.resultWidget(content, () {
|
||||
resultHandler(content, service);
|
||||
controller.closeView(null);
|
||||
});
|
||||
});
|
||||
|
||||
return _lastOptions;
|
||||
});
|
||||
return _lastOptions;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
enum InputTypes { text, number }
|
||||
|
||||
class FormTextInput extends StatelessWidget {
|
||||
const FormTextInput(
|
||||
@ -9,7 +12,10 @@ class FormTextInput extends StatelessWidget {
|
||||
this.maxLines,
|
||||
this.minLines,
|
||||
this.onTap,
|
||||
this.requiresValidation=true});
|
||||
this.requiresValidation = true,
|
||||
this.type = InputTypes.text,
|
||||
this.hint,
|
||||
this.validations});
|
||||
|
||||
final TextEditingController controller;
|
||||
final String title;
|
||||
@ -18,12 +24,25 @@ class FormTextInput extends StatelessWidget {
|
||||
final Icon? icon;
|
||||
final dynamic onTap;
|
||||
final bool requiresValidation;
|
||||
final InputTypes type;
|
||||
final String? hint;
|
||||
final Function? validations;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final Map params = {};
|
||||
if (type == InputTypes.number) {
|
||||
params['keyboardType'] = TextInputType.number;
|
||||
params['inputFormatters'] = <TextInputFormatter>[
|
||||
FilteringTextInputFormatter.digitsOnly
|
||||
];
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(top: 10, bottom: 10),
|
||||
child: TextFormField(
|
||||
keyboardType: params['keyboardType'] ?? TextInputType.text,
|
||||
inputFormatters: params['inputFormatters'] ?? [],
|
||||
minLines: minLines ?? 1,
|
||||
maxLines: maxLines ?? 1,
|
||||
controller: controller,
|
||||
@ -34,6 +53,7 @@ class FormTextInput extends StatelessWidget {
|
||||
borderSide: BorderSide.none,
|
||||
borderRadius: BorderRadius.circular(12)),
|
||||
labelText: title,
|
||||
hintText: hint ?? '',
|
||||
),
|
||||
validator: (String? value) {
|
||||
if (requiresValidation == true) {
|
||||
@ -41,9 +61,11 @@ class FormTextInput extends StatelessWidget {
|
||||
return 'Please enter some text';
|
||||
}
|
||||
|
||||
if (value.length < 3) {
|
||||
return 'Please enter a minimum of 3 characters';
|
||||
}
|
||||
if (validations != null) validations!(value);
|
||||
|
||||
// if (value.length < 3) {
|
||||
// return 'Please enter a minimum of 3 characters';
|
||||
// }
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
@ -1,6 +1,4 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
@ -8,7 +6,6 @@ import 'package:sendtrain/daos/media_items_dao.dart';
|
||||
import 'package:sendtrain/database/database.dart';
|
||||
import 'package:sendtrain/helpers/widget_helpers.dart';
|
||||
import 'package:sendtrain/widgets/builders/dialogs.dart';
|
||||
import 'package:video_player/video_player.dart';
|
||||
|
||||
class MediaCard extends StatelessWidget {
|
||||
const MediaCard(
|
||||
|
@ -51,6 +51,8 @@ dependencies:
|
||||
mime: ^2.0.0
|
||||
video_player: ^2.9.2
|
||||
dart_casing: ^3.0.1
|
||||
collection: ^1.18.0
|
||||
flutter_sound: ^9.23.1
|
||||
|
||||
flutter_launcher_name:
|
||||
name: "SendTrain"
|
||||
@ -86,6 +88,7 @@ flutter:
|
||||
# - images/a_dot_burr.jpeg
|
||||
# - images/a_dot_ham.jpeg
|
||||
assets:
|
||||
- assets/audio/
|
||||
- assets/images/
|
||||
- assets/exercises.json
|
||||
|
||||
|
@ -23,6 +23,21 @@ import 'schema_v7.dart' as v7;
|
||||
import 'schema_v8.dart' as v8;
|
||||
import 'schema_v9.dart' as v9;
|
||||
import 'schema_v20.dart' as v20;
|
||||
import 'schema_v21.dart' as v21;
|
||||
import 'schema_v22.dart' as v22;
|
||||
import 'schema_v23.dart' as v23;
|
||||
import 'schema_v24.dart' as v24;
|
||||
import 'schema_v25.dart' as v25;
|
||||
import 'schema_v26.dart' as v26;
|
||||
import 'schema_v27.dart' as v27;
|
||||
import 'schema_v28.dart' as v28;
|
||||
import 'schema_v29.dart' as v29;
|
||||
import 'schema_v30.dart' as v30;
|
||||
import 'schema_v31.dart' as v31;
|
||||
import 'schema_v32.dart' as v32;
|
||||
import 'schema_v33.dart' as v33;
|
||||
import 'schema_v34.dart' as v34;
|
||||
import 'schema_v35.dart' as v35;
|
||||
|
||||
class GeneratedHelper implements SchemaInstantiationHelper {
|
||||
@override
|
||||
@ -68,6 +83,36 @@ class GeneratedHelper implements SchemaInstantiationHelper {
|
||||
return v9.DatabaseAtV9(db);
|
||||
case 20:
|
||||
return v20.DatabaseAtV20(db);
|
||||
case 21:
|
||||
return v21.DatabaseAtV21(db);
|
||||
case 22:
|
||||
return v22.DatabaseAtV22(db);
|
||||
case 23:
|
||||
return v23.DatabaseAtV23(db);
|
||||
case 24:
|
||||
return v24.DatabaseAtV24(db);
|
||||
case 25:
|
||||
return v25.DatabaseAtV25(db);
|
||||
case 26:
|
||||
return v26.DatabaseAtV26(db);
|
||||
case 27:
|
||||
return v27.DatabaseAtV27(db);
|
||||
case 28:
|
||||
return v28.DatabaseAtV28(db);
|
||||
case 29:
|
||||
return v29.DatabaseAtV29(db);
|
||||
case 30:
|
||||
return v30.DatabaseAtV30(db);
|
||||
case 31:
|
||||
return v31.DatabaseAtV31(db);
|
||||
case 32:
|
||||
return v32.DatabaseAtV32(db);
|
||||
case 33:
|
||||
return v33.DatabaseAtV33(db);
|
||||
case 34:
|
||||
return v34.DatabaseAtV34(db);
|
||||
case 35:
|
||||
return v35.DatabaseAtV35(db);
|
||||
default:
|
||||
throw MissingSchemaException(version, versions);
|
||||
}
|
||||
@ -93,6 +138,21 @@ class GeneratedHelper implements SchemaInstantiationHelper {
|
||||
17,
|
||||
18,
|
||||
19,
|
||||
20
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23,
|
||||
24,
|
||||
25,
|
||||
26,
|
||||
27,
|
||||
28,
|
||||
29,
|
||||
30,
|
||||
31,
|
||||
32,
|
||||
33,
|
||||
34,
|
||||
35
|
||||
];
|
||||
}
|
||||
|
2604
test/drift/sendtrain/generated/schema_v21.dart
Normal file
2604
test/drift/sendtrain/generated/schema_v21.dart
Normal file
File diff suppressed because it is too large
Load Diff
2639
test/drift/sendtrain/generated/schema_v22.dart
Normal file
2639
test/drift/sendtrain/generated/schema_v22.dart
Normal file
File diff suppressed because it is too large
Load Diff
2669
test/drift/sendtrain/generated/schema_v23.dart
Normal file
2669
test/drift/sendtrain/generated/schema_v23.dart
Normal file
File diff suppressed because it is too large
Load Diff
2670
test/drift/sendtrain/generated/schema_v24.dart
Normal file
2670
test/drift/sendtrain/generated/schema_v24.dart
Normal file
File diff suppressed because it is too large
Load Diff
2702
test/drift/sendtrain/generated/schema_v25.dart
Normal file
2702
test/drift/sendtrain/generated/schema_v25.dart
Normal file
File diff suppressed because it is too large
Load Diff
2701
test/drift/sendtrain/generated/schema_v26.dart
Normal file
2701
test/drift/sendtrain/generated/schema_v26.dart
Normal file
File diff suppressed because it is too large
Load Diff
2701
test/drift/sendtrain/generated/schema_v27.dart
Normal file
2701
test/drift/sendtrain/generated/schema_v27.dart
Normal file
File diff suppressed because it is too large
Load Diff
2701
test/drift/sendtrain/generated/schema_v28.dart
Normal file
2701
test/drift/sendtrain/generated/schema_v28.dart
Normal file
File diff suppressed because it is too large
Load Diff
2702
test/drift/sendtrain/generated/schema_v29.dart
Normal file
2702
test/drift/sendtrain/generated/schema_v29.dart
Normal file
File diff suppressed because it is too large
Load Diff
2702
test/drift/sendtrain/generated/schema_v30.dart
Normal file
2702
test/drift/sendtrain/generated/schema_v30.dart
Normal file
File diff suppressed because it is too large
Load Diff
2702
test/drift/sendtrain/generated/schema_v31.dart
Normal file
2702
test/drift/sendtrain/generated/schema_v31.dart
Normal file
File diff suppressed because it is too large
Load Diff
2702
test/drift/sendtrain/generated/schema_v32.dart
Normal file
2702
test/drift/sendtrain/generated/schema_v32.dart
Normal file
File diff suppressed because it is too large
Load Diff
2702
test/drift/sendtrain/generated/schema_v33.dart
Normal file
2702
test/drift/sendtrain/generated/schema_v33.dart
Normal file
File diff suppressed because it is too large
Load Diff
2733
test/drift/sendtrain/generated/schema_v34.dart
Normal file
2733
test/drift/sendtrain/generated/schema_v34.dart
Normal file
File diff suppressed because it is too large
Load Diff
2733
test/drift/sendtrain/generated/schema_v35.dart
Normal file
2733
test/drift/sendtrain/generated/schema_v35.dart
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user