action prep, activity association removal, activity ui tweaks

This commit is contained in:
Joshua Burman 2025-01-06 10:10:08 -05:00
parent 7ead6ba631
commit ebca90e69a
7 changed files with 278 additions and 153 deletions

View File

@ -4,17 +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 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 {
@ -23,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();

View File

@ -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() {

View File

@ -1,7 +1,7 @@
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';
@ -14,9 +14,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; final Function? callback;
const ActivityCard({super.key, required this.activity, this.callback}); const ActivityCard(
{super.key,
required this.activity,
required this.session,
this.callback});
@override @override
State<ActivityCard> createState() => ActivityCardState(); State<ActivityCard> createState() => ActivityCardState();
@ -62,8 +67,8 @@ class ActivityCardState extends State<ActivityCard> {
} }
}, },
), ),
subtitle: subtitle: Text(
Text(maxLines: 2, widget.activity.description ?? ""), maxLines: 2, widget.activity.description ?? ""),
contentPadding: EdgeInsets.only(left: 13), contentPadding: EdgeInsets.only(left: 13),
trailing: Flex( trailing: Flex(
direction: Axis.vertical, direction: Axis.vertical,
@ -79,10 +84,11 @@ class ActivityCardState extends State<ActivityCard> {
'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(Provider.of<AppDatabase>( SessionActivitiesDao(
context, Provider.of<AppDatabase>(context,
listen: false)) listen: false))
.remove(widget.activity); .removeAssociation(widget.activity.id,
widget.session.id);
}).then((result) { }).then((result) {
setState(() {}); setState(() {});
}); });

View File

@ -11,8 +11,7 @@ import 'package:sendtrain/widgets/activities/activity_view_media.dart';
import 'package:sendtrain/widgets/activities/activity_view_types.dart'; import 'package:sendtrain/widgets/activities/activity_view_types.dart';
class ActivityView extends StatefulWidget { class ActivityView extends StatefulWidget {
const ActivityView( const ActivityView({super.key, required this.activity});
{super.key, required this.activity});
final Activity activity; final Activity activity;
@override @override
@ -20,6 +19,76 @@ class ActivityView extends StatefulWidget {
} }
class _ActivityViewState extends State<ActivityView> { class _ActivityViewState extends State<ActivityView> {
List<Widget> action(actions) {
if (actions.isNotEmpty) {
return [
Padding(
padding: const EdgeInsets.only(left: 10, right: 10),
child: Card(
clipBehavior: Clip.antiAlias,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(10),
topRight: Radius.circular(10)),
),
color: Theme.of(context).colorScheme.onPrimary,
child: Row(children: [
Ink(
width: 70,
color: Theme.of(context).colorScheme.primaryContainer,
child: Consumer<ActivityTimerModel>(
builder: (context, atm, child) {
return IconButton(
alignment: AlignmentDirectional.center,
icon: atm.isActive
? const Icon(Icons.pause_rounded)
: const Icon(Icons.play_arrow_rounded),
onPressed: () =>
{atm.isActive ? atm.pause() : atm.start()});
},
)),
Expanded(
flex: 1,
child: Stack(alignment: Alignment.center, children: [
Container(
alignment: Alignment.center,
child: Consumer<ActivityTimerModel>(
builder: (context, atm, child) {
return Text(
style: const TextStyle(fontSize: 20),
textAlign: TextAlign.center,
'${atm.actionCount} ${atm.currentAction['type']}'
.toTitleCase());
},
),
),
Container(
alignment: Alignment.centerRight,
padding: EdgeInsets.only(right: 15),
child: Consumer<ActivityTimerModel>(
builder: (context, atm, child) {
return Text(
style: const TextStyle(fontSize: 12),
textAlign: TextAlign.right,
'${atm.currentAction['actionID'] + 1} of ${atm.totalActions()}');
})),
])),
]))),
Padding(
padding: EdgeInsets.only(left: 14, right: 14),
child: Consumer<ActivityTimerModel>(builder: (context, atm, child) {
return LinearProgressIndicator(
value: atm.progress,
semanticsLabel: 'Activity Progress',
);
})),
ActivityActionView(actions: actions)
];
} else {
return [Text('add an action')];
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final Activity activity = widget.activity; final Activity activity = widget.activity;
@ -44,11 +113,11 @@ class _ActivityViewState extends State<ActivityView> {
blur: 10, blur: 10,
), ),
children: [ children: [
FloatingActionButton.extended( // FloatingActionButton.extended(
icon: const Icon(Icons.upload_outlined), // icon: const Icon(Icons.upload_outlined),
label: Text('Upload Media'), // label: Text('Upload Media'),
onPressed: () {}, // onPressed: () {},
), // ),
FloatingActionButton.extended( FloatingActionButton.extended(
icon: const Icon(Icons.note_add_outlined), icon: const Icon(Icons.note_add_outlined),
label: Text('Add Note'), label: Text('Add Note'),
@ -68,125 +137,159 @@ class _ActivityViewState extends State<ActivityView> {
body: Column( body: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
AppBar( AppBar(
titleSpacing: 0, titleSpacing: 0,
centerTitle: true, centerTitle: true,
title: const Text('Activity', title: const Text('Activity',
style: TextStyle(fontSize: 15)), style: TextStyle(fontSize: 15)),
), ),
Padding( Padding(
padding: const EdgeInsets.only( padding: const EdgeInsets.only(
left: 15, right: 20, top: 15, bottom: 10), left: 15, right: 20, top: 15, bottom: 10),
child: Text( child: Text(
maxLines: 1, maxLines: 1,
style: const TextStyle( style: const TextStyle(
fontSize: 25, fontWeight: FontWeight.bold), fontSize: 25,
activity.title.toTitleCase())), fontWeight: FontWeight.bold),
Padding( activity.title.toTitleCase())),
padding: const EdgeInsets.fromLTRB(10, 0, 0, 10), Padding(
child: Flex(direction: Axis.horizontal, children: [ padding: const EdgeInsets.fromLTRB(10, 0, 0, 10),
ActivityViewCategories( child:
categories: activity.category != null ? [activity.category!] : []), Flex(direction: Axis.horizontal, children: [
ActivityViewTypes(types: [activity.type!]) ActivityViewCategories<List<ActivityLevel>>(
])), icon: Icon(Icons.stairs_rounded),
Padding( text: "Activity Level",
padding: const EdgeInsets.only( object: activity.level != null
top: 0, bottom: 10, left: 15, right: 15), ? [activity.level!]
child: Text( : []),
textAlign: TextAlign.left, // ActivityViewCategories<List<ActivityMechanic>>(
style: const TextStyle(fontSize: 15), // icon: Icon(Icons.),
activity.description ?? "")), // text: 'Activity Mechanic',
const Padding( // object: activity.mechanic != null
padding: EdgeInsets.fromLTRB(15, 20, 0, 10), // ? [activity.mechanic!]
child: Text( // : []),
style: TextStyle( ActivityViewCategories<List<ActivityEquipment>>(
fontSize: 20, fontWeight: FontWeight.bold), icon: Icon(Icons.fitness_center_rounded),
'Media:')), text: 'Activity Equipments',
ActivityViewMedia(activity: activity), object: activity.equipment != null
const Padding( ? [activity.equipment!]
padding: EdgeInsets.fromLTRB(15, 30, 0, 10), : []),
child: Text( ActivityViewCategories<List<ActivityType>>(
textAlign: TextAlign.left, icon: Icon(Icons.type_specimen_rounded),
style: TextStyle( text: 'Activity Equipments',
fontSize: 20, fontWeight: FontWeight.bold), object: activity.equipment != null
'Actions')), ? [activity.type!]
Padding( : []),
padding: const EdgeInsets.only(left: 10, right: 10), ])),
child: Card( Padding(
clipBehavior: Clip.antiAlias, padding: const EdgeInsets.only(
shape: const RoundedRectangleBorder( top: 0, bottom: 10, left: 15, right: 15),
borderRadius: BorderRadius.only( child: Text(
topLeft: Radius.circular(10), maxLines: 5,
topRight: Radius.circular(10)), textAlign: TextAlign.left,
), style: const TextStyle(fontSize: 15),
color: Theme.of(context).colorScheme.onPrimary, activity.description ?? "")),
child: Row(children: [ Padding(
Ink( padding: EdgeInsets.only(left: 15),
width: 70, child: Text("read more...",
color: Theme.of(context) style: TextStyle(
.colorScheme color: Colors.deepPurpleAccent))),
.primaryContainer, const Padding(
child: Consumer<ActivityTimerModel>( padding: EdgeInsets.fromLTRB(15, 20, 0, 10),
builder: (context, atm, child) { child: Text(
return IconButton( style: TextStyle(
alignment: fontSize: 20,
AlignmentDirectional.center, fontWeight: FontWeight.bold),
icon: atm.isActive 'Media:')),
? const Icon( ActivityViewMedia(activity: activity),
Icons.pause_rounded) const Padding(
: const Icon( padding: EdgeInsets.fromLTRB(15, 30, 0, 10),
Icons.play_arrow_rounded), child: Text(
onPressed: () => { textAlign: TextAlign.left,
atm.isActive style: TextStyle(
? atm.pause() fontSize: 20,
: atm.start() fontWeight: FontWeight.bold),
}); 'Actions')),
},
)), // Padding(
Expanded( // padding: const EdgeInsets.only(left: 10, right: 10),
flex: 1, // child: Card(
child: Stack( // clipBehavior: Clip.antiAlias,
alignment: Alignment.center, // shape: const RoundedRectangleBorder(
children: [ // borderRadius: BorderRadius.only(
Container( // topLeft: Radius.circular(10),
alignment: Alignment.center, // topRight: Radius.circular(10)),
child: Consumer<ActivityTimerModel>( // ),
builder: (context, atm, child) { // color: Theme.of(context).colorScheme.onPrimary,
return Text( // child: Row(children: [
style: const TextStyle( // Ink(
fontSize: 20), // width: 70,
textAlign: TextAlign.center, // color: Theme.of(context)
'${atm.actionCount} ${atm.currentAction['type']}'.toTitleCase()); // .colorScheme
}, // .primaryContainer,
), // child: Consumer<ActivityTimerModel>(
), // builder: (context, atm, child) {
Container( // return IconButton(
alignment: Alignment.centerRight, // alignment:
padding: // AlignmentDirectional.center,
EdgeInsets.only(right: 15), // icon: atm.isActive
child: // ? const Icon(
Consumer<ActivityTimerModel>( // Icons.pause_rounded)
builder: (context, atm, // : const Icon(
child) { // Icons.play_arrow_rounded),
return Text( // onPressed: () => {
style: const TextStyle( // atm.isActive
fontSize: 12), // ? atm.pause()
textAlign: TextAlign.right, // : atm.start()
'${atm.currentAction['actionID'] + 1} of ${atm.totalActions()}'); // });
})), // },
])), // )),
]))), // Expanded(
Padding( // flex: 1,
padding: EdgeInsets.only(left: 14, right: 14), // child: Stack(
child: Consumer<ActivityTimerModel>( // alignment: Alignment.center,
builder: (context, atm, child) { // children: [
return LinearProgressIndicator( // Container(
value: atm.progress, // alignment: Alignment.center,
semanticsLabel: 'Activity Progress', // child: Consumer<ActivityTimerModel>(
); // builder: (context, atm, child) {
})), // return Text(
ActivityActionView(actions: actions), // style: const TextStyle(
])); // fontSize: 20),
// textAlign: TextAlign.center,
// '${atm.actionCount} ${atm.currentAction['type']}'
// .toTitleCase());
// },
// ),
// ),
// Container(
// alignment: Alignment.centerRight,
// padding:
// EdgeInsets.only(right: 15),
// child:
// Consumer<ActivityTimerModel>(
// builder: (context, atm,
// child) {
// return Text(
// style: const TextStyle(
// fontSize: 12),
// textAlign: TextAlign.right,
// '${atm.currentAction['actionID'] + 1} of ${atm.totalActions()}');
// })),
// ])),
// ]))),
// Padding(
// padding: EdgeInsets.only(left: 14, right: 14),
// child: Consumer<ActivityTimerModel>(
// builder: (context, atm, child) {
// return LinearProgressIndicator(
// value: atm.progress,
// semanticsLabel: 'Activity Progress',
// );
// })),
// ActivityActionView(actions: actions),
] +
action(actions)));
} else { } else {
return Container( return Container(
alignment: Alignment.center, alignment: Alignment.center,

View File

@ -1,11 +1,12 @@
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) {
@ -15,13 +16,13 @@ class ActivityViewCategories extends StatelessWidget {
shrinkWrap: true, shrinkWrap: true,
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
padding: const EdgeInsets.only(right: 10), padding: const EdgeInsets.only(right: 10),
itemCount: categories.length, itemCount: object.length,
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
return ActionChip( return 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: () {},
); );
}, },

View File

@ -57,8 +57,7 @@ class _FormSearchInputState extends State<FormSearchInput> {
_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!); await service.fetchSuggestions(_currentQuery!);

View File

@ -32,7 +32,7 @@ class _SessionViewActivitiesState extends State<SessionViewActivities> {
padding: const EdgeInsets.fromLTRB(10, 0, 10, 0), padding: const EdgeInsets.fromLTRB(10, 0, 10, 0),
itemCount: activities.length, itemCount: activities.length,
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
return ActivityCard(activity: activities[index]); return ActivityCard(activity: activities[index], session: widget.session);
}, },
)); ));
} else { } else {