further refactoring, conversion to stream

This commit is contained in:
Joshua Burman 2024-12-27 20:59:48 -05:00
parent fa374a5bc2
commit 8e0ec614a0
15 changed files with 315 additions and 282 deletions

View File

@ -4,7 +4,8 @@ import 'package:sendtrain/database/database.dart';
part 'activities_dao.g.dart';
@DriftAccessor(tables: [Activities])
class ActivitiesDao extends DatabaseAccessor<AppDatabase> with _$ActivitiesDaoMixin {
class ActivitiesDao extends DatabaseAccessor<AppDatabase>
with _$ActivitiesDaoMixin {
ActivitiesDao(super.db);
Future<List<Activity>> all() async {
@ -12,26 +13,71 @@ class ActivitiesDao extends DatabaseAccessor<AppDatabase> with _$ActivitiesDaoMi
}
Future<Activity> find(int id) async {
return await (select(activities)..where((activity) => activity.id.equals(id) )).getSingle();
return await (select(activities)
..where((activity) => activity.id.equals(id)))
.getSingle();
}
Future remove(Activity activity) => delete(activities).delete(activity);
Future<List<Activity>> sessionActivities(int id) async {
final result = select(db.sessionActivities).join(
Future<List<Activity>> activitiesFromSession(int id) async {
final result = select(db.sessionActivities).join(
[
innerJoin(
db.activities,
db.activities.id
.equalsExp(db.sessionActivities.activityId),
db.activities.id.equalsExp(db.sessionActivities.activityId),
),
],
)..where(db.sessionActivities.sessionId.equals(id));
final activities = (await result.get())
.map((e) => e.readTable(db.activities))
.toList();
final activities =
(await result.get()).map((e) => e.readTable(db.activities)).toList();
return activities;
}
}
Stream<List<Activity>> watchSessionActivities(int id) {
final query = select(db.sessionActivities).join(
[
innerJoin(
db.activities,
db.activities.id.equalsExp(db.sessionActivities.activityId),
),
],
)..where(db.sessionActivities.sessionId.equals(id));
return query.watch().map((rows){
final activities =
(rows).map((e) => e.readTable(db.activities)).toList();
return activities;
});
}
// MultiSelectable<SessionActivity> _selectableSessionActivities(int id) {
// // return select(db.sessionActivities)..limit(1, offset: 1);
// // final query = select(db.sessionActivities).join(
// // [
// // innerJoin(
// // db.activities,
// // db.activities.id.equalsExp(db.sessionActivities.activityId),
// // ),
// // ],
// // )..where(db.sessionActivities.sessionId.equals(id));
// // return query;
// final query = select(db.sessionActivities)..where((row) => row.sessionId.equals(id));
// query.join(
// [
// innerJoin(
// db.activities,
// db.activities.id.equalsExp(db.sessionActivities.activityId),
// ),
// ],
// );
// return query;
// }
}

View File

@ -0,0 +1,3 @@
import 'package:intl/intl.dart';
final DateFormat dateFormat = DateFormat('yyyy-MM-dd');

View File

@ -0,0 +1,13 @@
import 'package:flutter/material.dart';
import 'package:sendtrain/database/database.dart';
ImageProvider findMediaByType(List<MediaItem> media, String type) {
Iterable<MediaItem>? found = media.where((m) => m.type == MediaType.image);
if (found.isNotEmpty) {
return NetworkImage(found.first.reference);
} else {
// Element is not found
return const AssetImage('assets/images/placeholder.jpg');
}
}

View File

@ -4,8 +4,12 @@ import 'package:sendtrain/daos/activities_dao.dart';
import 'package:sendtrain/daos/media_items_dao.dart';
import 'package:sendtrain/database/database.dart';
import 'package:sendtrain/extensions/string_extensions.dart';
import 'package:sendtrain/helpers/media_helpers.dart';
import 'package:sendtrain/models/activity_timer_model.dart';
import 'package:sendtrain/widgets/activities/activity_view.dart';
import 'package:sendtrain/widgets/builders/dialogs.dart';
import 'package:sendtrain/widgets/generic/elements/card_image.dart';
import 'package:sendtrain/widgets/generic/elements/generic_progress_indicator.dart';
class ActivityCard extends StatefulWidget {
final Activity activity;
@ -43,49 +47,15 @@ class ActivityCardState extends State<ActivityCard> {
: Theme.of(context).colorScheme.surfaceContainerLow,
clipBehavior: Clip.hardEdge,
child: InkWell(
onTap: () => showGeneralDialog(
barrierColor: Colors.black.withOpacity(0.5),
transitionDuration: const Duration(milliseconds: 220),
transitionBuilder: (BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child) {
Animation<Offset> custom = Tween<Offset>(
begin: const Offset(0.0, 1.0),
end: const Offset(0.0, 0.0))
.animate(animation);
return SlideTransition(
position: custom,
child: Dialog.fullscreen(
child:
ActivityView(activity: widget.activity)));
},
barrierDismissible: true,
barrierLabel: '',
context: context,
pageBuilder: (context, animation1, animation2) {
return Container();
}),
onTap: () => showGenericDialog(
ActivityView(activity: widget.activity), context),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ListTile(
// visualDensity: VisualDensity(horizontal: VisualDensity.maximumDensity),
leading: Padding(
padding: const EdgeInsets.fromLTRB(0, 0, 0, 0),
child: Container(
// padding: EdgeInsets.only(top: 5, bottom: 5),
width: 60,
decoration: BoxDecoration(
image: DecorationImage(
fit: BoxFit.fill,
image:
findMediaByType(mediaItems, 'image')),
// color: Colors.blue,
borderRadius: const BorderRadius.all(
Radius.elliptical(8, 8)),
),
)),
leading: CardImage(
image: findMediaByType(mediaItems, 'image')),
title: Consumer<ActivityTimerModel>(
builder: (context, atm, child) {
if (atm.activity?.id == widget.activity.id) {
@ -112,95 +82,27 @@ class ActivityCardState extends State<ActivityCard> {
visualDensity: VisualDensity.compact,
icon: Icon(Icons.close_rounded),
onPressed: () {
showAdaptiveDialog(
context: context,
builder: (BuildContext context) =>
AlertDialog(
title: const Text('Activity Removal'),
content: const Text(
'Would you like to permanently remove this activity from the current session?'),
actions: <Widget>[
TextButton(
onPressed: () => Navigator.pop(
context, 'Cancel'),
child: const Text('Cancel'),
),
TextButton(
onPressed: () => {
ActivitiesDao(
Provider.of<AppDatabase>(
context,
listen: false))
.remove(widget.activity)
.then((result) {
setState(() {});
}),
Navigator.pop(context, 'OK')
},
child: const Text('OK'),
),
],
),
);
showRemovalDialog(
'Activity Removal',
'Would you like to permanently remove this activity from the current session?',
context,
ActivitiesDao(
Provider.of<AppDatabase>(
context,
listen: false)),
widget.activity)
.then((result) {
setState(() {});
});
},
)
])
// trailing: FittedBox(
// alignment: Alignment.topCenter,
// fit: BoxFit.fitHeight,
// child: IconButton(
// padding: EdgeInsets.all(0),
// alignment: Alignment.topCenter,
// // visualDensity: VisualDensity.compact,
// icon: Icon(Icons.close_rounded),
// onPressed: () {
// showAdaptiveDialog(
// context: context,
// builder: (BuildContext context) =>
// AlertDialog(
// title: const Text('Activity Removal'),
// content: const Text(
// 'Would you like to permanently remove this activity from the current session?'),
// actions: <Widget>[
// TextButton(
// onPressed: () =>
// Navigator.pop(context, 'Cancel'),
// child: const Text('Cancel'),
// ),
// TextButton(
// onPressed: () =>
// Navigator.pop(context, 'OK'),
// child: const Text('OK'),
// ),
// ],
// ),
// );
// },
// ))
),
])),
],
)),
);
} else {
return Container(
alignment: Alignment.center,
child: SizedBox(
height: 50.0,
width: 50.0,
child: CircularProgressIndicator(),
));
return GenericProgressIndicator();
}
});
}
ImageProvider findMediaByType(List<MediaItem> media, String type) {
Iterable<MediaItem>? found = media.where((m) => m.type == MediaType.image);
if (found.isNotEmpty) {
return NetworkImage(found.first.reference);
} else {
// Element is not found
return const AssetImage('assets/images/placeholder.jpg');
}
}
}

View File

@ -1,8 +1,6 @@
import 'package:flutter/material.dart';
import 'package:sendtrain/database/database.dart';
import 'package:sendtrain/widgets/sessions/session_view.dart';
Future showSessionDialog(Session session, BuildContext parentContext) {
Future showGenericDialog(dynamic object, BuildContext parentContext) {
return showGeneralDialog(
barrierColor: Colors.black.withOpacity(0.5),
transitionDuration: const Duration(milliseconds: 220),
@ -12,8 +10,7 @@ Future showSessionDialog(Session session, BuildContext parentContext) {
begin: const Offset(0.0, 1.0), end: const Offset(0.0, 0.0))
.animate(animation);
return SlideTransition(
position: custom,
child: Dialog.fullscreen(child: SessionView(session: session)));
position: custom, child: Dialog.fullscreen(child: object));
},
barrierDismissible: true,
barrierLabel: '',

View File

@ -0,0 +1,19 @@
import 'package:flutter/material.dart';
class CardContent extends StatelessWidget {
const CardContent({super.key, required this.content});
final String content;
@override
Widget build(BuildContext context) {
return ListTile(
contentPadding: const EdgeInsets.fromLTRB(15, 0, 15, 15),
title: Text(
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontWeight: FontWeight.w300),
content),
);
}
}

View File

@ -0,0 +1,24 @@
import 'package:flutter/material.dart';
class CardImage extends StatelessWidget {
const CardImage({super.key, required this.image});
final ImageProvider<Object> image;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.fromLTRB(0, 0, 0, 0),
child: Container(
width: 60,
decoration: BoxDecoration(
image: DecorationImage(
fit: BoxFit.cover,
image: image,
// color: Colors.blue,
),
borderRadius: BorderRadius.all(Radius.elliptical(8, 8)),
),
));
}
}

View File

@ -0,0 +1,16 @@
import 'package:flutter/material.dart';
class GenericProgressIndicator extends StatelessWidget {
const GenericProgressIndicator({super.key});
@override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.center,
child: SizedBox(
height: 50.0,
width: 50.0,
child: CircularProgressIndicator(),
));
}
}

View File

@ -4,6 +4,7 @@ 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/generic_progress_indicator.dart';
import 'package:sendtrain/widgets/sessions/session_card.dart';
import 'package:collection/collection.dart';
@ -95,13 +96,7 @@ class _SessionsScreenState extends State<SessionsScreen> {
],
);
} else {
return Container(
alignment: Alignment.center,
child: SizedBox(
height: 50.0,
width: 50.0,
child: CircularProgressIndicator(),
));
return GenericProgressIndicator();
}
});
}

View File

@ -1,12 +1,11 @@
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:intl/date_symbol_data_local.dart';
import 'package:provider/provider.dart';
import 'package:sendtrain/daos/media_items_dao.dart';
import 'package:sendtrain/daos/sessions_dao.dart';
import 'package:sendtrain/database/database.dart' hide ActivityAction;
import 'package:sendtrain/extensions/string_extensions.dart';
import 'package:sendtrain/widgets/builders/dialogs.dart';
import 'package:sendtrain/widgets/generic/elements/generic_progress_indicator.dart';
import 'package:sendtrain/widgets/sessions/session_card_full.dart';
import 'package:sendtrain/widgets/sessions/session_card_small.dart';
class SessionCard extends StatefulWidget {
final int type;
@ -24,11 +23,6 @@ class _SessionCardState extends State<SessionCard> {
final Session session = widget.session;
initializeDateFormatting('en');
final DateFormat dateFormat = DateFormat('yyyy-MM-dd');
Color color = (session.status == SessionStatus.started)
? Theme.of(context).colorScheme.primaryContainer
: Theme.of(context).colorScheme.surfaceContainerLow;
return FutureBuilder<List<MediaItem>>(
future: MediaItemsDao(Provider.of<AppDatabase>(context))
@ -38,126 +32,13 @@ class _SessionCardState extends State<SessionCard> {
List<MediaItem> mediaItems = snapshot.data!;
if (type == 0) {
return Card(
color: color,
margin: const EdgeInsets.fromLTRB(15, 15, 15, 0),
clipBehavior: Clip.hardEdge,
child: InkWell(
splashColor: Colors.deepPurple,
onTap: () => showSessionDialog(session, context),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ListTile(
contentPadding: EdgeInsets.only(left: 15),
leading: Padding(
padding: const EdgeInsets.fromLTRB(0, 8, 0, 0),
child: Container(
width: 60,
decoration: BoxDecoration(
image: DecorationImage(
fit: BoxFit.cover,
image:
findMediaByType(mediaItems, 'image')),
// color: Colors.blue,
borderRadius: BorderRadius.all(
Radius.elliptical(10, 10)),
),
)),
title: Text(maxLines: 1, session.title.toTitleCase()),
subtitle: Text(
maxLines: 1,
dateFormat.format(session.date as DateTime)),
trailing: IconButton(
padding: EdgeInsets.all(0),
alignment: Alignment.topCenter,
icon: Icon(Icons.close_rounded),
onPressed: () {
showRemovalDialog(
'Session Removal',
'Would you like to permanently remove this session?',
context,
SessionsDao(Provider.of<AppDatabase>(
context,
listen: false)),
session)
.then((result) {
setState(() {});
});
},
),
),
ListTile(
contentPadding:
const EdgeInsets.fromLTRB(15, 0, 15, 15),
title: Text(
maxLines: 2,
overflow: TextOverflow.ellipsis,
style:
const TextStyle(fontWeight: FontWeight.w300),
session.content),
),
],
)),
);
return SessionCardFull(session: session, mediaItems: mediaItems);
} else {
return Card(
color: color,
child: InkWell(
// overlayColor: MaterialStateColor(Colors.deepPurple as int),
splashColor: Colors.deepPurple,
borderRadius:
const BorderRadius.all(Radius.elliptical(10, 10)),
onTap: () => showSessionDialog(session, context),
child: Container(
decoration: BoxDecoration(
// color: const Color.fromARGB(47, 0, 0, 0),
borderRadius: BorderRadius.circular(10),
image: DecorationImage(
colorFilter: ColorFilter.mode(
Color.fromARGB(220, 41, 39, 39),
BlendMode.hardLight),
image: findMediaByType(mediaItems, 'image'),
fit: BoxFit.cover),
),
child: Align(
alignment: Alignment.center,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ListTile(
title: Text(
maxLines: 3,
session.title.toTitleCase(),
textAlign: TextAlign.center),
subtitle: Text(
maxLines: 1,
dateFormat
.format(session.date as DateTime),
textAlign: TextAlign.center),
),
])))));
return SessionCardSmall(session: session, mediaItems: mediaItems);
}
} else {
return Container(
alignment: Alignment.center,
child: SizedBox(
height: 50.0,
width: 50.0,
child: CircularProgressIndicator(),
));
return GenericProgressIndicator();
}
});
}
ImageProvider findMediaByType(List<MediaItem> media, String type) {
Iterable<MediaItem>? found = media.where((m) => m.type == MediaType.image);
if (found.isNotEmpty) {
return NetworkImage(found.first.reference);
} else {
// Element is not found
return const AssetImage('assets/images/placeholder.jpg');
}
}
}

View File

@ -0,0 +1,72 @@
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/extensions/string_extensions.dart';
import 'package:sendtrain/helpers/date_helpers.dart';
import 'package:sendtrain/helpers/media_helpers.dart';
import 'package:sendtrain/widgets/builders/dialogs.dart';
import 'package:sendtrain/widgets/generic/elements/card_content.dart';
import 'package:sendtrain/widgets/generic/elements/card_image.dart';
import 'package:sendtrain/widgets/sessions/session_view.dart';
class SessionCardFull extends StatefulWidget {
const SessionCardFull(
{super.key, required this.session, required this.mediaItems});
final Session session;
final List<MediaItem> mediaItems;
@override
State<SessionCardFull> createState() => _SessionCardFullState();
}
class _SessionCardFullState extends State<SessionCardFull> {
@override
Widget build(BuildContext context) {
final Session session = widget.session;
final List<MediaItem> mediaItems = widget.mediaItems;
return Card(
color: (session.status == SessionStatus.started)
? Theme.of(context).colorScheme.primaryContainer
: Theme.of(context).colorScheme.surfaceContainerLow,
margin: const EdgeInsets.fromLTRB(15, 15, 15, 0),
clipBehavior: Clip.hardEdge,
child: InkWell(
splashColor: Colors.deepPurple,
onTap: () =>
showGenericDialog(SessionView(session: session), context),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ListTile(
contentPadding: EdgeInsets.only(left: 8),
leading: CardImage(image: findMediaByType(mediaItems, 'image')),
title: Text(maxLines: 1, session.title.toTitleCase()),
subtitle: Text(
maxLines: 1, dateFormat.format(session.date as DateTime)),
trailing: IconButton(
padding: EdgeInsets.all(0),
alignment: Alignment.topCenter,
icon: Icon(Icons.close_rounded),
onPressed: () {
showRemovalDialog(
'Session Removal',
'Would you like to permanently remove this session?',
context,
SessionsDao(Provider.of<AppDatabase>(context,
listen: false)),
session)
.then((result) {
setState(() {});
});
},
),
),
CardContent(content: session.content)
],
)),
);
}
}

View File

@ -0,0 +1,62 @@
import 'package:flutter/material.dart';
import 'package:sendtrain/database/database.dart';
import 'package:sendtrain/extensions/string_extensions.dart';
import 'package:sendtrain/helpers/date_helpers.dart';
import 'package:sendtrain/helpers/media_helpers.dart';
import 'package:sendtrain/widgets/builders/dialogs.dart';
import 'package:sendtrain/widgets/sessions/session_view.dart';
class SessionCardSmall extends StatefulWidget {
const SessionCardSmall(
{super.key, required this.session, required this.mediaItems});
final Session session;
final List<MediaItem> mediaItems;
@override
State<SessionCardSmall> createState() => _SessionCardSmallState();
}
class _SessionCardSmallState extends State<SessionCardSmall> {
@override
Widget build(BuildContext context) {
final Session session = widget.session;
final List<MediaItem> mediaItems = widget.mediaItems;
return Card(
color: (session.status == SessionStatus.started)
? Theme.of(context).colorScheme.primaryContainer
: Theme.of(context).colorScheme.surfaceContainerLow,
child: InkWell(
// overlayColor: MaterialStateColor(Colors.deepPurple as int),
splashColor: Colors.deepPurple,
borderRadius: const BorderRadius.all(Radius.elliptical(10, 10)),
onTap: () => showGenericDialog(SessionView(session: session), context),
child: Container(
decoration: BoxDecoration(
// color: const Color.fromARGB(47, 0, 0, 0),
borderRadius: BorderRadius.circular(10),
image: DecorationImage(
colorFilter: ColorFilter.mode(
Color.fromARGB(220, 41, 39, 39), BlendMode.hardLight),
image: findMediaByType(mediaItems, 'image'),
fit: BoxFit.cover),
),
child: Align(
alignment: Alignment.center,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ListTile(
title: Text(
maxLines: 3,
session.title.toTitleCase(),
textAlign: TextAlign.center),
subtitle: Text(
maxLines: 1,
dateFormat.format(session.date as DateTime),
textAlign: TextAlign.center),
),
])))));
}
}

View File

@ -9,6 +9,7 @@ import 'package:sendtrain/database/database.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_text_input.dart';
import 'package:sendtrain/widgets/sessions/session_view.dart';
class SessionCreator extends StatefulWidget {
const SessionCreator({super.key, this.data, this.session});
@ -123,8 +124,8 @@ class _SessionCreatorState extends State<SessionCreator> {
createSession(_formKey.currentContext)
.then((id) => {
SessionsDao(db).find(id).then(
(session) => showSessionDialog(
session,
(session) => showGenericDialog(
SessionView(session: session),
_formKey
.currentContext!)),
Navigator.pop(

View File

@ -7,6 +7,7 @@ import 'package:provider/provider.dart';
import 'package:sendtrain/daos/activities_dao.dart';
import 'package:sendtrain/database/database.dart';
import 'package:sendtrain/extensions/string_extensions.dart';
import 'package:sendtrain/widgets/generic/elements/generic_progress_indicator.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_media.dart';
@ -28,12 +29,13 @@ class _SessionViewState extends State<SessionView> {
initializeDateFormatting('en');
final DateFormat dateFormat = DateFormat('yyyy-MM-dd');
return FutureBuilder<List<Activity>>(
future: ActivitiesDao(Provider.of<AppDatabase>(context))
.sessionActivities(session.id),
return StreamBuilder<List<Activity>>(
stream: ActivitiesDao(Provider.of<AppDatabase>(context))
.watchSessionActivities(session.id),
builder: (context, snapshot) {
if (snapshot.hasData) {
final activities = snapshot.data!;
return Scaffold(
floatingActionButtonLocation: ExpandableFab.location,
floatingActionButton: ExpandableFab(
@ -100,13 +102,7 @@ class _SessionViewState extends State<SessionView> {
],
));
} else {
return Container(
alignment: Alignment.center,
child: SizedBox(
height: 50.0,
width: 50.0,
child: CircularProgressIndicator(),
));
return GenericProgressIndicator();
}
});
}

View File

@ -2,21 +2,27 @@ import 'package:flutter/material.dart';
import 'package:sendtrain/database/database.dart';
import 'package:sendtrain/widgets/activities/activity_card.dart';
class SessionViewActivities extends StatelessWidget {
class SessionViewActivities extends StatefulWidget {
const SessionViewActivities({super.key, required this.activities });
final List<Activity> activities;
@override
State<SessionViewActivities> createState() => _SessionViewActivitiesState();
}
class _SessionViewActivitiesState extends State<SessionViewActivities> {
@override
Widget build(BuildContext context) {
return Expanded(
child: ListView.builder(
// shrinkWrap: true,
padding: const EdgeInsets.fromLTRB(10, 0, 10, 0),
itemCount: activities.length,
itemCount: widget.activities.length,
itemBuilder: (BuildContext context, int index) {
return ActivityCard(
activity: activities[index]);
activity: widget.activities[index]);
},
));
}