Compare commits
28
Commits
e36d2a837a
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bdc4fee8c2 | ||
|
|
8dcffcca11 | ||
|
|
2eda797375 | ||
|
|
9f5fb0d1ad | ||
|
|
23663f484b | ||
|
|
60bc571987 | ||
|
|
0cf62ec4b4 | ||
|
|
2288cba78e | ||
|
|
1027439848 | ||
|
|
6012a1541e | ||
|
|
d9a5599a4b | ||
|
|
0b0ca884bc | ||
|
|
8ec531c0ea | ||
|
|
6917754933 | ||
|
|
ebca90e69a | ||
|
|
7ead6ba631 | ||
|
|
a034c16160 | ||
|
|
acab37eb60 | ||
|
|
fec4eaaf92 | ||
|
|
ecc9aa3abc | ||
|
|
95701c73a6 | ||
|
|
2eb5d8f171 | ||
|
|
6e0b1263ba | ||
|
|
9fc5fb5d22 | ||
|
|
32826abcea | ||
|
|
2206720810 | ||
|
|
48f716cdb0 | ||
|
|
e78788d67a |
@@ -1,6 +1,7 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<uses-feature android:name="android.hardware.location.network" android:required="false" />
|
<uses-feature android:name="android.hardware.location.network" android:required="false" />
|
||||||
<uses-feature android:name="android.hardware.location.gps" android:required="false" />
|
<uses-feature android:name="android.hardware.location.gps" android:required="false" />
|
||||||
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
<application
|
<application
|
||||||
android:label="sendtrain"
|
android:label="sendtrain"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
+22617
File diff suppressed because it is too large
Load Diff
@@ -15,7 +15,7 @@ class ActionsDao extends DatabaseAccessor<AppDatabase> with _$ActionsDaoMixin {
|
|||||||
return await (select(actions)..where((action) => action.id.equals(id) )).getSingle();
|
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(
|
final result = select(db.activityActions).join(
|
||||||
[
|
[
|
||||||
innerJoin(
|
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())
|
final actions = (await result.get())
|
||||||
.map((e) => e.readTable(db.actions))
|
.map((e) => e.readTable(db.actions))
|
||||||
@@ -32,4 +33,32 @@ class ActionsDao extends DatabaseAccessor<AppDatabase> with _$ActionsDaoMixin {
|
|||||||
|
|
||||||
return actions;
|
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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,13 @@ class ActivitiesDao extends DatabaseAccessor<AppDatabase>
|
|||||||
|
|
||||||
Future remove(Activity activity) => delete(activities).delete(activity);
|
Future remove(Activity activity) => delete(activities).delete(activity);
|
||||||
|
|
||||||
|
Future<List<Activity>> contains(value) async {
|
||||||
|
return (select(activities)
|
||||||
|
..where((t) =>
|
||||||
|
t.title.contains(value) | t.description.contains(value) | t.category.contains(value)))
|
||||||
|
.get();
|
||||||
|
}
|
||||||
|
|
||||||
Future<List<Activity>> activitiesFromSession(int id) async {
|
Future<List<Activity>> activitiesFromSession(int id) async {
|
||||||
final result = select(db.sessionActivities).join(
|
final result = select(db.sessionActivities).join(
|
||||||
[
|
[
|
||||||
@@ -46,9 +53,8 @@ class ActivitiesDao extends DatabaseAccessor<AppDatabase>
|
|||||||
],
|
],
|
||||||
)..where(db.sessionActivities.sessionId.equals(id));
|
)..where(db.sessionActivities.sessionId.equals(id));
|
||||||
|
|
||||||
return query.watch().map((rows){
|
return query.watch().map((rows) {
|
||||||
final activities =
|
final activities = (rows).map((e) => e.readTable(db.activities)).toList();
|
||||||
(rows).map((e) => e.readTable(db.activities)).toList();
|
|
||||||
|
|
||||||
return activities;
|
return activities;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ class ActivityActionsDao extends DatabaseAccessor<AppDatabase> with _$ActivityAc
|
|||||||
Future insert(ActivityAction activityAction) => into(activityActions).insert(activityAction);
|
Future insert(ActivityAction activityAction) => into(activityActions).insert(activityAction);
|
||||||
Future replace(ActivityAction activityAction) => update(activityActions).replace(activityAction);
|
Future replace(ActivityAction activityAction) => update(activityActions).replace(activityAction);
|
||||||
Future remove(ActivityAction activityAction) => delete(activityActions).delete(activityAction);
|
Future remove(ActivityAction activityAction) => delete(activityActions).delete(activityAction);
|
||||||
|
Future createOrUpdate(ActivityActionsCompanion activityAction) => into(activityActions).insertOnConflictUpdate(activityAction);
|
||||||
|
|
||||||
// Future<List<ActivityAction>> all() async {
|
// Future<List<ActivityAction>> all() async {
|
||||||
// return await select(activityActions).get();
|
// return await select(activityActions).get();
|
||||||
|
|||||||
@@ -6,5 +6,6 @@ part of 'activity_actions_dao.dart';
|
|||||||
mixin _$ActivityActionsDaoMixin on DatabaseAccessor<AppDatabase> {
|
mixin _$ActivityActionsDaoMixin on DatabaseAccessor<AppDatabase> {
|
||||||
$ActivitiesTable get activities => attachedDatabase.activities;
|
$ActivitiesTable get activities => attachedDatabase.activities;
|
||||||
$ActionsTable get actions => attachedDatabase.actions;
|
$ActionsTable get actions => attachedDatabase.actions;
|
||||||
|
$SessionsTable get sessions => attachedDatabase.sessions;
|
||||||
$ActivityActionsTable get activityActions => attachedDatabase.activityActions;
|
$ActivityActionsTable get activityActions => attachedDatabase.activityActions;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ class MediaItemsDao extends DatabaseAccessor<AppDatabase>
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future remove(MediaItem mediaItem) => delete(mediaItems).delete(mediaItem);
|
||||||
Future removeAll(Iterable<int> mediaItemIds) {
|
Future removeAll(Iterable<int> mediaItemIds) {
|
||||||
return (delete(mediaItems)..where((table) => table.id.isIn(mediaItemIds)))
|
return (delete(mediaItems)..where((table) => table.id.isIn(mediaItemIds)))
|
||||||
.go();
|
.go();
|
||||||
|
|||||||
@@ -4,15 +4,21 @@ import 'package:sendtrain/database/database.dart';
|
|||||||
part 'session_activities_dao.g.dart';
|
part 'session_activities_dao.g.dart';
|
||||||
|
|
||||||
@DriftAccessor(tables: [SessionActivities])
|
@DriftAccessor(tables: [SessionActivities])
|
||||||
class SessionActivitiesDao extends DatabaseAccessor<AppDatabase> with _$SessionActivitiesDaoMixin {
|
class SessionActivitiesDao extends DatabaseAccessor<AppDatabase>
|
||||||
|
with _$SessionActivitiesDaoMixin {
|
||||||
SessionActivitiesDao(super.db);
|
SessionActivitiesDao(super.db);
|
||||||
|
|
||||||
|
Future createOrUpdate(SessionActivitiesCompanion sessionActivity) =>
|
||||||
|
into(sessionActivities).insertOnConflictUpdate(sessionActivity);
|
||||||
|
|
||||||
Future<List<SessionActivity>> all() async {
|
Future<List<SessionActivity>> all() async {
|
||||||
return await select(sessionActivities).get();
|
return await select(sessionActivities).get();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<SessionActivity> find(int id) async {
|
Future<SessionActivity> find(int id) async {
|
||||||
return await (select(sessionActivities)..where((sessionActivity) => sessionActivity.id.equals(id) )).getSingle();
|
return await (select(sessionActivities)
|
||||||
|
..where((sessionActivity) => sessionActivity.id.equals(id)))
|
||||||
|
.getSingle();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<SessionActivity>> fromSessionId(int id) async {
|
Future<List<SessionActivity>> fromSessionId(int id) async {
|
||||||
@@ -21,4 +27,16 @@ class SessionActivitiesDao extends DatabaseAccessor<AppDatabase> with _$SessionA
|
|||||||
|
|
||||||
return result.get();
|
return result.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future remove(SessionActivity sessionActivity) =>
|
||||||
|
delete(sessionActivities).delete(sessionActivity);
|
||||||
|
|
||||||
|
Future removeAssociation(int activityId, int sessionId) {
|
||||||
|
return (delete(sessionActivities)
|
||||||
|
..where((t) =>
|
||||||
|
t.sessionId.equals(sessionId) & t.activityId.equals(activityId)))
|
||||||
|
.go();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// return (delete(todos)..where((t) => t.id.isSmallerThanValue(10))).go();
|
||||||
@@ -8,7 +8,8 @@ class SessionsDao extends DatabaseAccessor<AppDatabase> with _$SessionsDaoMixin
|
|||||||
SessionsDao(super.db);
|
SessionsDao(super.db);
|
||||||
|
|
||||||
Future<Session> find(int id) => (select(sessions)..where((session) => session.id.equals(id) )).getSingle();
|
Future<Session> find(int id) => (select(sessions)..where((session) => session.id.equals(id) )).getSingle();
|
||||||
Future<List<Session>> all() => (select(sessions)..orderBy([(session) => OrderingTerm(expression: session.createdAt, mode: OrderingMode.desc)])).get();
|
Stream<Session> watchSession(int id) => (select(sessions)..where((session) => session.id.equals(id) )).watchSingle();
|
||||||
|
Future<List<Session>> all() => select(sessions).get();
|
||||||
Stream<List<Session>> watch() => select(sessions).watch();
|
Stream<List<Session>> watch() => select(sessions).watch();
|
||||||
Future createOrUpdate(SessionsCompanion session) => into(sessions).insertOnConflictUpdate(session);
|
Future createOrUpdate(SessionsCompanion session) => into(sessions).insertOnConflictUpdate(session);
|
||||||
Future replace(Session session) => update(sessions).replace(session);
|
Future replace(Session session) => update(sessions).replace(session);
|
||||||
|
|||||||
+91
-23
@@ -35,7 +35,7 @@ class AppDatabase extends _$AppDatabase {
|
|||||||
AppDatabase() : super(_openConnection());
|
AppDatabase() : super(_openConnection());
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get schemaVersion => 7;
|
int get schemaVersion => 35;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
MigrationStrategy get migration {
|
MigrationStrategy get migration {
|
||||||
@@ -66,6 +66,7 @@ class Sessions extends Table {
|
|||||||
TextColumn get title => text().withLength(min: 3, max: 32)();
|
TextColumn get title => text().withLength(min: 3, max: 32)();
|
||||||
TextColumn get content => text().named('body')();
|
TextColumn get content => text().named('body')();
|
||||||
TextColumn get status => textEnum<SessionStatus>()();
|
TextColumn get status => textEnum<SessionStatus>()();
|
||||||
|
TextColumn get achievements => text().nullable()();
|
||||||
TextColumn get address => text().withLength(min: 3, max: 256).nullable()();
|
TextColumn get address => text().withLength(min: 3, max: 256).nullable()();
|
||||||
DateTimeColumn get date => dateTime().nullable()();
|
DateTimeColumn get date => dateTime().nullable()();
|
||||||
DateTimeColumn get createdAt =>
|
DateTimeColumn get createdAt =>
|
||||||
@@ -74,11 +75,12 @@ class Sessions extends Table {
|
|||||||
|
|
||||||
class SessionActivities extends Table {
|
class SessionActivities extends Table {
|
||||||
IntColumn get id => integer().autoIncrement()();
|
IntColumn get id => integer().autoIncrement()();
|
||||||
IntColumn get sessionId => integer().references(Sessions, #id, onDelete: KeyAction.cascade)();
|
IntColumn get sessionId =>
|
||||||
IntColumn get activityId => integer().references(Activities, #id, onDelete: KeyAction.cascade)();
|
integer().references(Sessions, #id, onDelete: KeyAction.cascade)();
|
||||||
|
IntColumn get activityId =>
|
||||||
|
integer().references(Activities, #id, onDelete: KeyAction.cascade)();
|
||||||
IntColumn get position => integer()();
|
IntColumn get position => integer()();
|
||||||
TextColumn get results => text().nullable()();
|
TextColumn get results => text().nullable()();
|
||||||
TextColumn get achievements => text().nullable()();
|
|
||||||
DateTimeColumn get createdAt =>
|
DateTimeColumn get createdAt =>
|
||||||
dateTime().withDefault(Variable(DateTime.now()))();
|
dateTime().withDefault(Variable(DateTime.now()))();
|
||||||
}
|
}
|
||||||
@@ -87,23 +89,66 @@ enum ActivityCategories { fundamentals, conditioning, advanced, custom, pro }
|
|||||||
|
|
||||||
enum ActivityType {
|
enum ActivityType {
|
||||||
strength,
|
strength,
|
||||||
power,
|
stretching,
|
||||||
conditioning,
|
plyometrics,
|
||||||
hypertrophy,
|
strongman,
|
||||||
endurance,
|
powerlifting,
|
||||||
stability,
|
cardio,
|
||||||
mobility,
|
olympicWeightlifting
|
||||||
flexibility,
|
}
|
||||||
rehabilitation,
|
|
||||||
technical
|
enum ActivityLevel { beginner, intermediate, expert }
|
||||||
|
|
||||||
|
enum ActivityMechanic { compound, isolation }
|
||||||
|
|
||||||
|
enum ActivityEquipment {
|
||||||
|
bodyOnly,
|
||||||
|
machine,
|
||||||
|
other,
|
||||||
|
foamRoll,
|
||||||
|
kettlebells,
|
||||||
|
dumbbell,
|
||||||
|
cable,
|
||||||
|
barbell,
|
||||||
|
bands,
|
||||||
|
medicineBall,
|
||||||
|
exerciseBall,
|
||||||
|
eZCurlBar
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ActivityMuscle {
|
||||||
|
abdominals,
|
||||||
|
hamstrings,
|
||||||
|
calves,
|
||||||
|
shoulders,
|
||||||
|
adductors,
|
||||||
|
glutes,
|
||||||
|
quadriceps,
|
||||||
|
biceps,
|
||||||
|
forearms,
|
||||||
|
abductors,
|
||||||
|
triceps,
|
||||||
|
chest,
|
||||||
|
lowerBack,
|
||||||
|
traps,
|
||||||
|
middleBack,
|
||||||
|
lats,
|
||||||
|
neck
|
||||||
}
|
}
|
||||||
|
|
||||||
class Activities extends Table {
|
class Activities extends Table {
|
||||||
IntColumn get id => integer().autoIncrement()();
|
IntColumn get id => integer().autoIncrement()();
|
||||||
TextColumn get title => text().withLength(min: 3, max: 32)();
|
TextColumn get title => text().withLength(min: 3, max: 100)();
|
||||||
TextColumn get type => textEnum<ActivityType>()();
|
TextColumn get type => textEnum<ActivityType>().nullable()();
|
||||||
TextColumn get description => text().named('body')();
|
TextColumn get description => text().named('body').nullable()();
|
||||||
TextColumn get category => textEnum<ActivityCategories>()();
|
TextColumn get category => textEnum<ActivityCategories>().nullable()();
|
||||||
|
// from exercises.json
|
||||||
|
TextColumn get force => text().nullable()();
|
||||||
|
TextColumn get level => textEnum<ActivityLevel>().nullable()();
|
||||||
|
TextColumn get mechanic => textEnum<ActivityMechanic>().nullable()();
|
||||||
|
TextColumn get equipment => textEnum<ActivityEquipment>().nullable()();
|
||||||
|
TextColumn get primaryMuscles => textEnum<ActivityMuscle>().nullable()();
|
||||||
|
TextColumn get secondaryMuscles => textEnum<ActivityMuscle>().nullable()();
|
||||||
DateTimeColumn get createdAt =>
|
DateTimeColumn get createdAt =>
|
||||||
dateTime().withDefault(Variable(DateTime.now()))();
|
dateTime().withDefault(Variable(DateTime.now()))();
|
||||||
}
|
}
|
||||||
@@ -111,16 +156,38 @@ class Activities extends Table {
|
|||||||
class ActivityActions extends Table {
|
class ActivityActions extends Table {
|
||||||
IntColumn get id => integer().autoIncrement()();
|
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 actionId =>
|
||||||
|
integer().references(Actions, #id, onDelete: KeyAction.cascade)();
|
||||||
|
IntColumn get sessionId => integer().references(Sessions, #id, onDelete: KeyAction.cascade)();
|
||||||
IntColumn get position => integer()();
|
IntColumn get position => integer()();
|
||||||
DateTimeColumn get createdAt =>
|
DateTimeColumn get createdAt =>
|
||||||
dateTime().withDefault(Variable(DateTime.now()))();
|
dateTime().withDefault(Variable(DateTime.now()))();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum RepType { time, count }
|
||||||
|
|
||||||
|
enum ActionStatus { pending, started, paused, complete }
|
||||||
|
|
||||||
class Actions extends Table {
|
class Actions extends Table {
|
||||||
IntColumn get id => integer().autoIncrement()();
|
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')();
|
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()();
|
TextColumn get set => text()();
|
||||||
DateTimeColumn get createdAt =>
|
DateTimeColumn get createdAt =>
|
||||||
dateTime().withDefault(Variable(DateTime.now()))();
|
dateTime().withDefault(Variable(DateTime.now()))();
|
||||||
@@ -136,18 +203,19 @@ class ObjectMediaItems extends Table {
|
|||||||
IntColumn get id => integer().autoIncrement()();
|
IntColumn get id => integer().autoIncrement()();
|
||||||
IntColumn get objectId => integer()();
|
IntColumn get objectId => integer()();
|
||||||
TextColumn get objectType => textEnum<ObjectType>()();
|
TextColumn get objectType => textEnum<ObjectType>()();
|
||||||
IntColumn get mediaId => integer().references(MediaItems, #id, onDelete: KeyAction.cascade)();
|
IntColumn get mediaId =>
|
||||||
|
integer().references(MediaItems, #id, onDelete: KeyAction.cascade)();
|
||||||
DateTimeColumn get createdAt =>
|
DateTimeColumn get createdAt =>
|
||||||
dateTime().withDefault(Variable(DateTime.now()))();
|
dateTime().withDefault(Variable(DateTime.now()))();
|
||||||
}
|
}
|
||||||
|
|
||||||
enum MediaType { youtube, image, location }
|
enum MediaType { youtube, image, location, localImage, localVideo }
|
||||||
|
|
||||||
class MediaItems extends Table {
|
class MediaItems extends Table {
|
||||||
IntColumn get id => integer().autoIncrement()();
|
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')();
|
TextColumn get description => text().named('body')();
|
||||||
TextColumn get reference => text().withLength(min: 3, max: 256)();
|
TextColumn get reference => text()();
|
||||||
TextColumn get type => textEnum<MediaType>()();
|
TextColumn get type => textEnum<MediaType>()();
|
||||||
DateTimeColumn get createdAt =>
|
DateTimeColumn get createdAt =>
|
||||||
dateTime().withDefault(Variable(DateTime.now()))();
|
dateTime().withDefault(Variable(DateTime.now()))();
|
||||||
|
|||||||
+1615
-169
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
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
+192
-89
@@ -1,5 +1,7 @@
|
|||||||
|
import 'dart:convert';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
import 'package:dart_casing/dart_casing.dart';
|
||||||
|
import 'package:flutter/services.dart' as root_bundle;
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:sendtrain/database/database.dart';
|
import 'package:sendtrain/database/database.dart';
|
||||||
|
|
||||||
@@ -7,24 +9,29 @@ Future<void> seedDb(AppDatabase database) async {
|
|||||||
// seed data setup
|
// seed data setup
|
||||||
final List<List> sessionValues = [
|
final List<List> sessionValues = [
|
||||||
[
|
[
|
||||||
'Projecting @ Climbers Rock',
|
'Projecting',
|
||||||
'Beta pully beta beta pinch one arm crimpy. Futuristic pinch, dyno dynamic drop knee climb. Climbing ondra slopey onsight beta ondra power endurance.'
|
'Beta pully beta beta pinch one arm crimpy. Futuristic pinch, dyno dynamic drop knee climb. Climbing ondra slopey onsight beta ondra power endurance.',
|
||||||
|
'Climbers Rock Inc.'
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'Moonboard @ Boardroom',
|
'Moonboard',
|
||||||
'Beta pully beta beta pinch one arm crimpy. Futuristic pinch, dyno dynamic drop knee climb. Climbing ondra slopey onsight beta ondra power endurance.'
|
'Beta pully beta beta pinch one arm crimpy. Futuristic pinch, dyno dynamic drop knee climb. Climbing ondra slopey onsight beta ondra power endurance.',
|
||||||
|
'Beta Bloc'
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'Off-Wall Training',
|
'Off-Wall Training',
|
||||||
'Beta pully beta beta pinch one arm crimpy. Futuristic pinch, dyno dynamic drop knee climb. Climbing ondra slopey onsight beta ondra power endurance.'
|
'Beta pully beta beta pinch one arm crimpy. Futuristic pinch, dyno dynamic drop knee climb. Climbing ondra slopey onsight beta ondra power endurance.',
|
||||||
|
'Climbers Rock Inc.'
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'Climbing Outdoors',
|
'Climbing Outdoors',
|
||||||
'Beta pully beta beta pinch one arm crimpy. Futuristic pinch, dyno dynamic drop knee climb. Climbing ondra slopey onsight beta ondra power endurance.'
|
'Beta pully beta beta pinch one arm crimpy. Futuristic pinch, dyno dynamic drop knee climb. Climbing ondra slopey onsight beta ondra power endurance.',
|
||||||
|
'Gravity Hamilton'
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'Volume Session @ Gravity',
|
'Volume Session',
|
||||||
'Beta pully beta beta pinch one arm crimpy. Futuristic pinch, dyno dynamic drop knee climb. Climbing ondra slopey onsight beta ondra power endurance.'
|
'Beta pully beta beta pinch one arm crimpy. Futuristic pinch, dyno dynamic drop knee climb. Climbing ondra slopey onsight beta ondra power endurance.',
|
||||||
|
'Up the Bloc'
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -36,16 +43,106 @@ Future<void> seedDb(AppDatabase database) async {
|
|||||||
['BgheYcxhrsw', MediaType.youtube]
|
['BgheYcxhrsw', MediaType.youtube]
|
||||||
];
|
];
|
||||||
|
|
||||||
final List<String> actionTypes = [
|
// 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\": \"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}]]"
|
// "[[{\"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 totalSessions = 15;
|
||||||
final int totalActivities = 6;
|
final int totalActivities = 6;
|
||||||
final int totalActions = 5;
|
// final int totalActions = 5;
|
||||||
final int totalMedia = 5;
|
final int totalMedia = 5;
|
||||||
final random = Random();
|
final random = Random();
|
||||||
|
final whitespaceRE = RegExp(r"(?! )\s+| \s+");
|
||||||
|
// we gotta build all the activities!
|
||||||
|
final jsondata =
|
||||||
|
await root_bundle.rootBundle.loadString('assets/exercises.json');
|
||||||
|
final exercises = json.decode(jsondata);
|
||||||
|
List<int> activityIds = [];
|
||||||
|
|
||||||
|
for (int i = 0; i < exercises.length; i++) {
|
||||||
|
var exercise = exercises[i];
|
||||||
|
var images = [];
|
||||||
|
if (exercise['images'] != null) {
|
||||||
|
for (int j = 0; j < exercise['images'].length; j++) {
|
||||||
|
var image = exercise['images'][j];
|
||||||
|
images.add(
|
||||||
|
"https://raw.githubusercontent.com/yuhonas/free-exercise-db/main/exercises/$image");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<Symbol, Value> payload = {
|
||||||
|
Symbol('title'): Value<String>(
|
||||||
|
exercise['name'].toString().trim().replaceAll(whitespaceRE, " ")),
|
||||||
|
Symbol('description'): Value<String>(json.encode(exercise['instructions']
|
||||||
|
.toString()
|
||||||
|
.trim()
|
||||||
|
.replaceAll(whitespaceRE, " "))),
|
||||||
|
Symbol('force'): Value<String>(exercise['force'] ?? "")
|
||||||
|
};
|
||||||
|
|
||||||
|
// well this fucking sucks
|
||||||
|
if (exercise['category'] != null) {
|
||||||
|
payload[Symbol('type')] = Value<ActivityType>(ActivityType.values
|
||||||
|
.firstWhere((e) =>
|
||||||
|
e.toString() ==
|
||||||
|
"ActivityType.${Casing.camelCase(exercise['category'])}"));
|
||||||
|
}
|
||||||
|
if (exercise['level'] != null) {
|
||||||
|
payload[Symbol('level')] = Value<ActivityLevel>(ActivityLevel.values
|
||||||
|
.firstWhere((e) =>
|
||||||
|
e.toString() ==
|
||||||
|
"ActivityLevel.${Casing.camelCase(exercise['level'])}"));
|
||||||
|
}
|
||||||
|
if (exercise['mechanic'] != null) {
|
||||||
|
payload[Symbol('mechanic')] = Value<ActivityMechanic>(
|
||||||
|
ActivityMechanic.values.firstWhere((e) =>
|
||||||
|
e.toString() ==
|
||||||
|
"ActivityMechanic.${Casing.camelCase(exercise['mechanic'])}"));
|
||||||
|
}
|
||||||
|
if (exercise['equipment'] != null) {
|
||||||
|
payload[Symbol('equipment')] = Value<ActivityEquipment>(
|
||||||
|
ActivityEquipment.values.firstWhere((e) =>
|
||||||
|
e.toString() ==
|
||||||
|
"ActivityEquipment.${Casing.camelCase(exercise['equipment'])}"));
|
||||||
|
}
|
||||||
|
if (exercise['primaryMuscles'].isNotEmpty) {
|
||||||
|
payload[Symbol('primaryMuscles')] = Value<ActivityMuscle>(
|
||||||
|
ActivityMuscle.values.firstWhere((e) =>
|
||||||
|
e.toString() ==
|
||||||
|
"ActivityMuscle.${Casing.camelCase(exercise['primaryMuscles'].first)}"));
|
||||||
|
}
|
||||||
|
if (exercise['secondaryMuscles'].isNotEmpty) {
|
||||||
|
payload[Symbol('secondaryMuscles')] = Value<ActivityMuscle>(
|
||||||
|
ActivityMuscle.values.firstWhere((e) =>
|
||||||
|
e.toString() ==
|
||||||
|
"ActivityMuscle.${Casing.camelCase(exercise['secondaryMuscles'].first)}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
activityIds.add(await database
|
||||||
|
.into(database.activities)
|
||||||
|
.insert(Function.apply(ActivitiesCompanion.new, [], payload))
|
||||||
|
.then((activityId) async {
|
||||||
|
for (int m = 0; m < images.length; m++) {
|
||||||
|
final mediaItem = images[m];
|
||||||
|
await database
|
||||||
|
.into(database.mediaItems)
|
||||||
|
.insert(MediaItemsCompanion.insert(
|
||||||
|
title: exercise['name'],
|
||||||
|
description: exercise['name'],
|
||||||
|
reference: mediaItem,
|
||||||
|
type: MediaType.image))
|
||||||
|
.then((mediaId) async {
|
||||||
|
await database.into(database.objectMediaItems).insert(
|
||||||
|
ObjectMediaItemsCompanion.insert(
|
||||||
|
objectId: activityId,
|
||||||
|
mediaId: mediaId,
|
||||||
|
objectType: ObjectType.activities));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return activityId;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
// seed loop
|
// seed loop
|
||||||
for (int i = 0; i < totalSessions; i++) {
|
for (int i = 0; i < totalSessions; i++) {
|
||||||
@@ -61,87 +158,76 @@ Future<void> seedDb(AppDatabase database) async {
|
|||||||
title: sessionValue[0],
|
title: sessionValue[0],
|
||||||
content: sessionValue[1],
|
content: sessionValue[1],
|
||||||
status: status,
|
status: status,
|
||||||
|
address: Value(sessionValue[2]),
|
||||||
|
achievements: Value(
|
||||||
|
"[\"achievement 1\", \"achievement 2\", \"achievement 3\"]"),
|
||||||
date: Value(DateTime.now())))
|
date: Value(DateTime.now())))
|
||||||
.then((sessionId) async {
|
.then((sessionId) async {
|
||||||
// activities things
|
// activities things
|
||||||
for (int j = 0; j <= random.nextInt(totalActivities); j++) {
|
for (int j = 0; j <= random.nextInt(totalActivities); j++) {
|
||||||
|
int activityId = random.nextInt(activityIds.length);
|
||||||
|
activityIds.removeAt(activityId);
|
||||||
|
|
||||||
await database
|
await database
|
||||||
.into(database.activities)
|
.into(database.sessionActivities)
|
||||||
.insert(ActivitiesCompanion.insert(
|
.insert(SessionActivitiesCompanion.insert(
|
||||||
title: "Test activity $j",
|
sessionId: sessionId,
|
||||||
type: ActivityType
|
activityId: activityId,
|
||||||
.values[random.nextInt(ActivityType.values.length)],
|
position: j,
|
||||||
description:
|
results: Value("results json, will need to test"),
|
||||||
"$j Beta pully beta beta pinch one arm crimpy. Futuristic pinch, dyno dynamic drop knee climb. Climbing ondra slopey onsight beta ondra power endurance.",
|
));
|
||||||
category: ActivityCategories
|
|
||||||
.values[random.nextInt(ActivityCategories.values.length)]))
|
|
||||||
.then((activityId) async {
|
|
||||||
// session activity relationships
|
|
||||||
await database
|
|
||||||
.into(database.sessionActivities)
|
|
||||||
.insert(SessionActivitiesCompanion.insert(
|
|
||||||
sessionId: sessionId,
|
|
||||||
activityId: activityId,
|
|
||||||
position: j,
|
|
||||||
results: Value("results json, will need to test"),
|
|
||||||
achievements: Value("comma, seperated, items"),
|
|
||||||
));
|
|
||||||
|
|
||||||
// actions
|
// actions
|
||||||
for (int k = 0; k <= random.nextInt(totalActions); k++) {
|
// await database
|
||||||
await database
|
// .into(database.actions)
|
||||||
.into(database.actions)
|
// .insert(ActionsCompanion.insert(
|
||||||
.insert(ActionsCompanion.insert(
|
// title: 'Test action',
|
||||||
title: 'Test action $k',
|
// description:
|
||||||
description:
|
// 'Beta pully beta beta pinch one arm crimpy. Futuristic pinch, dyno dynamic drop knee climb. Climbing ondra slopey onsight beta ondra power endurance.',
|
||||||
'$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,
|
||||||
set: actionTypes[random.nextInt(actionTypes.length)]))
|
// totalReps: "[1]",
|
||||||
.then((actionId) async {
|
// restBeforeSets: Value(30000),
|
||||||
// add activity action association
|
// restBetweenSets: Value(300000),
|
||||||
await database.into(database.activityActions).insert(
|
// restBetweenReps: Value(15000),
|
||||||
ActivityActionsCompanion.insert(
|
// restAfterSets: Value(300000),
|
||||||
activityId: activityId, actionId: actionId, position: k));
|
// repType: RepType.time,
|
||||||
|
// repLength: Value(10000),
|
||||||
for (int l = 0; l <= random.nextInt(totalMedia); l++) {
|
// repWeights: Value("[110]"),
|
||||||
final mediaItem = mediaItems[random.nextInt(mediaItems.length)];
|
// setWeights: Value("[1]"),
|
||||||
await database
|
// isAlternating: Value(true),
|
||||||
.into(database.mediaItems)
|
// set: actionTypes[random.nextInt(actionTypes.length)]))
|
||||||
.insert(MediaItemsCompanion.insert(
|
// .then((actionId) async {
|
||||||
title: 'Media title $l',
|
// // add activity action association
|
||||||
description:
|
// await database.into(database.activityActions).insert(
|
||||||
'Media description $l Beta pully beta beta pinch one arm crimpy. Futuristic pinch, dyno dynamic drop knee climb. Climbing ondra slopey onsight beta ondra power endurance.',
|
// ActivityActionsCompanion.insert(
|
||||||
reference: mediaItem[0],
|
// activityId: activityId, actionId: actionId, sessionId: sessionId, position: 0));
|
||||||
type: mediaItem[1]))
|
// });
|
||||||
.then((mediaId) async {
|
// for (int k = 0; k <= random.nextInt(totalActions); k++) {
|
||||||
await database.into(database.objectMediaItems).insert(
|
// await database
|
||||||
ObjectMediaItemsCompanion.insert(
|
// .into(database.actions)
|
||||||
objectId: actionId,
|
// .insert(ActionsCompanion.insert(
|
||||||
mediaId: mediaId,
|
// title: 'Test action $k',
|
||||||
objectType: ObjectType.actions));
|
// 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),
|
||||||
for (int m = 0; m <= random.nextInt(totalMedia); m++) {
|
// restBetweenReps: Value(15000),
|
||||||
final mediaItem = mediaItems[random.nextInt(mediaItems.length)];
|
// restAfterSets: Value(300000),
|
||||||
await database
|
// repType: RepType.time,
|
||||||
.into(database.mediaItems)
|
// repLength: Value(10000),
|
||||||
.insert(MediaItemsCompanion.insert(
|
// repWeights: Value("[110]"),
|
||||||
title: 'Media title $m',
|
// setWeights: Value("[1]"),
|
||||||
description:
|
// isAlternating: Value(true),
|
||||||
'Media description $m 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)]))
|
||||||
reference: mediaItem[0],
|
// .then((actionId) async {
|
||||||
type: mediaItem[1]))
|
// // add activity action association
|
||||||
.then((mediaId) async {
|
// await database.into(database.activityActions).insert(
|
||||||
await database.into(database.objectMediaItems).insert(
|
// ActivityActionsCompanion.insert(
|
||||||
ObjectMediaItemsCompanion.insert(
|
// activityId: activityId, actionId: actionId, position: k));
|
||||||
objectId: activityId,
|
// });
|
||||||
mediaId: mediaId,
|
// }
|
||||||
objectType: ObjectType.activities));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int n = 0; n <= random.nextInt(totalMedia); n++) {
|
for (int n = 0; n <= random.nextInt(totalMedia); n++) {
|
||||||
@@ -162,6 +248,23 @@ Future<void> seedDb(AppDatabase database) async {
|
|||||||
objectType: ObjectType.sessions));
|
objectType: ObjectType.sessions));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await database
|
||||||
|
.into(database.mediaItems)
|
||||||
|
.insert(MediaItemsCompanion.insert(
|
||||||
|
title: 'Locations details',
|
||||||
|
description:
|
||||||
|
'5155 Harvester Rd #1, Burlington, ON L7L 6V2, Canada',
|
||||||
|
reference:
|
||||||
|
'https://lh3.googleusercontent.com/places/ANXAkqHwtb5oRMGG3haJkaHeTxdTI1lQ17RgvkCXwzA1dGV53BXPbHrdXIs1mLC_-4exyRW8dbYhMOeiOCHJqGeVBx-dNtABZAl9tQA=s4800-w800',
|
||||||
|
type: MediaType.location))
|
||||||
|
.then((mediaId) async {
|
||||||
|
await database.into(database.objectMediaItems).insert(
|
||||||
|
ObjectMediaItemsCompanion.insert(
|
||||||
|
objectId: sessionId,
|
||||||
|
mediaId: mediaId,
|
||||||
|
objectType: ObjectType.sessions));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,3 +9,8 @@ String formattedTime(int timeInSecond) {
|
|||||||
String second = sec.toString().length <= 1 ? "0$sec" : "$sec";
|
String second = sec.toString().length <= 1 ? "0$sec" : "$sec";
|
||||||
return "$minute:$second";
|
return "$minute:$second";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int toSeconds(int milliseconds) {
|
||||||
|
int sec = (milliseconds / 1000).floor();
|
||||||
|
return sec;
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,15 +3,60 @@ import 'package:sendtrain/database/database.dart';
|
|||||||
import 'package:sendtrain/widgets/media/media_details.dart';
|
import 'package:sendtrain/widgets/media/media_details.dart';
|
||||||
|
|
||||||
showMediaDetailWidget(BuildContext context, MediaItem media) {
|
showMediaDetailWidget(BuildContext context, MediaItem media) {
|
||||||
|
showEditorSheet(context, MediaDetails(media: media));
|
||||||
|
}
|
||||||
|
|
||||||
|
showGenericSheet(BuildContext context, Widget widget,
|
||||||
|
[Color? backgroundColor]) {
|
||||||
|
backgroundColor ??= Theme.of(context).colorScheme.surfaceBright;
|
||||||
|
|
||||||
showModalBottomSheet<void>(
|
showModalBottomSheet<void>(
|
||||||
|
backgroundColor: backgroundColor,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.all(Radius.circular(10.0)),
|
borderRadius: BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(10.0), topRight: Radius.circular(10.0)),
|
||||||
),
|
),
|
||||||
context: context,
|
context: context,
|
||||||
showDragHandle: true,
|
showDragHandle: true,
|
||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
useSafeArea: true,
|
useSafeArea: true,
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return MediaDetails(media: media);
|
return widget;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showEditorSheet(BuildContext context, Widget widget) {
|
||||||
|
showGenericSheet(context, widget);
|
||||||
|
}
|
||||||
|
|
||||||
|
String jsonToDescription(List text) {
|
||||||
|
String content = '';
|
||||||
|
|
||||||
|
for (int i = 0; i < text.length; i++) {
|
||||||
|
if (content.isEmpty) {
|
||||||
|
content = text[i];
|
||||||
|
} else {
|
||||||
|
content = "$content\n\n${text[i]}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|||||||
+16
-16
@@ -1,7 +1,9 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:sendtrain/database/database.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/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/activities_screen.dart';
|
||||||
import 'package:sendtrain/widgets/screens/sessions_screen.dart';
|
import 'package:sendtrain/widgets/screens/sessions_screen.dart';
|
||||||
// ignore: unused_import
|
// ignore: unused_import
|
||||||
@@ -77,6 +79,10 @@ class _AppState extends State<App> {
|
|||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
child: const Text('In Progress...'),
|
child: const Text('In Progress...'),
|
||||||
),
|
),
|
||||||
|
Container(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: const Text('Profile in Progress...'),
|
||||||
|
),
|
||||||
][currentPageIndex]),
|
][currentPageIndex]),
|
||||||
bottomNavigationBar: NavigationBar(
|
bottomNavigationBar: NavigationBar(
|
||||||
onDestinationSelected: (int index) {
|
onDestinationSelected: (int index) {
|
||||||
@@ -89,27 +95,20 @@ class _AppState extends State<App> {
|
|||||||
NavigationDestination(
|
NavigationDestination(
|
||||||
icon: Icon(Icons.sports), label: "Sessions"),
|
icon: Icon(Icons.sports), label: "Sessions"),
|
||||||
NavigationDestination(
|
NavigationDestination(
|
||||||
icon: Icon(Icons.landscape), label: "Activities"),
|
icon: Icon(Icons.sports_gymnastics_rounded),
|
||||||
|
label: "Activities"),
|
||||||
NavigationDestination(
|
NavigationDestination(
|
||||||
icon: Icon(Icons.calendar_month_rounded), label: "Plan"),
|
icon: Icon(Icons.calendar_month_rounded), label: "Plan"),
|
||||||
NavigationDestination(
|
NavigationDestination(
|
||||||
icon: Icon(Icons.group), label: "Team Send"),
|
icon: Icon(Icons.group), label: "Team Send"),
|
||||||
NavigationDestination(
|
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(
|
floatingActionButton: FloatingActionButton.extended(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
showModalBottomSheet<void>(
|
showEditorSheet(context, SessionEditor());
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(10.0)),
|
|
||||||
),
|
|
||||||
context: context,
|
|
||||||
showDragHandle: true,
|
|
||||||
isScrollControlled: true,
|
|
||||||
useSafeArea: true,
|
|
||||||
builder: (BuildContext context) {
|
|
||||||
return SessionEditor();
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
label: const Text('New Session'),
|
label: const Text('New Session'),
|
||||||
icon: const Icon(Icons.add_chart),
|
icon: const Icon(Icons.add_chart),
|
||||||
@@ -119,12 +118,13 @@ class _AppState extends State<App> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
|
var db = AppDatabase();
|
||||||
runApp(MultiProvider(
|
runApp(MultiProvider(
|
||||||
providers: [
|
providers: [
|
||||||
ChangeNotifierProvider(create: (context) => ActivityTimerModel()),
|
|
||||||
Provider<AppDatabase>(
|
Provider<AppDatabase>(
|
||||||
create: (context) => AppDatabase(),
|
create: (context) => db, dispose: (context, db) => db.close()),
|
||||||
dispose: (context, db) => db.close()),
|
ChangeNotifierProvider(create: (context) => ActivityTimerModel()),
|
||||||
|
ChangeNotifierProvider(create: (context) => ActionTimer()),
|
||||||
],
|
],
|
||||||
child: const SendTrain(),
|
child: const SendTrain(),
|
||||||
));
|
));
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -19,9 +19,9 @@ class ActivityTimerModel with ChangeNotifier {
|
|||||||
|
|
||||||
int get actionCount => _actionCounter;
|
int get actionCount => _actionCounter;
|
||||||
int get currentActionNum => _currentActionNum;
|
int get currentActionNum => _currentActionNum;
|
||||||
dynamic get currentAction => currentSet[_currentActionNum];
|
dynamic get currentAction => currentSet.isNotEmpty ? currentSet[_currentActionNum] : {};
|
||||||
int get currentSetNum => _currentSetNum;
|
int get currentSetNum => _currentSetNum;
|
||||||
dynamic get currentSet => _sets[_currentSetNum];
|
dynamic get currentSet => _sets.isNotEmpty ? _sets[_currentSetNum] : {};
|
||||||
Activity? get activity => _activity;
|
Activity? get activity => _activity;
|
||||||
List get sets => _sets;
|
List get sets => _sets;
|
||||||
Timer? get periodicTimer => _periodicTimer;
|
Timer? get periodicTimer => _periodicTimer;
|
||||||
@@ -36,7 +36,7 @@ class ActivityTimerModel with ChangeNotifier {
|
|||||||
_isc = null;
|
_isc = null;
|
||||||
_activity = activity;
|
_activity = activity;
|
||||||
// only one action for now
|
// only one action for now
|
||||||
_sets = json.decode(actions[0].set);
|
_sets = actions.isNotEmpty ? json.decode(actions[0].set) : [];
|
||||||
// _actions = actions;
|
// _actions = actions;
|
||||||
_currentActionNum = 0;
|
_currentActionNum = 0;
|
||||||
_currentSetNum = 0;
|
_currentSetNum = 0;
|
||||||
@@ -92,7 +92,7 @@ class ActivityTimerModel with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void setActionCount() {
|
void setActionCount() {
|
||||||
_actionCounter = currentAction['amount'];
|
_actionCounter = currentAction.isNotEmpty ? currentAction['amount'] : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void pause() {
|
void pause() {
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
class GooglePlaceModel {
|
||||||
|
final String placeId;
|
||||||
|
final String description;
|
||||||
|
final String address;
|
||||||
|
final List<dynamic>? imageReferences;
|
||||||
|
|
||||||
|
GooglePlaceModel(
|
||||||
|
{required this.placeId,
|
||||||
|
required this.description,
|
||||||
|
required this.address,
|
||||||
|
this.imageReferences});
|
||||||
|
}
|
||||||
@@ -0,0 +1,223 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.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';
|
||||||
|
import 'package:vibration/vibration.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.clear();
|
||||||
|
_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.mp3')
|
||||||
|
.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.mp3)
|
||||||
|
.then((duration) async {
|
||||||
|
if (await Vibration.hasVibrator()) {
|
||||||
|
Vibration.vibrate(duration: 250);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_currentTime == 0) {
|
||||||
|
// move to next action
|
||||||
|
await _mPlayer
|
||||||
|
.startPlayer(fromDataBuffer: finishTone, codec: Codec.mp3)
|
||||||
|
.then((duration) async {
|
||||||
|
if (await Vibration.hasVibrator()) {
|
||||||
|
Vibration.vibrate(duration: 250);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:sendtrain/daos/activities_dao.dart';
|
||||||
|
import 'package:sendtrain/database/database.dart';
|
||||||
|
import 'package:sendtrain/helpers/widget_helpers.dart';
|
||||||
|
import 'package:sendtrain/widgets/generic/elements/form_search_input.dart';
|
||||||
|
|
||||||
|
class ActivityFinderService {
|
||||||
|
final BuildContext context;
|
||||||
|
final ActivitiesDao dao;
|
||||||
|
|
||||||
|
ActivityFinderService(this.context)
|
||||||
|
: dao = ActivitiesDao(Provider.of<AppDatabase>(context, listen: false));
|
||||||
|
|
||||||
|
void finish() {}
|
||||||
|
|
||||||
|
Future<List<Suggestion>?> fetchSuggestions(String input) async {
|
||||||
|
List<Activity> activities = await dao.contains(input);
|
||||||
|
|
||||||
|
if (activities.isNotEmpty) {
|
||||||
|
return activities
|
||||||
|
.map<Suggestion>((activity) => Suggestion<Activity>(activity))
|
||||||
|
.toList();
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget resultWidget(Activity activity, Function? callback) {
|
||||||
|
return ListTile(
|
||||||
|
title: Text(activity.title),
|
||||||
|
subtitle: Text(jsonToDescription(json.decode(activity.description ?? "")),
|
||||||
|
maxLines: 2, softWrap: true, overflow: TextOverflow.ellipsis),
|
||||||
|
onTap: () {
|
||||||
|
if (callback != null) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
+23
-32
@@ -1,7 +1,10 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:http/http.dart';
|
import 'package:http/http.dart';
|
||||||
|
import 'package:sendtrain/models/google_place_model.dart';
|
||||||
|
import 'package:sendtrain/widgets/generic/elements/form_search_input.dart';
|
||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
class GooglePlacesService {
|
class GooglePlacesService {
|
||||||
@@ -13,7 +16,7 @@ class GooglePlacesService {
|
|||||||
client.close();
|
client.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<Suggestion>?> fetchSuggestions(String input, String lang) async {
|
Future<List<Suggestion>?> fetchSuggestions(String input) async {
|
||||||
var headers = {
|
var headers = {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'X-Goog-Api-Key': apiKey,
|
'X-Goog-Api-Key': apiKey,
|
||||||
@@ -33,11 +36,12 @@ class GooglePlacesService {
|
|||||||
|
|
||||||
if (result.isNotEmpty) {
|
if (result.isNotEmpty) {
|
||||||
return result['places']
|
return result['places']
|
||||||
.map<Suggestion>((p) => Suggestion(
|
.map<Suggestion>((p) => Suggestion<GooglePlaceModel>(
|
||||||
placeId: p['id'],
|
GooglePlaceModel(
|
||||||
description: p['displayName']['text'],
|
placeId: p['id'],
|
||||||
address: p['formattedAddress'],
|
description: p['displayName']['text'],
|
||||||
imageReferences: p['photos']))
|
address: p['formattedAddress'],
|
||||||
|
imageReferences: p['photos'])))
|
||||||
.toList();
|
.toList();
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
@@ -52,9 +56,10 @@ class GooglePlacesService {
|
|||||||
"Access-Control-Allow-Origin": "*",
|
"Access-Control-Allow-Origin": "*",
|
||||||
};
|
};
|
||||||
|
|
||||||
var request = Request('GET',
|
var request = Request(
|
||||||
Uri.parse('https://places.googleapis.com/v1/$name/media?key=$apiKey&maxWidthPx=800&skipHttpRedirect=true')
|
'GET',
|
||||||
);
|
Uri.parse(
|
||||||
|
'https://places.googleapis.com/v1/$name/media?key=$apiKey&maxWidthPx=800&skipHttpRedirect=true'));
|
||||||
request.headers.addAll(headers);
|
request.headers.addAll(headers);
|
||||||
|
|
||||||
StreamedResponse response = await request.send();
|
StreamedResponse response = await request.send();
|
||||||
@@ -71,29 +76,15 @@ class GooglePlacesService {
|
|||||||
throw Exception(response.reasonPhrase);
|
throw Exception(response.reasonPhrase);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
class Suggestion {
|
Widget resultWidget(GooglePlaceModel place, Function? callback) {
|
||||||
final String placeId;
|
return ListTile(
|
||||||
final String description;
|
title: Text(place.description),
|
||||||
final String address;
|
onTap: () async {
|
||||||
final List<dynamic>? imageReferences;
|
if (callback != null) {
|
||||||
|
callback();
|
||||||
Suggestion(
|
}
|
||||||
{required this.placeId,
|
},
|
||||||
required this.description,
|
);
|
||||||
required this.address,
|
|
||||||
this.imageReferences});
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return 'Suggestion(description: $description, placeId: $placeId)';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Map toJson() => {
|
|
||||||
'placeId': placeId,
|
|
||||||
'name': description,
|
|
||||||
'address': address,
|
|
||||||
'imageReferences': imageReferences
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:drift/drift.dart' hide Column;
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:sendtrain/daos/sessions_dao.dart';
|
||||||
|
import 'package:sendtrain/database/database.dart';
|
||||||
|
import 'package:sendtrain/widgets/generic/elements/form_text_input.dart';
|
||||||
|
|
||||||
|
class AchievementEditor extends StatelessWidget {
|
||||||
|
AchievementEditor({super.key, required this.session, this.callback});
|
||||||
|
|
||||||
|
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
|
||||||
|
final TextEditingController tec = TextEditingController();
|
||||||
|
final Session session;
|
||||||
|
final Function? callback;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(
|
||||||
|
BuildContext context,
|
||||||
|
) {
|
||||||
|
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('Create Achievement',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: Theme.of(context).textTheme.titleLarge)),
|
||||||
|
FormTextInput(controller: tec, title: 'Achievement', icon: Icon(Icons.military_tech_rounded)),
|
||||||
|
Row(mainAxisAlignment: MainAxisAlignment.end, children: [
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.only(top: 10),
|
||||||
|
child: FilledButton(
|
||||||
|
child: Text('Submit'),
|
||||||
|
onPressed: () async {
|
||||||
|
session.achievements;
|
||||||
|
List achievements =
|
||||||
|
json.decode(session.achievements ?? "[]");
|
||||||
|
achievements.add(tec.text);
|
||||||
|
Session updatedSession = session.copyWith(
|
||||||
|
achievements:
|
||||||
|
Value<String>(json.encode(achievements)));
|
||||||
|
|
||||||
|
SessionsDao(Provider.of<AppDatabase>(context,
|
||||||
|
listen: false))
|
||||||
|
.replace(updatedSession);
|
||||||
|
|
||||||
|
Navigator.pop(_formKey.currentContext!, 'Submit');
|
||||||
|
|
||||||
|
if (callback != null) {
|
||||||
|
await callback!();
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
])
|
||||||
|
])));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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('rep'),
|
||||||
|
description: Value('exercise action'),
|
||||||
|
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:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:scrollable_positioned_list/scrollable_positioned_list.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/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 {
|
// class ActivityActionView extends StatefulWidget {
|
||||||
const ActivityActionView({super.key, required this.actions});
|
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 List actions;
|
||||||
|
final Function? callback;
|
||||||
|
final bool resetOnLoad;
|
||||||
|
|
||||||
@override
|
// @override
|
||||||
State<ActivityActionView> createState() => ActivityActionViewState();
|
// 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 ItemScrollController itemScrollController = ItemScrollController();
|
||||||
final ScrollOffsetController scrollOffsetController =
|
final ScrollOffsetController scrollOffsetController =
|
||||||
ScrollOffsetController();
|
ScrollOffsetController();
|
||||||
@@ -23,88 +41,210 @@ class ActivityActionViewState extends State<ActivityActionView> {
|
|||||||
final ScrollOffsetListener scrollOffsetListener =
|
final ScrollOffsetListener scrollOffsetListener =
|
||||||
ScrollOffsetListener.create();
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
ActivityTimerModel atm =
|
at = Provider.of<ActionTimer>(context, listen: false);
|
||||||
Provider.of<ActivityTimerModel>(context, listen: true);
|
int actionCount = 0;
|
||||||
List sets = json.decode(widget.actions[0].set);
|
if (actions.isNotEmpty) {
|
||||||
|
at.setup(
|
||||||
|
ActionModel(
|
||||||
|
action: actions.first,
|
||||||
|
db: Provider.of<AppDatabase>(context)),
|
||||||
|
itemScrollController,
|
||||||
|
resetOnLoad);
|
||||||
|
|
||||||
// we need to set the scroll controller
|
// WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
// so we can update the selected item position
|
// if (itemScrollController.isAttached) {
|
||||||
atm.setScrollController(itemScrollController);
|
// itemScrollController.scrollTo(
|
||||||
|
// index: at.currentAction.parentId != null
|
||||||
|
// ? at.currentAction.parentId!
|
||||||
|
// : at.currentAction.id,
|
||||||
|
// duration: Duration(milliseconds: 500),
|
||||||
|
// curve: Curves.easeInOutCubic);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
return Expanded(
|
return Expanded(
|
||||||
child: ScrollablePositionedList.builder(
|
child: Column(children: [
|
||||||
padding: const EdgeInsets.fromLTRB(10, 0, 10, 20),
|
Padding(
|
||||||
itemCount: sets.length,
|
padding: const EdgeInsets.only(left: 10, right: 10),
|
||||||
itemScrollController: itemScrollController,
|
child: Card(
|
||||||
scrollOffsetController: scrollOffsetController,
|
clipBehavior: Clip.antiAlias,
|
||||||
itemPositionsListener: itemPositionsListener,
|
shape: const RoundedRectangleBorder(
|
||||||
scrollOffsetListener: scrollOffsetListener,
|
borderRadius: BorderRadius.only(
|
||||||
itemBuilder: (BuildContext context, int setNum) {
|
topLeft: Radius.circular(10),
|
||||||
List<GestureDetector> content = [];
|
topRight: Radius.circular(10)),
|
||||||
List set = sets[setNum];
|
),
|
||||||
|
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++) {
|
List<GestureDetector> content = [];
|
||||||
Map<String, dynamic> setItem = set[actionNum];
|
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(
|
for (int setItemNum = 0;
|
||||||
onTap: () {
|
setItemNum < setItems.length;
|
||||||
atm.setAction(setNum, actionNum, 'manual');
|
setItemNum++) {
|
||||||
atm.setActionCount();
|
Item setItem = setItems[setItemNum];
|
||||||
|
content.add(gtBuild(at, setItem, actionCount++, itemNum,
|
||||||
|
order: (item as Set).setOrder));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
itemScrollController.scrollTo(
|
if (itemNum == 0) {
|
||||||
index: setNum,
|
return Card(
|
||||||
duration: Duration(milliseconds: 500),
|
shape: const RoundedRectangleBorder(
|
||||||
curve: Curves.easeInOutCubic);
|
borderRadius: BorderRadius.only(
|
||||||
},
|
topLeft: Radius.circular(0),
|
||||||
child: Row(children: [
|
topRight: Radius.circular(0),
|
||||||
Ink(
|
bottomLeft: Radius.circular(10),
|
||||||
width: 70,
|
bottomRight: Radius.circular(10)),
|
||||||
padding: const EdgeInsets.all(15),
|
),
|
||||||
color: atm.isCurrentItem(setNum, actionNum)
|
clipBehavior: Clip.antiAlias,
|
||||||
? Theme.of(context).colorScheme.primaryContainer
|
child: Column(children: content));
|
||||||
: Theme.of(context).colorScheme.onPrimary,
|
} else {
|
||||||
child: Text(
|
return Card(
|
||||||
textAlign: TextAlign.center,
|
shape: const RoundedRectangleBorder(
|
||||||
'${setNum + 1}.${actionNum + 1} ')),
|
borderRadius: BorderRadius.only(
|
||||||
Expanded(
|
topLeft: Radius.circular(10),
|
||||||
child: Ink(
|
topRight: Radius.circular(10),
|
||||||
padding: const EdgeInsets.all(15),
|
bottomLeft: Radius.circular(10),
|
||||||
color: atm.isCurrentItem(setNum, actionNum)
|
bottomRight: Radius.circular(10)),
|
||||||
? Theme.of(context).colorScheme.surfaceBright
|
),
|
||||||
: Theme.of(context).colorScheme.surfaceContainerLow,
|
clipBehavior: Clip.antiAlias,
|
||||||
child: Text(
|
child: Column(children: content));
|
||||||
textAlign: TextAlign.center,
|
}
|
||||||
'${setItem['name']}: ${setItem['amount']} ${setItem['type']}'.toTitleCase())))
|
}))
|
||||||
])));
|
]));
|
||||||
}
|
} else {
|
||||||
|
return AddCardGeneric(
|
||||||
if (setNum == 0) {
|
title: 'Add an Action!',
|
||||||
return Card(
|
description:
|
||||||
shape: const RoundedRectangleBorder(
|
'Click here to create an exercise template (sets and reps, etc) for your activity!',
|
||||||
borderRadius: BorderRadius.only(
|
action: () {
|
||||||
topLeft: Radius.circular(0),
|
showEditorSheet(
|
||||||
topRight: Radius.circular(0),
|
context,
|
||||||
bottomLeft: Radius.circular(10),
|
ActivityActionEditor(
|
||||||
bottomRight: Radius.circular(10)),
|
session: session,
|
||||||
),
|
activity: activity,
|
||||||
clipBehavior: Clip.antiAlias,
|
callback: callback));
|
||||||
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);
|
|
||||||
},
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:sendtrain/daos/activities_dao.dart';
|
|
||||||
import 'package:sendtrain/daos/media_items_dao.dart';
|
import 'package:sendtrain/daos/media_items_dao.dart';
|
||||||
|
import 'package:sendtrain/daos/session_activities_dao.dart';
|
||||||
import 'package:sendtrain/database/database.dart';
|
import 'package:sendtrain/database/database.dart';
|
||||||
import 'package:sendtrain/extensions/string_extensions.dart';
|
import 'package:sendtrain/extensions/string_extensions.dart';
|
||||||
import 'package:sendtrain/helpers/date_time_helpers.dart';
|
import 'package:sendtrain/helpers/date_time_helpers.dart';
|
||||||
import 'package:sendtrain/helpers/media_helpers.dart';
|
import 'package:sendtrain/helpers/media_helpers.dart';
|
||||||
|
import 'package:sendtrain/helpers/widget_helpers.dart';
|
||||||
import 'package:sendtrain/models/activity_timer_model.dart';
|
import 'package:sendtrain/models/activity_timer_model.dart';
|
||||||
import 'package:sendtrain/widgets/activities/activity_view.dart';
|
import 'package:sendtrain/widgets/activities/activity_view.dart';
|
||||||
import 'package:sendtrain/widgets/builders/dialogs.dart';
|
import 'package:sendtrain/widgets/builders/dialogs.dart';
|
||||||
@@ -14,8 +17,14 @@ import 'package:sendtrain/widgets/generic/elements/generic_progress_indicator.da
|
|||||||
|
|
||||||
class ActivityCard extends StatefulWidget {
|
class ActivityCard extends StatefulWidget {
|
||||||
final Activity activity;
|
final Activity activity;
|
||||||
|
final Session session;
|
||||||
|
final Function? callback;
|
||||||
|
|
||||||
const ActivityCard({super.key, required this.activity});
|
const ActivityCard(
|
||||||
|
{super.key,
|
||||||
|
required this.activity,
|
||||||
|
required this.session,
|
||||||
|
this.callback});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ActivityCard> createState() => ActivityCardState();
|
State<ActivityCard> createState() => ActivityCardState();
|
||||||
@@ -24,8 +33,7 @@ class ActivityCard extends StatefulWidget {
|
|||||||
class ActivityCardState extends State<ActivityCard> {
|
class ActivityCardState extends State<ActivityCard> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final ActivityTimerModel atm =
|
final ActivityTimerModel atm = Provider.of<ActivityTimerModel>(context);
|
||||||
Provider.of<ActivityTimerModel>(context);
|
|
||||||
|
|
||||||
return FutureBuilder<List<MediaItem>>(
|
return FutureBuilder<List<MediaItem>>(
|
||||||
future: MediaItemsDao(Provider.of<AppDatabase>(context))
|
future: MediaItemsDao(Provider.of<AppDatabase>(context))
|
||||||
@@ -34,36 +42,42 @@ class ActivityCardState extends State<ActivityCard> {
|
|||||||
if (snapshot.hasData) {
|
if (snapshot.hasData) {
|
||||||
List<MediaItem> mediaItems = snapshot.data!;
|
List<MediaItem> mediaItems = snapshot.data!;
|
||||||
|
|
||||||
return Card(
|
return Card.outlined(
|
||||||
color: atm.activity?.id == widget.activity.id
|
color: atm.activity?.id == widget.activity.id
|
||||||
? Theme.of(context).colorScheme.primaryContainer
|
? Theme.of(context).colorScheme.primaryContainer
|
||||||
: Theme.of(context).colorScheme.surfaceContainerLow,
|
: Theme.of(context).colorScheme.surfaceContainerLow,
|
||||||
clipBehavior: Clip.hardEdge,
|
clipBehavior: Clip.hardEdge,
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () => showGenericDialog(
|
onTap: () => showGenericDialog(
|
||||||
ActivityView(activity: widget.activity), context),
|
ActivityView(session: widget.session, activity: widget.activity), context),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
ListTile(
|
ListTile(
|
||||||
// visualDensity: VisualDensity(horizontal: VisualDensity.maximumDensity),
|
|
||||||
leading: CardImage(
|
leading: CardImage(
|
||||||
image: findMediaByType(mediaItems, MediaType.image)),
|
image:
|
||||||
|
findMediaByType(mediaItems, MediaType.image)),
|
||||||
title: Consumer<ActivityTimerModel>(
|
title: Consumer<ActivityTimerModel>(
|
||||||
builder: (context, atm, child) {
|
builder: (context, atm, child) {
|
||||||
if (atm.activity?.id == widget.activity.id) {
|
if (atm.activity?.id == widget.activity.id) {
|
||||||
return Text(
|
return Text(
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
"${widget.activity.title.toTitleCase()} (${formattedTime(atm.totalTime)})");
|
"${widget.activity.title.toTitleCase()} (${formattedTime(atm.totalTime)})");
|
||||||
} else {
|
} else {
|
||||||
return Text(
|
return Text(
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
widget.activity.title.toTitleCase());
|
widget.activity.title.toTitleCase());
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
subtitle:
|
subtitle: Text(
|
||||||
Text(maxLines: 2, widget.activity.description),
|
overflow: TextOverflow.ellipsis,
|
||||||
|
maxLines: 2,
|
||||||
|
softWrap: true,
|
||||||
|
jsonToDescription(json
|
||||||
|
.decode(widget.activity.description ?? ""))),
|
||||||
contentPadding: EdgeInsets.only(left: 13),
|
contentPadding: EdgeInsets.only(left: 13),
|
||||||
trailing: Flex(
|
trailing: Flex(
|
||||||
direction: Axis.vertical,
|
direction: Axis.vertical,
|
||||||
@@ -76,15 +90,15 @@ class ActivityCardState extends State<ActivityCard> {
|
|||||||
icon: Icon(Icons.close_rounded),
|
icon: Icon(Icons.close_rounded),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
showRemovalDialog(
|
showRemovalDialog(
|
||||||
'Activity Removal',
|
'Activity Removal',
|
||||||
'Would you like to permanently remove this activity from the current session?',
|
'Would you like to permanently remove this activity from the current session?',
|
||||||
context,
|
context, () {
|
||||||
ActivitiesDao(
|
SessionActivitiesDao(
|
||||||
Provider.of<AppDatabase>(
|
Provider.of<AppDatabase>(context,
|
||||||
context,
|
listen: false))
|
||||||
listen: false)),
|
.removeAssociation(widget.activity.id,
|
||||||
widget.activity)
|
widget.session.id);
|
||||||
.then((result) {
|
}).then((result) {
|
||||||
setState(() {});
|
setState(() {});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,18 +1,23 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart' hide Action;
|
||||||
import 'package:flutter_expandable_fab/flutter_expandable_fab.dart';
|
import 'package:flutter_expandable_fab/flutter_expandable_fab.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:sendtrain/daos/actions_dao.dart';
|
import 'package:sendtrain/daos/actions_dao.dart';
|
||||||
import 'package:sendtrain/database/database.dart';
|
import 'package:sendtrain/database/database.dart';
|
||||||
import 'package:sendtrain/extensions/string_extensions.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/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_action_view.dart';
|
||||||
import 'package:sendtrain/widgets/activities/activity_view_categories.dart';
|
import 'package:sendtrain/widgets/activities/activity_view_categories.dart';
|
||||||
import 'package:sendtrain/widgets/activities/activity_view_media.dart';
|
import 'package:sendtrain/widgets/activities/activity_view_media.dart';
|
||||||
import 'package:sendtrain/widgets/activities/activity_view_types.dart';
|
import 'package:sendtrain/widgets/builders/dialogs.dart';
|
||||||
|
|
||||||
class ActivityView extends StatefulWidget {
|
class ActivityView extends StatefulWidget {
|
||||||
const ActivityView(
|
const ActivityView(
|
||||||
{super.key, required this.activity});
|
{super.key, required this.session, required this.activity});
|
||||||
|
final Session session;
|
||||||
final Activity activity;
|
final Activity activity;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -20,173 +25,254 @@ class ActivityView extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _ActivityViewState extends State<ActivityView> {
|
class _ActivityViewState extends State<ActivityView> {
|
||||||
|
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) {
|
||||||
|
muscles.add(activity.primaryMuscles!);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activity.secondaryMuscles != null) {
|
||||||
|
muscles.add(activity.secondaryMuscles!);
|
||||||
|
}
|
||||||
|
|
||||||
|
return muscles;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final Activity activity = widget.activity;
|
final Activity activity = widget.activity;
|
||||||
ActivityTimerModel atm =
|
final Session session = widget.session;
|
||||||
Provider.of<ActivityTimerModel>(context, listen: false);
|
|
||||||
|
|
||||||
return FutureBuilder<List>(
|
return FutureBuilder<List>(
|
||||||
future: ActionsDao(Provider.of<AppDatabase>(context))
|
future: ActionsDao(Provider.of<AppDatabase>(context))
|
||||||
.fromActivity(activity),
|
.fromActivity(activity, session),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.hasData) {
|
if (snapshot.hasData) {
|
||||||
List actions = snapshot.data!;
|
List<Action> actions = snapshot.data! as List<Action>;
|
||||||
atm.setup(activity, actions);
|
|
||||||
|
|
||||||
return Scaffold(
|
return PopScope(
|
||||||
floatingActionButtonLocation: ExpandableFab.location,
|
canPop: false,
|
||||||
floatingActionButton: ExpandableFab(
|
onPopInvokedWithResult: (didPop, result) async {
|
||||||
distance: 70,
|
if (didPop) {
|
||||||
type: ExpandableFabType.up,
|
return;
|
||||||
overlayStyle: ExpandableFabOverlayStyle(
|
}
|
||||||
color: Colors.black.withOpacity(0.5),
|
final bool shouldPop = await showBackDialog(context) ?? false;
|
||||||
blur: 10,
|
if (context.mounted && shouldPop) {
|
||||||
),
|
Navigator.pop(context);
|
||||||
children: [
|
}
|
||||||
FloatingActionButton.extended(
|
},
|
||||||
icon: const Icon(Icons.upload_outlined),
|
child: Scaffold(
|
||||||
label: Text('Upload Media'),
|
floatingActionButtonLocation: ExpandableFab.location,
|
||||||
onPressed: () {},
|
floatingActionButton: ExpandableFab(
|
||||||
),
|
key: _fabKey,
|
||||||
FloatingActionButton.extended(
|
distance: 70,
|
||||||
icon: const Icon(Icons.note_add_outlined),
|
type: ExpandableFabType.up,
|
||||||
label: Text('Add Note'),
|
overlayStyle: ExpandableFabOverlayStyle(
|
||||||
onPressed: () {},
|
color: Colors.black.withValues(alpha: 0.5),
|
||||||
),
|
blur: 10,
|
||||||
FloatingActionButton.extended(
|
),
|
||||||
icon: const Icon(Icons.history_outlined),
|
onOpen: () {
|
||||||
label: Text('Restart'),
|
// pause the activity on open
|
||||||
onPressed: () {},
|
ActionTimer at =
|
||||||
),
|
Provider.of<ActionTimer>(context, listen: false);
|
||||||
FloatingActionButton.extended(
|
if (at.started) at.pause();
|
||||||
icon: const Icon(Icons.done_all_outlined),
|
},
|
||||||
label: Text('Done'),
|
children: [
|
||||||
onPressed: () {},
|
// FloatingActionButton.extended(
|
||||||
),
|
// icon: const Icon(Icons.upload_outlined),
|
||||||
]),
|
// label: Text('Upload Media'),
|
||||||
body: Column(
|
// onPressed: () {},
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
// ),
|
||||||
children: [
|
FloatingActionButton.extended(
|
||||||
AppBar(
|
icon: const Icon(Icons.done_all_outlined),
|
||||||
titleSpacing: 0,
|
label: Text('Edit Action'),
|
||||||
centerTitle: true,
|
onPressed: () {
|
||||||
title: const Text('Activity',
|
showEditorSheet(
|
||||||
style: TextStyle(fontSize: 15)),
|
context,
|
||||||
),
|
ActivityActionEditor(
|
||||||
Padding(
|
session: session,
|
||||||
padding: const EdgeInsets.only(
|
activity: activity,
|
||||||
left: 15, right: 20, top: 15, bottom: 10),
|
action: actions.first,
|
||||||
child: Text(
|
callback: resetState));
|
||||||
maxLines: 1,
|
},
|
||||||
style: const TextStyle(
|
),
|
||||||
fontSize: 25, fontWeight: FontWeight.bold),
|
FloatingActionButton.extended(
|
||||||
activity.title.toTitleCase())),
|
icon: const Icon(Icons.note_add_outlined),
|
||||||
Padding(
|
label: Text('Add Note'),
|
||||||
padding: const EdgeInsets.fromLTRB(10, 0, 0, 10),
|
onPressed: () {},
|
||||||
child: Flex(direction: Axis.horizontal, children: [
|
),
|
||||||
ActivityViewCategories(
|
FloatingActionButton.extended(
|
||||||
categories: [activity.category]),
|
icon: const Icon(Icons.history_outlined),
|
||||||
ActivityViewTypes(types: [activity.type])
|
label: Text('Restart'),
|
||||||
])),
|
onPressed: () {},
|
||||||
Padding(
|
),
|
||||||
padding: const EdgeInsets.only(
|
FloatingActionButton.extended(
|
||||||
top: 0, bottom: 10, left: 15, right: 15),
|
icon: const Icon(Icons.done_all_outlined),
|
||||||
child: Text(
|
label: Text('Done'),
|
||||||
textAlign: TextAlign.left,
|
onPressed: () {},
|
||||||
style: const TextStyle(fontSize: 15),
|
),
|
||||||
activity.description)),
|
]),
|
||||||
const Padding(
|
body: Column(
|
||||||
padding: EdgeInsets.fromLTRB(15, 20, 0, 10),
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
child: Text(
|
children: [
|
||||||
style: TextStyle(
|
AppBar(
|
||||||
fontSize: 20, fontWeight: FontWeight.bold),
|
titleSpacing: 0,
|
||||||
'Media:')),
|
centerTitle: true,
|
||||||
ActivityViewMedia(activity: activity),
|
title: const Text('Activity',
|
||||||
const Padding(
|
style: TextStyle(fontSize: 15)),
|
||||||
padding: EdgeInsets.fromLTRB(15, 30, 0, 10),
|
),
|
||||||
child: Text(
|
Padding(
|
||||||
textAlign: TextAlign.left,
|
padding: const EdgeInsets.only(
|
||||||
style: TextStyle(
|
left: 15, right: 20, top: 15, bottom: 10),
|
||||||
fontSize: 20, fontWeight: FontWeight.bold),
|
child: Text(
|
||||||
'Actions')),
|
maxLines: 1,
|
||||||
Padding(
|
style: const TextStyle(
|
||||||
padding: const EdgeInsets.only(left: 10, right: 10),
|
fontSize: 25,
|
||||||
child: Card(
|
fontWeight: FontWeight.bold),
|
||||||
clipBehavior: Clip.antiAlias,
|
activity.title.toTitleCase())),
|
||||||
shape: const RoundedRectangleBorder(
|
SizedBox(
|
||||||
borderRadius: BorderRadius.only(
|
height: 40,
|
||||||
topLeft: Radius.circular(10),
|
child: ListView(
|
||||||
topRight: Radius.circular(10)),
|
scrollDirection: Axis.horizontal,
|
||||||
),
|
padding:
|
||||||
color: Theme.of(context).colorScheme.onPrimary,
|
const EdgeInsets.fromLTRB(10, 0, 10, 0),
|
||||||
|
shrinkWrap: true,
|
||||||
|
children: [
|
||||||
|
ActivityViewCategories<List<ActivityLevel>>(
|
||||||
|
icon: Icon(Icons.stairs_rounded),
|
||||||
|
text: "Activity Level",
|
||||||
|
object: activity.level != null
|
||||||
|
? [activity.level!]
|
||||||
|
: []),
|
||||||
|
// ActivityViewCategories<List<ActivityMechanic>>(
|
||||||
|
// icon: Icon(Icons.),
|
||||||
|
// text: 'Activity Mechanic',
|
||||||
|
// object: activity.mechanic != null
|
||||||
|
// ? [activity.mechanic!]
|
||||||
|
// : []),
|
||||||
|
ActivityViewCategories<
|
||||||
|
List<ActivityEquipment>>(
|
||||||
|
icon:
|
||||||
|
Icon(Icons.fitness_center_rounded),
|
||||||
|
text: 'Equipment Used',
|
||||||
|
object: activity.equipment != null
|
||||||
|
? [activity.equipment!]
|
||||||
|
: []),
|
||||||
|
ActivityViewCategories<List<ActivityType>>(
|
||||||
|
icon: Icon(Icons.type_specimen_rounded),
|
||||||
|
text: 'Activity Type',
|
||||||
|
object: activity.type != null
|
||||||
|
? [activity.type!]
|
||||||
|
: []),
|
||||||
|
ActivityViewCategories<
|
||||||
|
List<ActivityMuscle>>(
|
||||||
|
icon: Icon(Icons.person),
|
||||||
|
text: 'Muscles used',
|
||||||
|
object: activityMuscle(activity))
|
||||||
|
])),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
top: 10, bottom: 0, left: 15, right: 15),
|
||||||
|
child: Text(
|
||||||
|
maxLines: 4,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
// softWrap: true,
|
||||||
|
textAlign: TextAlign.left,
|
||||||
|
style: const TextStyle(fontSize: 15),
|
||||||
|
jsonToDescription([
|
||||||
|
json.decode(activity.description ?? "")[0]
|
||||||
|
]))),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.only(right: 15),
|
||||||
|
child: Align(
|
||||||
|
alignment: Alignment.topRight,
|
||||||
|
child: TextButton(
|
||||||
|
style: ButtonStyle(
|
||||||
|
textStyle:
|
||||||
|
WidgetStateProperty.all<TextStyle>(
|
||||||
|
TextStyle(
|
||||||
|
fontWeight:
|
||||||
|
FontWeight.normal)),
|
||||||
|
shape: WidgetStateProperty.all<
|
||||||
|
RoundedRectangleBorder>(
|
||||||
|
RoundedRectangleBorder(
|
||||||
|
borderRadius:
|
||||||
|
BorderRadius.circular(10.0),
|
||||||
|
))),
|
||||||
|
onPressed: () {
|
||||||
|
showGenericSheet(
|
||||||
|
context,
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.all(15),
|
||||||
|
child: Text(
|
||||||
|
style:
|
||||||
|
TextStyle(fontSize: 18),
|
||||||
|
jsonToDescription(json.decode(
|
||||||
|
activity.description ??
|
||||||
|
"")))));
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
"read more",
|
||||||
|
textAlign: TextAlign.right,
|
||||||
|
style: TextStyle(fontSize: 12),
|
||||||
|
),
|
||||||
|
))),
|
||||||
|
const Padding(
|
||||||
|
padding: EdgeInsets.fromLTRB(15, 10, 0, 10),
|
||||||
|
child: Text(
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.bold),
|
||||||
|
'Media:')),
|
||||||
|
ActivityViewMedia(activity: activity),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(15, 20, 5, 0),
|
||||||
child: Row(children: [
|
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(
|
Expanded(
|
||||||
flex: 1,
|
child: const Text(
|
||||||
child: Stack(
|
textAlign: TextAlign.left,
|
||||||
alignment: Alignment.center,
|
style: TextStyle(
|
||||||
children: [
|
fontSize: 20,
|
||||||
Container(
|
fontWeight: FontWeight.bold),
|
||||||
alignment: Alignment.center,
|
'Actions')),
|
||||||
child: Consumer<ActivityTimerModel>(
|
IconButton(
|
||||||
builder: (context, atm, child) {
|
onPressed: () {
|
||||||
return Text(
|
showGenericSheet(
|
||||||
style: const TextStyle(
|
context,
|
||||||
fontSize: 20),
|
Column(children: [
|
||||||
textAlign: TextAlign.center,
|
ActivityActionView(
|
||||||
'${atm.actionCount} ${atm.currentAction['type']}'.toTitleCase());
|
session: session,
|
||||||
},
|
activity: activity,
|
||||||
),
|
actions: actions,
|
||||||
),
|
callback: resetState,
|
||||||
Container(
|
resetOnLoad: false)
|
||||||
alignment: Alignment.centerRight,
|
]),
|
||||||
padding:
|
Theme.of(context).colorScheme.surface);
|
||||||
EdgeInsets.only(right: 15),
|
},
|
||||||
child:
|
icon: Icon(Icons.expand),
|
||||||
Consumer<ActivityTimerModel>(
|
alignment: Alignment.bottomCenter,
|
||||||
builder: (context, atm,
|
)
|
||||||
child) {
|
])),
|
||||||
return Text(
|
ActivityActionView(
|
||||||
style: const TextStyle(
|
session: session,
|
||||||
fontSize: 12),
|
activity: activity,
|
||||||
textAlign: TextAlign.right,
|
actions: actions,
|
||||||
'${atm.currentAction['actionID'] + 1} of ${atm.totalActions()}');
|
callback: resetState)
|
||||||
})),
|
])));
|
||||||
])),
|
// ] +
|
||||||
]))),
|
// action(actions, context)));
|
||||||
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 {
|
} else {
|
||||||
return Container(
|
return Container(
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
|
|||||||
@@ -1,30 +1,35 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:sendtrain/database/database.dart';
|
|
||||||
import 'package:sendtrain/extensions/string_extensions.dart';
|
import 'package:sendtrain/extensions/string_extensions.dart';
|
||||||
|
|
||||||
class ActivityViewCategories extends StatelessWidget {
|
class ActivityViewCategories<T extends List<Enum>> extends StatelessWidget {
|
||||||
const ActivityViewCategories({super.key, required this.categories});
|
const ActivityViewCategories(
|
||||||
|
{super.key,
|
||||||
|
required this.object,
|
||||||
|
required this.icon,
|
||||||
|
required this.text});
|
||||||
|
|
||||||
final List<ActivityCategories> categories;
|
final T object;
|
||||||
|
final Icon icon;
|
||||||
|
final String text;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SizedBox(
|
return ListView.builder(
|
||||||
height: 40,
|
shrinkWrap: true,
|
||||||
child: ListView.builder(
|
scrollDirection: Axis.horizontal,
|
||||||
shrinkWrap: true,
|
// padding: const EdgeInsets.only(right: 10, left: 10),
|
||||||
scrollDirection: Axis.horizontal,
|
itemCount: object.length,
|
||||||
padding: const EdgeInsets.only(right: 10),
|
itemBuilder: (BuildContext context, int index) {
|
||||||
itemCount: categories.length,
|
return Padding(
|
||||||
itemBuilder: (BuildContext context, int index) {
|
padding: EdgeInsets.only(right: 5),
|
||||||
return ActionChip(
|
child: ActionChip(
|
||||||
visualDensity: VisualDensity.compact,
|
visualDensity: VisualDensity.compact,
|
||||||
avatar: const Icon(Icons.category_rounded),
|
avatar: icon,
|
||||||
label: Text(maxLines: 1, categories[index].name.toTitleCase()),
|
label: Text(maxLines: 1, object[index].name.toTitleCase()),
|
||||||
tooltip: "Activity Category",
|
tooltip: text,
|
||||||
onPressed: () {},
|
onPressed: () {},
|
||||||
);
|
));
|
||||||
},
|
},
|
||||||
));
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:sendtrain/providers/action_timer.dart';
|
||||||
|
|
||||||
Future showGenericDialog(dynamic object, BuildContext parentContext) {
|
Future showGenericDialog(dynamic object, BuildContext parentContext) {
|
||||||
return showGeneralDialog(
|
return showGeneralDialog(
|
||||||
barrierColor: Colors.black.withOpacity(0.5),
|
barrierColor: Colors.black.withValues(alpha: 0.5),
|
||||||
transitionDuration: const Duration(milliseconds: 220),
|
transitionDuration: const Duration(milliseconds: 220),
|
||||||
transitionBuilder: (BuildContext context, Animation<double> animation,
|
transitionBuilder: (BuildContext context, Animation<double> animation,
|
||||||
Animation<double> secondaryAnimation, Widget child) {
|
Animation<double> secondaryAnimation, Widget child) {
|
||||||
@@ -20,8 +22,8 @@ Future showGenericDialog(dynamic object, BuildContext parentContext) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future showRemovalDialog(String title, String content, BuildContext context,
|
Future showCrudDialog(String title, String content, BuildContext context,
|
||||||
dynamic dao, dynamic object) {
|
[Function? callback]) {
|
||||||
return showAdaptiveDialog(
|
return showAdaptiveDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext context) => AlertDialog(
|
builder: (BuildContext context) => AlertDialog(
|
||||||
@@ -35,10 +37,71 @@ Future showRemovalDialog(String title, String content, BuildContext context,
|
|||||||
child: const Text('Cancel'),
|
child: const Text('Cancel'),
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => {dao.remove(object), Navigator.pop(context, 'OK')},
|
onPressed: () => {
|
||||||
|
if (callback != null) {callback()},
|
||||||
|
Navigator.pop(context, 'OK')
|
||||||
|
},
|
||||||
child: const Text('OK'),
|
child: const Text('OK'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future showRemovalDialog(String title, String content, BuildContext context,
|
||||||
|
[Function? callback]) {
|
||||||
|
return showCrudDialog(title, content, context, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class AddCardGeneric extends StatelessWidget {
|
||||||
|
const AddCardGeneric(
|
||||||
|
{super.key, required this.title, required this.description, this.action});
|
||||||
|
|
||||||
|
final String title;
|
||||||
|
final String description;
|
||||||
|
final Function? action;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Expanded(
|
||||||
|
child: ListView(
|
||||||
|
padding: const EdgeInsets.fromLTRB(10, 0, 10, 0),
|
||||||
|
children: [
|
||||||
|
Card.outlined(
|
||||||
|
child: InkWell(
|
||||||
|
customBorder: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
if (action != null) {
|
||||||
|
action!();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: ListTile(
|
||||||
|
contentPadding:
|
||||||
|
EdgeInsets.only(top: 5, left: 15, right: 5, bottom: 5),
|
||||||
|
autofocus: true,
|
||||||
|
leading: Icon(Icons.add_box_rounded),
|
||||||
|
title: Text(title),
|
||||||
|
subtitle: Text(description),
|
||||||
|
)))
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,17 +1,37 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:math';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:sendtrain/services/apis/google_places_service.dart';
|
|
||||||
import 'package:sendtrain/services/functional/debouncer.dart';
|
import 'package:sendtrain/services/functional/debouncer.dart';
|
||||||
import 'package:sendtrain/widgets/generic/elements/form_text_input.dart';
|
import 'package:sendtrain/widgets/generic/elements/form_text_input.dart';
|
||||||
|
|
||||||
|
class Suggestion<T> {
|
||||||
|
T content;
|
||||||
|
|
||||||
|
Suggestion(this.content);
|
||||||
|
|
||||||
|
Widget resultWidget() {
|
||||||
|
return ListTile(
|
||||||
|
title: Text('test'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// controller: manages the selected content
|
||||||
|
// service: manages the requests for the specific data to search against
|
||||||
|
// title: the title of the text input
|
||||||
|
// callback: the fuction called when a selection is made
|
||||||
class FormSearchInput extends StatefulWidget {
|
class FormSearchInput extends StatefulWidget {
|
||||||
const FormSearchInput(
|
const FormSearchInput(
|
||||||
{super.key, required this.sessionController, this.optionalPayload});
|
{super.key,
|
||||||
|
required this.controller,
|
||||||
|
required this.service,
|
||||||
|
required this.resultHandler,
|
||||||
|
this.title});
|
||||||
|
|
||||||
final TextEditingController sessionController;
|
final String? title;
|
||||||
final dynamic optionalPayload;
|
final TextEditingController controller;
|
||||||
|
final dynamic service;
|
||||||
|
final Function resultHandler;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<FormSearchInput> createState() => _FormSearchInputState();
|
State<FormSearchInput> createState() => _FormSearchInputState();
|
||||||
@@ -20,22 +40,26 @@ class FormSearchInput extends StatefulWidget {
|
|||||||
class _FormSearchInputState extends State<FormSearchInput> {
|
class _FormSearchInputState extends State<FormSearchInput> {
|
||||||
String? _currentQuery;
|
String? _currentQuery;
|
||||||
|
|
||||||
final service = GooglePlacesService();
|
late final service = widget.service;
|
||||||
|
late final resultHandler = widget.resultHandler;
|
||||||
// The most recent suggestions received from the API.
|
// The most recent suggestions received from the API.
|
||||||
late Iterable<Widget> _lastOptions = <Widget>[];
|
late Iterable<Widget> _lastOptions = <Widget>[];
|
||||||
late final Debouncer debouncer;
|
late final Debouncer debouncer;
|
||||||
|
|
||||||
|
// @override
|
||||||
|
// initState() {
|
||||||
|
// service = widget.service;
|
||||||
|
// }
|
||||||
|
|
||||||
// Calls the "remote" API to search with the given query. Returns null when
|
// Calls the "remote" API to search with the given query. Returns null when
|
||||||
// the call has been made obsolete.
|
// the call has been made obsolete.
|
||||||
Future<Iterable<Suggestion>?> _search(String query) async {
|
Future<Iterable<Suggestion>?> _search(String query) async {
|
||||||
_currentQuery = query;
|
_currentQuery = query;
|
||||||
|
|
||||||
// In a real application, there should be some error handling here.
|
// In a real application, there should be some error handling here.
|
||||||
// final Iterable<String> options = await _FakeAPI.search(_currentQuery!);
|
if (query.isNotEmpty && query.length > 3) {
|
||||||
if (query.isNotEmpty) {
|
|
||||||
final List<Suggestion>? suggestions =
|
final List<Suggestion>? suggestions =
|
||||||
await service.fetchSuggestions(_currentQuery!, 'en');
|
await service.fetchSuggestions(_currentQuery!);
|
||||||
|
|
||||||
// If another search happened after this one, throw away these options.
|
// If another search happened after this one, throw away these options.
|
||||||
if (_currentQuery != query) {
|
if (_currentQuery != query) {
|
||||||
@@ -58,48 +82,35 @@ class _FormSearchInputState extends State<FormSearchInput> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SearchAnchor(
|
return SearchAnchor(
|
||||||
|
isFullScreen: false,
|
||||||
builder: (BuildContext context, SearchController controller) {
|
builder: (BuildContext context, SearchController controller) {
|
||||||
return FormTextInput(
|
return FormTextInput(
|
||||||
controller: widget.sessionController,
|
controller: widget.controller,
|
||||||
title: 'Location (optional)',
|
title: widget.title ?? "",
|
||||||
icon: Icon(Icons.search_rounded),
|
icon: Icon(Icons.search_rounded),
|
||||||
maxLines: 2,
|
maxLines: 2,
|
||||||
requiresValidation: false,
|
requiresValidation: false,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
controller.openView();
|
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];
|
|
||||||
return ListTile(
|
|
||||||
title: Text(item.description),
|
|
||||||
onTap: () async {
|
|
||||||
// widget.optionalPayload = service.fetchPhoto(json.decode(item.image));
|
|
||||||
if (item.imageReferences != null) {
|
|
||||||
// get a random photo item from the returned result
|
|
||||||
Map<String, dynamic> photo = item.imageReferences![
|
|
||||||
Random().nextInt(item.imageReferences!.length)];
|
|
||||||
|
|
||||||
await service.fetchPhoto(photo['name']).then((photoMap) {
|
|
||||||
widget.optionalPayload.photoUri = photoMap['photoUri'];
|
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
widget.optionalPayload.address = item.address;
|
return _lastOptions;
|
||||||
widget.sessionController.text = item.description;
|
});
|
||||||
service.finish();
|
|
||||||
controller.closeView(item.description);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
return _lastOptions;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
|
enum InputTypes { text, number }
|
||||||
|
|
||||||
class FormTextInput extends StatelessWidget {
|
class FormTextInput extends StatelessWidget {
|
||||||
const FormTextInput(
|
const FormTextInput(
|
||||||
@@ -9,7 +12,10 @@ class FormTextInput extends StatelessWidget {
|
|||||||
this.maxLines,
|
this.maxLines,
|
||||||
this.minLines,
|
this.minLines,
|
||||||
this.onTap,
|
this.onTap,
|
||||||
this.requiresValidation=true});
|
this.requiresValidation = true,
|
||||||
|
this.type = InputTypes.text,
|
||||||
|
this.hint,
|
||||||
|
this.validations});
|
||||||
|
|
||||||
final TextEditingController controller;
|
final TextEditingController controller;
|
||||||
final String title;
|
final String title;
|
||||||
@@ -18,12 +24,25 @@ class FormTextInput extends StatelessWidget {
|
|||||||
final Icon? icon;
|
final Icon? icon;
|
||||||
final dynamic onTap;
|
final dynamic onTap;
|
||||||
final bool requiresValidation;
|
final bool requiresValidation;
|
||||||
|
final InputTypes type;
|
||||||
|
final String? hint;
|
||||||
|
final Function? validations;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final Map params = {};
|
||||||
|
if (type == InputTypes.number) {
|
||||||
|
params['keyboardType'] = TextInputType.number;
|
||||||
|
params['inputFormatters'] = <TextInputFormatter>[
|
||||||
|
FilteringTextInputFormatter.digitsOnly
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: EdgeInsets.only(top: 10, bottom: 10),
|
padding: EdgeInsets.only(top: 10, bottom: 10),
|
||||||
child: TextFormField(
|
child: TextFormField(
|
||||||
|
keyboardType: params['keyboardType'] ?? TextInputType.text,
|
||||||
|
inputFormatters: params['inputFormatters'] ?? [],
|
||||||
minLines: minLines ?? 1,
|
minLines: minLines ?? 1,
|
||||||
maxLines: maxLines ?? 1,
|
maxLines: maxLines ?? 1,
|
||||||
controller: controller,
|
controller: controller,
|
||||||
@@ -34,6 +53,7 @@ class FormTextInput extends StatelessWidget {
|
|||||||
borderSide: BorderSide.none,
|
borderSide: BorderSide.none,
|
||||||
borderRadius: BorderRadius.circular(12)),
|
borderRadius: BorderRadius.circular(12)),
|
||||||
labelText: title,
|
labelText: title,
|
||||||
|
hintText: hint ?? '',
|
||||||
),
|
),
|
||||||
validator: (String? value) {
|
validator: (String? value) {
|
||||||
if (requiresValidation == true) {
|
if (requiresValidation == true) {
|
||||||
@@ -41,9 +61,11 @@ class FormTextInput extends StatelessWidget {
|
|||||||
return 'Please enter some text';
|
return 'Please enter some text';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value.length < 3) {
|
if (validations != null) validations!(value);
|
||||||
return 'Please enter a minimum of 3 characters';
|
|
||||||
}
|
// if (value.length < 3) {
|
||||||
|
// return 'Please enter a minimum of 3 characters';
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,24 +1,35 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:sendtrain/daos/media_items_dao.dart';
|
||||||
import 'package:sendtrain/database/database.dart';
|
import 'package:sendtrain/database/database.dart';
|
||||||
import 'package:sendtrain/helpers/widget_helpers.dart';
|
import 'package:sendtrain/helpers/widget_helpers.dart';
|
||||||
|
import 'package:sendtrain/widgets/builders/dialogs.dart';
|
||||||
|
|
||||||
class MediaCard extends StatelessWidget {
|
class MediaCard extends StatelessWidget {
|
||||||
const MediaCard({super.key, required this.media});
|
const MediaCard(
|
||||||
|
{super.key, required this.media, this.callback, this.canDelete});
|
||||||
|
|
||||||
final MediaItem media;
|
final MediaItem media;
|
||||||
|
final bool? canDelete;
|
||||||
|
final Function? callback;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
DecorationImage mediaImage(MediaItem media) {
|
DecorationImage mediaImage(MediaItem media) {
|
||||||
String image = '';
|
dynamic image;
|
||||||
|
|
||||||
if (media.type == MediaType.image || media.type == MediaType.location) {
|
if (media.type == MediaType.image || media.type == MediaType.location) {
|
||||||
image = media.reference;
|
image = NetworkImage(media.reference);
|
||||||
|
} else if (media.type == MediaType.localImage) {
|
||||||
|
image = Image.memory(base64Decode(media.reference)).image;
|
||||||
} else if (media.type == MediaType.youtube) {
|
} else if (media.type == MediaType.youtube) {
|
||||||
image = 'https://img.youtube.com/vi/${media.reference}/0.jpg';
|
image =
|
||||||
}
|
NetworkImage('https://img.youtube.com/vi/${media.reference}/0.jpg');
|
||||||
|
} else if (media.type == MediaType.localVideo) {}
|
||||||
|
|
||||||
return DecorationImage(image: NetworkImage(image), fit: BoxFit.cover);
|
return DecorationImage(image: image, fit: BoxFit.cover);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
@@ -32,6 +43,22 @@ class MediaCard extends StatelessWidget {
|
|||||||
RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
||||||
shadowColor: const Color.fromARGB(0, 255, 255, 255),
|
shadowColor: const Color.fromARGB(0, 255, 255, 255),
|
||||||
child: TextButton(
|
child: TextButton(
|
||||||
|
onLongPress: () {
|
||||||
|
if (canDelete == true) {
|
||||||
|
showRemovalDialog(
|
||||||
|
'Media Removal',
|
||||||
|
'Would you like to permanently remove this media from the current session?',
|
||||||
|
context, () {
|
||||||
|
MediaItemsDao(
|
||||||
|
Provider.of<AppDatabase>(context, listen: false))
|
||||||
|
.remove(media);
|
||||||
|
}).then((result) {
|
||||||
|
if (callback != null) {
|
||||||
|
callback!();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
onPressed: () => showMediaDetailWidget(context, media),
|
onPressed: () => showMediaDetailWidget(context, media),
|
||||||
child: const ListTile(
|
child: const ListTile(
|
||||||
title: Text(''),
|
title: Text(''),
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:sendtrain/database/database.dart';
|
import 'package:sendtrain/database/database.dart';
|
||||||
import 'package:youtube_player_flutter/youtube_player_flutter.dart';
|
import 'package:youtube_player_flutter/youtube_player_flutter.dart';
|
||||||
@@ -16,6 +18,8 @@ class MediaContent extends StatelessWidget {
|
|||||||
|
|
||||||
if (media.type == MediaType.image || media.type == MediaType.location) {
|
if (media.type == MediaType.image || media.type == MediaType.location) {
|
||||||
return Image(image: NetworkImage(media.reference));
|
return Image(image: NetworkImage(media.reference));
|
||||||
|
} else if (media.type == MediaType.localImage) {
|
||||||
|
return Image.memory(base64Decode(media.reference));
|
||||||
} else if (media.type == MediaType.youtube) {
|
} else if (media.type == MediaType.youtube) {
|
||||||
return YoutubePlayer(
|
return YoutubePlayer(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
|
|||||||
@@ -11,9 +11,8 @@ class MediaDetails extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: EdgeInsets.fromLTRB(15, 0, 15, 15),
|
padding: EdgeInsets.fromLTRB(15, 0, 15, 15),
|
||||||
child: Column(
|
child: ListView(
|
||||||
mainAxisSize: MainAxisSize.min,
|
shrinkWrap: true,
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
ClipRRect(
|
ClipRRect(
|
||||||
borderRadius: BorderRadius.circular(10),
|
borderRadius: BorderRadius.circular(10),
|
||||||
@@ -26,6 +25,7 @@ class MediaDetails extends StatelessWidget {
|
|||||||
media.description,
|
media.description,
|
||||||
style: const TextStyle(fontSize: 20),
|
style: const TextStyle(fontSize: 20),
|
||||||
)),
|
)),
|
||||||
|
|
||||||
const Divider(
|
const Divider(
|
||||||
indent: 20,
|
indent: 20,
|
||||||
endIndent: 20,
|
endIndent: 20,
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import 'package:drift/drift.dart' hide Column;
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:sendtrain/daos/session_activities_dao.dart';
|
||||||
|
import 'package:sendtrain/database/database.dart';
|
||||||
|
import 'package:sendtrain/services/search/activity_finder_service.dart';
|
||||||
|
import 'package:sendtrain/widgets/generic/elements/form_search_input.dart';
|
||||||
|
|
||||||
|
class SessionActivitiesEditor extends StatelessWidget {
|
||||||
|
SessionActivitiesEditor({super.key, required this.session, this.callback});
|
||||||
|
|
||||||
|
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
|
||||||
|
final TextEditingController tec = TextEditingController();
|
||||||
|
final Session session;
|
||||||
|
final Function? callback;
|
||||||
|
late final Activity selectedActivity;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(
|
||||||
|
BuildContext context,
|
||||||
|
) {
|
||||||
|
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('Add Activity',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: Theme.of(context).textTheme.titleLarge)),
|
||||||
|
FormSearchInput(
|
||||||
|
title: 'Find an Activity',
|
||||||
|
controller: tec,
|
||||||
|
service: ActivityFinderService(context),
|
||||||
|
resultHandler: (Activity content,
|
||||||
|
ActivityFinderService service) async {
|
||||||
|
tec.text = content.title;
|
||||||
|
selectedActivity = content;
|
||||||
|
}),
|
||||||
|
Row(mainAxisAlignment: MainAxisAlignment.end, children: [
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.only(top: 10),
|
||||||
|
child: FilledButton(
|
||||||
|
child: Text('Submit'),
|
||||||
|
onPressed: () async {
|
||||||
|
final SessionActivitiesDao dao =
|
||||||
|
SessionActivitiesDao(
|
||||||
|
Provider.of<AppDatabase>(context, listen: false));
|
||||||
|
|
||||||
|
await dao.createOrUpdate(SessionActivitiesCompanion(
|
||||||
|
sessionId: Value(session.id),
|
||||||
|
activityId: Value(selectedActivity.id),
|
||||||
|
position: Value(0),
|
||||||
|
));
|
||||||
|
|
||||||
|
Navigator.pop(_formKey.currentContext!, 'Submit');
|
||||||
|
|
||||||
|
if (callback != null) {
|
||||||
|
await callback!();
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
])
|
||||||
|
])));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,9 +24,9 @@ class SessionCardFull extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _SessionCardFullState extends State<SessionCardFull> {
|
class _SessionCardFullState extends State<SessionCardFull> {
|
||||||
late final List<MediaItem> mediaItems;
|
// late final List<MediaItem> mediaItems;
|
||||||
late final MediaItem? sessionImage;
|
// late final MediaItem? sessionImage;
|
||||||
late final Session session;
|
// late final Session session;
|
||||||
|
|
||||||
String sessionTitle(Session session) {
|
String sessionTitle(Session session) {
|
||||||
String title = session.title.toTitleCase();
|
String title = session.title.toTitleCase();
|
||||||
@@ -36,18 +36,23 @@ class _SessionCardFullState extends State<SessionCardFull> {
|
|||||||
return title;
|
return title;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
// @override
|
||||||
initState() {
|
// initState() {
|
||||||
super.initState();
|
// super.initState();
|
||||||
session = widget.session;
|
// session = widget.session;
|
||||||
mediaItems = widget.mediaItems;
|
// mediaItems = widget.mediaItems;
|
||||||
sessionImage = mediaItems
|
// sessionImage = mediaItems
|
||||||
.firstWhereOrNull((mediaItem) => mediaItem.type == MediaType.location);
|
// .firstWhereOrNull((mediaItem) => mediaItem.type == MediaType.location);
|
||||||
}
|
// }
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Card(
|
final Session session = widget.session;
|
||||||
|
final List<MediaItem> mediaItems = widget.mediaItems;
|
||||||
|
final MediaItem? sessionImage = mediaItems
|
||||||
|
.firstWhereOrNull((mediaItem) => mediaItem.type == MediaType.location);
|
||||||
|
|
||||||
|
return Card.outlined(
|
||||||
color: (session.status == SessionStatus.started)
|
color: (session.status == SessionStatus.started)
|
||||||
? Theme.of(context).colorScheme.primaryContainer
|
? Theme.of(context).colorScheme.primaryContainer
|
||||||
: Theme.of(context).colorScheme.surfaceContainerLow,
|
: Theme.of(context).colorScheme.surfaceContainerLow,
|
||||||
@@ -75,13 +80,13 @@ class _SessionCardFullState extends State<SessionCardFull> {
|
|||||||
icon: Icon(Icons.close_rounded),
|
icon: Icon(Icons.close_rounded),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
showRemovalDialog(
|
showRemovalDialog(
|
||||||
'Session Removal',
|
'Session Removal',
|
||||||
'Would you like to permanently remove this session?',
|
'Would you like to permanently remove this session?',
|
||||||
context,
|
context, () {
|
||||||
SessionsDao(Provider.of<AppDatabase>(context,
|
SessionsDao(
|
||||||
listen: false)),
|
Provider.of<AppDatabase>(context, listen: false))
|
||||||
session)
|
.remove(session);
|
||||||
.then((result) {
|
}).then((result) {
|
||||||
setState(() {});
|
setState(() {});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -20,20 +20,25 @@ class SessionCardSmall extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _SessionCardSmallState extends State<SessionCardSmall> {
|
class _SessionCardSmallState extends State<SessionCardSmall> {
|
||||||
late final List<MediaItem> mediaItems;
|
// late final List<MediaItem> mediaItems;
|
||||||
late final MediaItem? sessionImage;
|
// late final MediaItem? sessionImage;
|
||||||
late final Session session;
|
// late final Session session;
|
||||||
|
|
||||||
@override
|
// @override
|
||||||
initState() {
|
// initState() {
|
||||||
super.initState();
|
// super.initState();
|
||||||
session = widget.session;
|
// session = widget.session;
|
||||||
mediaItems = widget.mediaItems;
|
// mediaItems = widget.mediaItems;
|
||||||
sessionImage = mediaItems.firstWhereOrNull((mediaItem) => mediaItem.type == MediaType.location);
|
// sessionImage = mediaItems.firstWhereOrNull((mediaItem) => mediaItem.type == MediaType.location);
|
||||||
}
|
// }
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final Session session = widget.session;
|
||||||
|
final List<MediaItem> mediaItems = widget.mediaItems;
|
||||||
|
final MediaItem? sessionImage = mediaItems
|
||||||
|
.firstWhereOrNull((mediaItem) => mediaItem.type == MediaType.location);
|
||||||
|
|
||||||
return Card(
|
return Card(
|
||||||
color: (session.status == SessionStatus.started)
|
color: (session.status == SessionStatus.started)
|
||||||
? Theme.of(context).colorScheme.primaryContainer
|
? Theme.of(context).colorScheme.primaryContainer
|
||||||
@@ -42,7 +47,11 @@ class _SessionCardSmallState extends State<SessionCardSmall> {
|
|||||||
// overlayColor: MaterialStateColor(Colors.deepPurple as int),
|
// overlayColor: MaterialStateColor(Colors.deepPurple as int),
|
||||||
splashColor: Colors.deepPurple,
|
splashColor: Colors.deepPurple,
|
||||||
borderRadius: const BorderRadius.all(Radius.elliptical(10, 10)),
|
borderRadius: const BorderRadius.all(Radius.elliptical(10, 10)),
|
||||||
onLongPress: () => showMediaDetailWidget(context, sessionImage!),
|
onLongPress: () {
|
||||||
|
if (sessionImage != null) {
|
||||||
|
showMediaDetailWidget(context, sessionImage);
|
||||||
|
}
|
||||||
|
},
|
||||||
onTap: () =>
|
onTap: () =>
|
||||||
showGenericDialog(SessionView(session: session), context),
|
showGenericDialog(SessionView(session: session), context),
|
||||||
child: Container(
|
child: Container(
|
||||||
|
|||||||
@@ -1,13 +1,18 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:drift/drift.dart' hide Column;
|
import 'package:drift/drift.dart' hide Column;
|
||||||
|
import 'package:file_picker/file_picker.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:mime/mime.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:sendtrain/daos/media_items_dao.dart';
|
import 'package:sendtrain/daos/media_items_dao.dart';
|
||||||
import 'package:sendtrain/daos/object_media_items_dao.dart';
|
import 'package:sendtrain/daos/object_media_items_dao.dart';
|
||||||
import 'package:sendtrain/daos/sessions_dao.dart';
|
import 'package:sendtrain/daos/sessions_dao.dart';
|
||||||
import 'package:sendtrain/database/database.dart';
|
import 'package:sendtrain/database/database.dart';
|
||||||
|
import 'package:sendtrain/services/search/google_places_service.dart';
|
||||||
import 'package:sendtrain/widgets/builders/dialogs.dart';
|
import 'package:sendtrain/widgets/builders/dialogs.dart';
|
||||||
import 'package:sendtrain/widgets/generic/elements/form_search_input.dart';
|
import 'package:sendtrain/widgets/generic/elements/form_search_input.dart';
|
||||||
import 'package:sendtrain/widgets/generic/elements/form_text_input.dart';
|
import 'package:sendtrain/widgets/generic/elements/form_text_input.dart';
|
||||||
@@ -28,6 +33,7 @@ class SessionEditor extends StatefulWidget {
|
|||||||
class SessionPayload {
|
class SessionPayload {
|
||||||
String? photoUri;
|
String? photoUri;
|
||||||
String? address;
|
String? address;
|
||||||
|
List<PlatformFile>? files;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _SessionEditorState extends State<SessionEditor> {
|
class _SessionEditorState extends State<SessionEditor> {
|
||||||
@@ -95,32 +101,43 @@ class _SessionEditorState extends State<SessionEditor> {
|
|||||||
.createOrUpdate(Function.apply(SessionsCompanion.new, [], payload));
|
.createOrUpdate(Function.apply(SessionsCompanion.new, [], payload));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future createSessionMedia(context, sessionId) async {
|
Future deleteSessionMedia(int sessionId, MediaType mediaType) async {
|
||||||
List<MediaItem> deletedMedia =
|
List<MediaItem> deletedMedia =
|
||||||
await MediaItemsDao(db).fromSession(sessionId)
|
(await MediaItemsDao(db).fromSession(sessionId))
|
||||||
..where((mediaItem) => mediaItem.type == MediaType.location);
|
.where((mediaItem) => mediaItem.type == mediaType)
|
||||||
|
.toList();
|
||||||
|
|
||||||
await MediaItemsDao(db).removeAll(deletedMedia.map((m) => m.id));
|
for (int i = 0; i < deletedMedia.length; i++) {
|
||||||
|
await MediaItemsDao(db).remove(deletedMedia[i]);
|
||||||
if (sessionPayload.photoUri != null) {
|
|
||||||
MediaItemsCompanion mediaItem = MediaItemsCompanion(
|
|
||||||
title: Value('Location Image'),
|
|
||||||
description: Value(sessionPayload.address!),
|
|
||||||
reference: Value(sessionPayload.photoUri!),
|
|
||||||
type: Value(MediaType.location));
|
|
||||||
|
|
||||||
return await MediaItemsDao(db).createOrUpdate(mediaItem).then((id) async {
|
|
||||||
ObjectMediaItemsCompanion omi = ObjectMediaItemsCompanion(
|
|
||||||
objectId: Value(sessionId),
|
|
||||||
objectType: Value(ObjectType.sessions),
|
|
||||||
mediaId: Value(id),
|
|
||||||
);
|
|
||||||
|
|
||||||
await ObjectMediaItemsDao(db).createOrUpdate(omi);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future createSessionMedia(
|
||||||
|
title,
|
||||||
|
sessionId,
|
||||||
|
description,
|
||||||
|
reference,
|
||||||
|
mediaType,
|
||||||
|
) async {
|
||||||
|
// if (sessionPayload.photoUri != null) {
|
||||||
|
MediaItemsCompanion mediaItem = MediaItemsCompanion(
|
||||||
|
title: Value(title),
|
||||||
|
description: Value(description),
|
||||||
|
reference: Value(reference.toString()),
|
||||||
|
type: Value(mediaType));
|
||||||
|
|
||||||
|
return await MediaItemsDao(db).createOrUpdate(mediaItem).then((id) async {
|
||||||
|
ObjectMediaItemsCompanion omi = ObjectMediaItemsCompanion(
|
||||||
|
objectId: Value(sessionId),
|
||||||
|
objectType: Value(ObjectType.sessions),
|
||||||
|
mediaId: Value(id),
|
||||||
|
);
|
||||||
|
|
||||||
|
await ObjectMediaItemsDao(db).createOrUpdate(omi);
|
||||||
|
});
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
sessionCreateController['date']!.text =
|
sessionCreateController['date']!.text =
|
||||||
@@ -164,33 +181,54 @@ class _SessionEditorState extends State<SessionEditor> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
// Padding(
|
|
||||||
// padding: EdgeInsets.only(top: 10, bottom: 10),
|
|
||||||
// child: TextFormField(
|
|
||||||
// readOnly: true,
|
|
||||||
// decoration: InputDecoration(
|
|
||||||
// prefixIcon: Icon(Icons.image_rounded),
|
|
||||||
// filled: true,
|
|
||||||
// border: OutlineInputBorder(
|
|
||||||
// borderSide: BorderSide.none,
|
|
||||||
// borderRadius: BorderRadius.circular(12),
|
|
||||||
// ),
|
|
||||||
// labelText: 'Select Media (optional)',
|
|
||||||
// ),
|
|
||||||
// controller: sessionCreateController['media'],
|
|
||||||
// onTap: () async {
|
|
||||||
// FilePickerResult? result = await FilePicker.platform
|
|
||||||
// .pickFiles(allowMultiple: true);
|
|
||||||
|
|
||||||
// if (result != null) {
|
|
||||||
// List<File> files = result.paths
|
|
||||||
// .map((path) => File(path!))
|
|
||||||
// .toList();
|
|
||||||
// }
|
|
||||||
// })),
|
|
||||||
FormSearchInput(
|
FormSearchInput(
|
||||||
sessionController: sessionCreateController['address']!,
|
title: 'Location (optional)',
|
||||||
optionalPayload: sessionPayload),
|
controller: sessionCreateController['address']!,
|
||||||
|
service: GooglePlacesService(),
|
||||||
|
resultHandler: (content, service) async {
|
||||||
|
if (content.imageReferences != null) {
|
||||||
|
// get a random photo item from the returned result
|
||||||
|
Map<String, dynamic> photo = content.imageReferences![
|
||||||
|
Random().nextInt(content.imageReferences!.length)];
|
||||||
|
|
||||||
|
await service
|
||||||
|
.fetchPhoto(photo['name'])
|
||||||
|
.then((photoMap) {
|
||||||
|
sessionPayload.photoUri = photoMap['photoUri'];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionPayload.address = content.address;
|
||||||
|
sessionCreateController['address']!.text =
|
||||||
|
content.description;
|
||||||
|
service.finish();
|
||||||
|
}),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.only(top: 10, bottom: 10),
|
||||||
|
child: TextFormField(
|
||||||
|
readOnly: true,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
prefixIcon: Icon(Icons.image_rounded),
|
||||||
|
filled: true,
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderSide: BorderSide.none,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
labelText: 'Media (optional)',
|
||||||
|
),
|
||||||
|
controller: sessionCreateController['media'],
|
||||||
|
onTap: () async {
|
||||||
|
FilePickerResult? result = await FilePicker.platform
|
||||||
|
.pickFiles(
|
||||||
|
allowMultiple: true, type: FileType.image);
|
||||||
|
|
||||||
|
if (result != null) {
|
||||||
|
List<PlatformFile> files = result.files;
|
||||||
|
sessionCreateController['media']!.text =
|
||||||
|
files.map((file) => file.name).toString();
|
||||||
|
sessionPayload.files = files;
|
||||||
|
}
|
||||||
|
})),
|
||||||
Row(mainAxisAlignment: MainAxisAlignment.end, children: [
|
Row(mainAxisAlignment: MainAxisAlignment.end, children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.only(top: 10),
|
padding: EdgeInsets.only(top: 10),
|
||||||
@@ -211,9 +249,44 @@ class _SessionEditorState extends State<SessionEditor> {
|
|||||||
|
|
||||||
// if we've found a photo add it to media!
|
// if we've found a photo add it to media!
|
||||||
if (sessionPayload.photoUri != null) {
|
if (sessionPayload.photoUri != null) {
|
||||||
|
await deleteSessionMedia(
|
||||||
|
currentSessionId,
|
||||||
|
MediaType.location);
|
||||||
await createSessionMedia(
|
await createSessionMedia(
|
||||||
_formKey.currentContext,
|
'Location Image',
|
||||||
currentSessionId);
|
currentSessionId,
|
||||||
|
sessionPayload.address,
|
||||||
|
sessionPayload.photoUri,
|
||||||
|
MediaType.location);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we've selected files to save, save them
|
||||||
|
if (sessionPayload.files != null) {
|
||||||
|
for (int i = 0;
|
||||||
|
i < sessionPayload.files!.length;
|
||||||
|
i++) {
|
||||||
|
PlatformFile file =
|
||||||
|
sessionPayload.files![i];
|
||||||
|
String? type =
|
||||||
|
lookupMimeType(file.path!)!
|
||||||
|
.split('/')
|
||||||
|
.first;
|
||||||
|
Uint8List fileBytes =
|
||||||
|
await file.xFile.readAsBytes();
|
||||||
|
|
||||||
|
MediaType mediaType =
|
||||||
|
MediaType.localImage;
|
||||||
|
if (type == "video") {
|
||||||
|
mediaType = MediaType.localVideo;
|
||||||
|
}
|
||||||
|
|
||||||
|
await createSessionMedia(
|
||||||
|
'Local Media',
|
||||||
|
currentSessionId,
|
||||||
|
file.name,
|
||||||
|
base64Encode(fileBytes),
|
||||||
|
mediaType);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if session is null it's new so we show the dialog
|
// if session is null it's new so we show the dialog
|
||||||
@@ -232,8 +305,7 @@ class _SessionEditorState extends State<SessionEditor> {
|
|||||||
_formKey.currentContext!, 'Submit');
|
_formKey.currentContext!, 'Submit');
|
||||||
|
|
||||||
if (widget.callback != null) {
|
if (widget.callback != null) {
|
||||||
await widget
|
await widget.callback!();
|
||||||
.callback!(widget.session!.id);
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,13 @@ import 'package:intl/intl.dart';
|
|||||||
import 'package:intl/date_symbol_data_local.dart';
|
import 'package:intl/date_symbol_data_local.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import 'package:sendtrain/daos/activities_dao.dart';
|
|
||||||
import 'package:sendtrain/daos/sessions_dao.dart';
|
import 'package:sendtrain/daos/sessions_dao.dart';
|
||||||
import 'package:sendtrain/database/database.dart';
|
import 'package:sendtrain/database/database.dart';
|
||||||
import 'package:sendtrain/extensions/string_extensions.dart';
|
import 'package:sendtrain/extensions/string_extensions.dart';
|
||||||
|
import 'package:sendtrain/helpers/widget_helpers.dart';
|
||||||
|
import 'package:sendtrain/widgets/achievements/achievement_editor.dart';
|
||||||
import 'package:sendtrain/widgets/generic/elements/generic_progress_indicator.dart';
|
import 'package:sendtrain/widgets/generic/elements/generic_progress_indicator.dart';
|
||||||
|
import 'package:sendtrain/widgets/sessions/session_activities_editor.dart';
|
||||||
import 'package:sendtrain/widgets/sessions/session_editor.dart';
|
import 'package:sendtrain/widgets/sessions/session_editor.dart';
|
||||||
import 'package:sendtrain/widgets/sessions/session_view_achievements.dart';
|
import 'package:sendtrain/widgets/sessions/session_view_achievements.dart';
|
||||||
import 'package:sendtrain/widgets/sessions/session_view_activities.dart';
|
import 'package:sendtrain/widgets/sessions/session_view_activities.dart';
|
||||||
@@ -38,13 +40,13 @@ class _SessionViewState extends State<SessionView> {
|
|||||||
return title;
|
return title;
|
||||||
}
|
}
|
||||||
|
|
||||||
void resetState(int sessionId) async {
|
void resetState() async {
|
||||||
Session updatedSession =
|
Session updatedSession =
|
||||||
await SessionsDao(Provider.of<AppDatabase>(context, listen: false))
|
await SessionsDao(Provider.of<AppDatabase>(context, listen: false))
|
||||||
.find(sessionId);
|
.find(session.id);
|
||||||
|
|
||||||
final state = _fabKey.currentState;
|
final state = _fabKey.currentState;
|
||||||
if (state != null) {
|
if (state != null && state.isOpen) {
|
||||||
state.toggle();
|
state.toggle();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,13 +65,12 @@ class _SessionViewState extends State<SessionView> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return StreamBuilder<List<Activity>>(
|
return StreamBuilder<Session>(
|
||||||
stream: ActivitiesDao(Provider.of<AppDatabase>(context))
|
initialData: session,
|
||||||
.watchSessionActivities(session.id),
|
stream: SessionsDao(Provider.of<AppDatabase>(context))
|
||||||
|
.watchSession(session.id),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.hasData) {
|
if (snapshot.hasData) {
|
||||||
final activities = snapshot.data!;
|
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
floatingActionButtonLocation: ExpandableFab.location,
|
floatingActionButtonLocation: ExpandableFab.location,
|
||||||
floatingActionButton: ExpandableFab(
|
floatingActionButton: ExpandableFab(
|
||||||
@@ -77,13 +78,43 @@ class _SessionViewState extends State<SessionView> {
|
|||||||
distance: 70,
|
distance: 70,
|
||||||
type: ExpandableFabType.up,
|
type: ExpandableFabType.up,
|
||||||
overlayStyle: ExpandableFabOverlayStyle(
|
overlayStyle: ExpandableFabOverlayStyle(
|
||||||
color: Colors.black.withOpacity(0.5),
|
color: Colors.black.withValues(alpha: 0.5),
|
||||||
blur: 10,
|
blur: 10,
|
||||||
),
|
),
|
||||||
children: [
|
children: [
|
||||||
|
FloatingActionButton.extended(
|
||||||
|
icon: const Icon(Icons.military_tech_rounded),
|
||||||
|
label: Text('Add Achievement'),
|
||||||
|
onPressed: () {
|
||||||
|
showEditorSheet(
|
||||||
|
context,
|
||||||
|
AchievementEditor(
|
||||||
|
session: session, callback: resetState));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
FloatingActionButton.extended(
|
||||||
|
icon: const Icon(Icons.sports_gymnastics_rounded),
|
||||||
|
label: Text('Add Activity'),
|
||||||
|
onPressed: () {
|
||||||
|
showEditorSheet(
|
||||||
|
context,
|
||||||
|
SessionActivitiesEditor(
|
||||||
|
session: session, callback: resetState));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
FloatingActionButton.extended(
|
||||||
|
icon: const Icon(Icons.edit_outlined),
|
||||||
|
label: Text('Edit Session'),
|
||||||
|
onPressed: () {
|
||||||
|
showEditorSheet(
|
||||||
|
context,
|
||||||
|
SessionEditor(
|
||||||
|
session: session, callback: resetState));
|
||||||
|
},
|
||||||
|
),
|
||||||
FloatingActionButton.extended(
|
FloatingActionButton.extended(
|
||||||
icon: const Icon(Icons.history_outlined),
|
icon: const Icon(Icons.history_outlined),
|
||||||
label: Text('Restart'),
|
label: Text('Restart Session'),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Session newSession =
|
Session newSession =
|
||||||
session.copyWith(status: SessionStatus.pending);
|
session.copyWith(status: SessionStatus.pending);
|
||||||
@@ -100,7 +131,7 @@ class _SessionViewState extends State<SessionView> {
|
|||||||
),
|
),
|
||||||
FloatingActionButton.extended(
|
FloatingActionButton.extended(
|
||||||
icon: const Icon(Icons.done_all_outlined),
|
icon: const Icon(Icons.done_all_outlined),
|
||||||
label: Text('Done'),
|
label: Text('Finish Session'),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Session newSession =
|
Session newSession =
|
||||||
session.copyWith(status: SessionStatus.completed);
|
session.copyWith(status: SessionStatus.completed);
|
||||||
@@ -115,25 +146,6 @@ class _SessionViewState extends State<SessionView> {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
FloatingActionButton.extended(
|
|
||||||
icon: const Icon(Icons.edit_outlined),
|
|
||||||
label: Text('Edit'),
|
|
||||||
onPressed: () {
|
|
||||||
showModalBottomSheet<void>(
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius:
|
|
||||||
BorderRadius.all(Radius.circular(10.0)),
|
|
||||||
),
|
|
||||||
context: context,
|
|
||||||
showDragHandle: true,
|
|
||||||
isScrollControlled: true,
|
|
||||||
useSafeArea: true,
|
|
||||||
builder: (BuildContext context) {
|
|
||||||
return SessionEditor(
|
|
||||||
session: session, callback: resetState);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]),
|
]),
|
||||||
body: Column(
|
body: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@@ -152,7 +164,8 @@ class _SessionViewState extends State<SessionView> {
|
|||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 25, fontWeight: FontWeight.bold),
|
fontSize: 25, fontWeight: FontWeight.bold),
|
||||||
title())),
|
title())),
|
||||||
SessionViewAchievements(session: session),
|
SessionViewAchievements(
|
||||||
|
session: session, callback: resetState),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(left: 15, right: 15),
|
padding: const EdgeInsets.only(left: 15, right: 15),
|
||||||
child: Text(
|
child: Text(
|
||||||
@@ -171,7 +184,7 @@ class _SessionViewState extends State<SessionView> {
|
|||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 20, fontWeight: FontWeight.bold),
|
fontSize: 20, fontWeight: FontWeight.bold),
|
||||||
'Activites:')),
|
'Activites:')),
|
||||||
SessionViewActivities(activities: activities),
|
SessionViewActivities(session: session),
|
||||||
],
|
],
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,68 +1,85 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:drift/drift.dart' hide Column;
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:sendtrain/daos/session_activities_dao.dart';
|
import 'package:sendtrain/daos/sessions_dao.dart';
|
||||||
import 'package:sendtrain/database/database.dart';
|
import 'package:sendtrain/database/database.dart';
|
||||||
import 'package:sendtrain/extensions/string_extensions.dart';
|
import 'package:sendtrain/extensions/string_extensions.dart';
|
||||||
|
import 'package:sendtrain/helpers/widget_helpers.dart';
|
||||||
|
import 'package:sendtrain/widgets/achievements/achievement_editor.dart';
|
||||||
|
import 'package:sendtrain/widgets/builders/dialogs.dart';
|
||||||
|
|
||||||
class SessionViewAchievements extends StatelessWidget {
|
class SessionViewAchievements extends StatelessWidget {
|
||||||
const SessionViewAchievements({super.key, required this.session});
|
const SessionViewAchievements(
|
||||||
|
{super.key, required this.session, this.callback});
|
||||||
|
|
||||||
final Session session;
|
final Session session;
|
||||||
|
final Function? callback;
|
||||||
|
|
||||||
List<String> getAchievements(List<SessionActivity> sessionActivities) {
|
Session updateAchievements(int index, List achievements) {
|
||||||
List<String> achievements = [];
|
achievements.removeAt(index);
|
||||||
|
return session.copyWith(
|
||||||
for (int i = 0; i < sessionActivities.length; i++) {
|
achievements: Value<String>(json.encode(achievements)));
|
||||||
final SessionActivity sessionActivity = sessionActivities[i];
|
|
||||||
final List? saAchievments = sessionActivity.achievements?.split(',');
|
|
||||||
|
|
||||||
if (saAchievments != null) {
|
|
||||||
saAchievments.forEach((achievement) => achievements.add(achievement));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return achievements;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return FutureBuilder<List<SessionActivity>>(
|
Widget content;
|
||||||
future: SessionActivitiesDao(Provider.of<AppDatabase>(context))
|
final AppDatabase db = Provider.of<AppDatabase>(context, listen: false);
|
||||||
.fromSessionId(session.id),
|
List achievements = json.decode(session.achievements ?? "[]");
|
||||||
builder: (context, snapshot) {
|
|
||||||
if (snapshot.hasData) {
|
|
||||||
final sessionActivities = snapshot.data!;
|
|
||||||
final achievements = getAchievements(sessionActivities);
|
|
||||||
|
|
||||||
return Column(
|
if (achievements.isEmpty) {
|
||||||
children: [
|
content = Padding(
|
||||||
Padding(
|
padding: const EdgeInsets.only(left: 10, right: 5),
|
||||||
padding: const EdgeInsets.only(bottom: 10),
|
child: ActionChip(
|
||||||
child: SizedBox(
|
visualDensity: VisualDensity.compact,
|
||||||
height: 40,
|
avatar: const Icon(Icons.check_circle_outline),
|
||||||
child: ListView.builder(
|
label: Text(maxLines: 1, 'Add Achievements!'),
|
||||||
scrollDirection: Axis.horizontal,
|
onPressed: () {
|
||||||
padding: const EdgeInsets.fromLTRB(10, 0, 10, 0),
|
showEditorSheet(context,
|
||||||
itemCount: achievements.length,
|
AchievementEditor(session: session, callback: callback));
|
||||||
itemBuilder: (BuildContext context, int index) {
|
},
|
||||||
return Padding(
|
));
|
||||||
padding: const EdgeInsets.only(right: 5),
|
} else {
|
||||||
child: ActionChip(
|
content = ListView.builder(
|
||||||
visualDensity: VisualDensity.compact,
|
scrollDirection: Axis.horizontal,
|
||||||
avatar:
|
padding: const EdgeInsets.fromLTRB(10, 0, 10, 0),
|
||||||
const Icon(Icons.check_circle_outline),
|
itemCount: achievements.length,
|
||||||
label: Text(maxLines: 1, achievements[index].toTitleCase()),
|
itemBuilder: (BuildContext context, int index) {
|
||||||
onPressed: () {},
|
return Padding(
|
||||||
));
|
padding: const EdgeInsets.only(right: 5),
|
||||||
},
|
child: ActionChip(
|
||||||
))),
|
visualDensity: VisualDensity.compact,
|
||||||
],
|
avatar: const Icon(Icons.check_circle_outline),
|
||||||
);
|
label: Text(
|
||||||
} else {
|
maxLines: 1,
|
||||||
return Padding(
|
achievements[index].toString().toTitleCase()),
|
||||||
padding: EdgeInsets.all(15),
|
onPressed: () async {
|
||||||
child: CircularProgressIndicator());
|
// remove the achievement at index
|
||||||
}
|
// then update session
|
||||||
});
|
Session newSession =
|
||||||
|
updateAchievements(index, achievements);
|
||||||
|
await showUpdateDialog(
|
||||||
|
'Achievement Removal',
|
||||||
|
'Would you like to remove this achievement?',
|
||||||
|
context, () {
|
||||||
|
SessionsDao(db).replace(newSession);
|
||||||
|
if (callback != null) {
|
||||||
|
callback!();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 10),
|
||||||
|
child: SizedBox(height: 40, child: content)),
|
||||||
|
],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +1,57 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:sendtrain/daos/activities_dao.dart';
|
||||||
import 'package:sendtrain/database/database.dart';
|
import 'package:sendtrain/database/database.dart';
|
||||||
|
import 'package:sendtrain/helpers/widget_helpers.dart';
|
||||||
import 'package:sendtrain/widgets/activities/activity_card.dart';
|
import 'package:sendtrain/widgets/activities/activity_card.dart';
|
||||||
|
import 'package:sendtrain/widgets/generic/elements/add_card_generic.dart';
|
||||||
|
import 'package:sendtrain/widgets/generic/elements/generic_progress_indicator.dart';
|
||||||
|
import 'package:sendtrain/widgets/sessions/session_activities_editor.dart';
|
||||||
|
|
||||||
class SessionViewActivities extends StatefulWidget {
|
class SessionViewActivities extends StatefulWidget {
|
||||||
const SessionViewActivities({super.key, required this.activities });
|
const SessionViewActivities({super.key, required this.session});
|
||||||
|
|
||||||
final List<Activity> activities;
|
final Session session;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<SessionViewActivities> createState() => _SessionViewActivitiesState();
|
State<SessionViewActivities> createState() => _SessionViewActivitiesState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _SessionViewActivitiesState extends State<SessionViewActivities> {
|
class _SessionViewActivitiesState extends State<SessionViewActivities> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Expanded(
|
return StreamBuilder<List<Activity>>(
|
||||||
child: ListView.builder(
|
stream: ActivitiesDao(Provider.of<AppDatabase>(context))
|
||||||
// shrinkWrap: true,
|
.watchSessionActivities(widget.session.id),
|
||||||
padding: const EdgeInsets.fromLTRB(10, 0, 10, 0),
|
builder: (context, snapshot) {
|
||||||
itemCount: widget.activities.length,
|
if (snapshot.hasData) {
|
||||||
itemBuilder: (BuildContext context, int index) {
|
final activities = snapshot.data!;
|
||||||
return ActivityCard(
|
if (activities.isNotEmpty) {
|
||||||
activity: widget.activities[index]);
|
return Expanded(
|
||||||
},
|
child: ListView.builder(
|
||||||
));
|
// shrinkWrap: true,
|
||||||
|
padding: const EdgeInsets.fromLTRB(10, 0, 10, 0),
|
||||||
|
itemCount: activities.length,
|
||||||
|
itemBuilder: (BuildContext context, int index) {
|
||||||
|
return ActivityCard(
|
||||||
|
activity: activities[index], session: widget.session);
|
||||||
|
},
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
return AddCardGeneric(
|
||||||
|
title: 'Add an Activity!',
|
||||||
|
description:
|
||||||
|
'Here you can associate one or more activities that you can follow along with during your session.',
|
||||||
|
action: () {
|
||||||
|
showEditorSheet(
|
||||||
|
context,
|
||||||
|
SessionActivitiesEditor(
|
||||||
|
session: widget.session, callback: () {}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return GenericProgressIndicator();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,24 +2,51 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:sendtrain/daos/media_items_dao.dart';
|
import 'package:sendtrain/daos/media_items_dao.dart';
|
||||||
import 'package:sendtrain/database/database.dart';
|
import 'package:sendtrain/database/database.dart';
|
||||||
|
import 'package:sendtrain/helpers/widget_helpers.dart';
|
||||||
import 'package:sendtrain/widgets/media/media_card.dart';
|
import 'package:sendtrain/widgets/media/media_card.dart';
|
||||||
|
import 'package:sendtrain/widgets/sessions/session_editor.dart';
|
||||||
|
|
||||||
class SessionViewMedia extends StatelessWidget {
|
class SessionViewMedia extends StatefulWidget {
|
||||||
const SessionViewMedia({super.key, required this.session});
|
const SessionViewMedia({super.key, required this.session});
|
||||||
|
|
||||||
final Session session;
|
final Session session;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<SessionViewMedia> createState() => _SessionViewMediaState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SessionViewMediaState extends State<SessionViewMedia> {
|
||||||
|
void resetState() {
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return FutureBuilder<List<MediaItem>>(
|
return FutureBuilder<List<MediaItem>>(
|
||||||
future: MediaItemsDao(Provider.of<AppDatabase>(context))
|
future: MediaItemsDao(Provider.of<AppDatabase>(context))
|
||||||
.fromSession(session.id),
|
.fromSession(widget.session.id),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.hasData) {
|
if (snapshot.hasData) {
|
||||||
final mediaItems = snapshot.data!;
|
final mediaItems = snapshot.data!;
|
||||||
|
|
||||||
List<Widget> mediaCards = List.generate(
|
List<Widget> content;
|
||||||
mediaItems.length, (i) => MediaCard(media: mediaItems[i]));
|
if (mediaItems.isNotEmpty) {
|
||||||
|
List<Widget> mediaCards = List.generate(mediaItems.length,
|
||||||
|
(i) => MediaCard(media: mediaItems[i], callback: resetState, canDelete: true));
|
||||||
|
content = mediaCards;
|
||||||
|
} else {
|
||||||
|
content = [
|
||||||
|
FloatingActionButton(
|
||||||
|
onPressed: () {
|
||||||
|
showEditorSheet(
|
||||||
|
context,
|
||||||
|
SessionEditor(
|
||||||
|
session: widget.session, callback: resetState));
|
||||||
|
},
|
||||||
|
mini: true,
|
||||||
|
child: Icon(Icons.add_a_photo_rounded))
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
@@ -32,7 +59,7 @@ class SessionViewMedia extends StatelessWidget {
|
|||||||
crossAxisSpacing: 5,
|
crossAxisSpacing: 5,
|
||||||
mainAxisSpacing: 5,
|
mainAxisSpacing: 5,
|
||||||
crossAxisCount: 1,
|
crossAxisCount: 1,
|
||||||
children: mediaCards))
|
children: content))
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -48,6 +48,12 @@ dependencies:
|
|||||||
file_picker: ^8.1.7
|
file_picker: ^8.1.7
|
||||||
http: ^1.2.2
|
http: ^1.2.2
|
||||||
uuid: ^4.5.1
|
uuid: ^4.5.1
|
||||||
|
mime: ^2.0.0
|
||||||
|
video_player: ^2.9.2
|
||||||
|
dart_casing: ^3.0.1
|
||||||
|
collection: ^1.18.0
|
||||||
|
flutter_sound: ^9.23.1
|
||||||
|
vibration: ^3.1.2
|
||||||
|
|
||||||
flutter_launcher_name:
|
flutter_launcher_name:
|
||||||
name: "SendTrain"
|
name: "SendTrain"
|
||||||
@@ -83,7 +89,9 @@ flutter:
|
|||||||
# - images/a_dot_burr.jpeg
|
# - images/a_dot_burr.jpeg
|
||||||
# - images/a_dot_ham.jpeg
|
# - images/a_dot_ham.jpeg
|
||||||
assets:
|
assets:
|
||||||
|
- assets/audio/
|
||||||
- assets/images/
|
- assets/images/
|
||||||
|
- assets/exercises.json
|
||||||
|
|
||||||
# An image asset can refer to one or more resolution-specific "variants", see
|
# An image asset can refer to one or more resolution-specific "variants", see
|
||||||
# https://flutter.dev/assets-and-images/#resolution-aware
|
# https://flutter.dev/assets-and-images/#resolution-aware
|
||||||
|
|||||||
@@ -7,9 +7,37 @@ import 'schema_v1.dart' as v1;
|
|||||||
import 'schema_v2.dart' as v2;
|
import 'schema_v2.dart' as v2;
|
||||||
import 'schema_v3.dart' as v3;
|
import 'schema_v3.dart' as v3;
|
||||||
import 'schema_v4.dart' as v4;
|
import 'schema_v4.dart' as v4;
|
||||||
|
import 'schema_v10.dart' as v10;
|
||||||
|
import 'schema_v11.dart' as v11;
|
||||||
|
import 'schema_v12.dart' as v12;
|
||||||
|
import 'schema_v13.dart' as v13;
|
||||||
|
import 'schema_v14.dart' as v14;
|
||||||
|
import 'schema_v15.dart' as v15;
|
||||||
|
import 'schema_v16.dart' as v16;
|
||||||
|
import 'schema_v17.dart' as v17;
|
||||||
|
import 'schema_v18.dart' as v18;
|
||||||
|
import 'schema_v19.dart' as v19;
|
||||||
import 'schema_v5.dart' as v5;
|
import 'schema_v5.dart' as v5;
|
||||||
import 'schema_v6.dart' as v6;
|
import 'schema_v6.dart' as v6;
|
||||||
import 'schema_v7.dart' as v7;
|
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 {
|
class GeneratedHelper implements SchemaInstantiationHelper {
|
||||||
@override
|
@override
|
||||||
@@ -23,16 +51,108 @@ class GeneratedHelper implements SchemaInstantiationHelper {
|
|||||||
return v3.DatabaseAtV3(db);
|
return v3.DatabaseAtV3(db);
|
||||||
case 4:
|
case 4:
|
||||||
return v4.DatabaseAtV4(db);
|
return v4.DatabaseAtV4(db);
|
||||||
|
case 10:
|
||||||
|
return v10.DatabaseAtV10(db);
|
||||||
|
case 11:
|
||||||
|
return v11.DatabaseAtV11(db);
|
||||||
|
case 12:
|
||||||
|
return v12.DatabaseAtV12(db);
|
||||||
|
case 13:
|
||||||
|
return v13.DatabaseAtV13(db);
|
||||||
|
case 14:
|
||||||
|
return v14.DatabaseAtV14(db);
|
||||||
|
case 15:
|
||||||
|
return v15.DatabaseAtV15(db);
|
||||||
|
case 16:
|
||||||
|
return v16.DatabaseAtV16(db);
|
||||||
|
case 17:
|
||||||
|
return v17.DatabaseAtV17(db);
|
||||||
|
case 18:
|
||||||
|
return v18.DatabaseAtV18(db);
|
||||||
|
case 19:
|
||||||
|
return v19.DatabaseAtV19(db);
|
||||||
case 5:
|
case 5:
|
||||||
return v5.DatabaseAtV5(db);
|
return v5.DatabaseAtV5(db);
|
||||||
case 6:
|
case 6:
|
||||||
return v6.DatabaseAtV6(db);
|
return v6.DatabaseAtV6(db);
|
||||||
case 7:
|
case 7:
|
||||||
return v7.DatabaseAtV7(db);
|
return v7.DatabaseAtV7(db);
|
||||||
|
case 8:
|
||||||
|
return v8.DatabaseAtV8(db);
|
||||||
|
case 9:
|
||||||
|
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:
|
default:
|
||||||
throw MissingSchemaException(version, versions);
|
throw MissingSchemaException(version, versions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static const versions = const [1, 2, 3, 4, 5, 6, 7];
|
static const versions = const [
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
3,
|
||||||
|
4,
|
||||||
|
5,
|
||||||
|
6,
|
||||||
|
7,
|
||||||
|
8,
|
||||||
|
9,
|
||||||
|
10,
|
||||||
|
11,
|
||||||
|
12,
|
||||||
|
13,
|
||||||
|
14,
|
||||||
|
15,
|
||||||
|
16,
|
||||||
|
17,
|
||||||
|
18,
|
||||||
|
19,
|
||||||
|
20,
|
||||||
|
21,
|
||||||
|
22,
|
||||||
|
23,
|
||||||
|
24,
|
||||||
|
25,
|
||||||
|
26,
|
||||||
|
27,
|
||||||
|
28,
|
||||||
|
29,
|
||||||
|
30,
|
||||||
|
31,
|
||||||
|
32,
|
||||||
|
33,
|
||||||
|
34,
|
||||||
|
35
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user