From 10332ec8bea1bf71b72158d9c305085cc95d2fb2 Mon Sep 17 00:00:00 2001 From: Joshua Burman Date: Tue, 31 Dec 2024 22:41:17 -0500 Subject: [PATCH] media item and session images and location management, also refactoring and DRYing up code --- lib/daos/media_items_dao.dart | 9 +- lib/daos/sessions_dao.dart | 6 +- lib/database/database.dart | 4 +- lib/database/database.g.dart | 10 ++ lib/helpers/media_helpers.dart | 21 +-- lib/helpers/widget_helpers.dart | 17 +++ lib/main.dart | 4 +- lib/widgets/activities/activity_card.dart | 2 +- lib/widgets/generic/elements/card_image.dart | 1 + lib/widgets/media/media_card.dart | 59 +-------- lib/widgets/media/media_content.dart | 28 ++++ lib/widgets/media/media_details.dart | 35 +++++ lib/widgets/sessions/session_card_full.dart | 20 ++- lib/widgets/sessions/session_card_small.dart | 23 +++- ...ssion_creator.dart => session_editor.dart} | 125 +++++++++++++----- lib/widgets/sessions/session_view.dart | 91 +++++++++++-- lib/widgets/sessions/session_view_media.dart | 2 +- 17 files changed, 328 insertions(+), 129 deletions(-) create mode 100644 lib/helpers/widget_helpers.dart create mode 100644 lib/widgets/media/media_content.dart create mode 100644 lib/widgets/media/media_details.dart rename lib/widgets/sessions/{session_creator.dart => session_editor.dart} (58%) diff --git a/lib/daos/media_items_dao.dart b/lib/daos/media_items_dao.dart index dc098a5..fc3c313 100644 --- a/lib/daos/media_items_dao.dart +++ b/lib/daos/media_items_dao.dart @@ -39,7 +39,7 @@ class MediaItemsDao extends DatabaseAccessor return mediaItems; } - Future> fromSession(Session session) async { + Future> fromSession(int sessionId) async { final result = select(db.objectMediaItems).join( [ innerJoin( @@ -49,7 +49,7 @@ class MediaItemsDao extends DatabaseAccessor ], ) ..where(db.objectMediaItems.objectType.equals(ObjectType.sessions.name)) - ..where(db.objectMediaItems.objectId.equals(session.id)); + ..where(db.objectMediaItems.objectId.equals(sessionId)); final mediaItems = (await result.get()).map((e) => e.readTable(db.mediaItems)).toList(); @@ -75,4 +75,9 @@ class MediaItemsDao extends DatabaseAccessor return mediaItems; }); } + + Future removeAll(Iterable mediaItemIds) { + return (delete(mediaItems)..where((table) => table.id.isIn(mediaItemIds))) + .go(); + } } diff --git a/lib/daos/sessions_dao.dart b/lib/daos/sessions_dao.dart index fccce8c..c27bf42 100644 --- a/lib/daos/sessions_dao.dart +++ b/lib/daos/sessions_dao.dart @@ -8,11 +8,9 @@ class SessionsDao extends DatabaseAccessor with _$SessionsDaoMixin SessionsDao(super.db); Future find(int id) => (select(sessions)..where((session) => session.id.equals(id) )).getSingle(); - Future> all() => select(sessions).get(); + Future> all() => (select(sessions)..orderBy([(session) => OrderingTerm(expression: session.createdAt, mode: OrderingMode.desc)])).get(); Stream> watch() => select(sessions).watch(); Future createOrUpdate(SessionsCompanion session) => into(sessions).insertOnConflictUpdate(session); - // Future replace(Session session) => update(sessions).replace(session); + Future replace(Session session) => update(sessions).replace(session); Future remove(Session session) => delete(sessions).delete(session); - - } \ No newline at end of file diff --git a/lib/database/database.dart b/lib/database/database.dart index 18d74f5..680f2bd 100644 --- a/lib/database/database.dart +++ b/lib/database/database.dart @@ -35,7 +35,7 @@ class AppDatabase extends _$AppDatabase { AppDatabase() : super(_openConnection()); @override - int get schemaVersion => 6; + int get schemaVersion => 7; @override MigrationStrategy get migration { @@ -141,7 +141,7 @@ class ObjectMediaItems extends Table { dateTime().withDefault(Variable(DateTime.now()))(); } -enum MediaType { youtube, image } +enum MediaType { youtube, image, location } class MediaItems extends Table { IntColumn get id => integer().autoIncrement()(); diff --git a/lib/database/database.g.dart b/lib/database/database.g.dart index 8ff36d1..dad2c01 100644 --- a/lib/database/database.g.dart +++ b/lib/database/database.g.dart @@ -2398,6 +2398,16 @@ abstract class _$AppDatabase extends GeneratedDatabase { late final $MediaItemsTable mediaItems = $MediaItemsTable(this); late final $ObjectMediaItemsTable objectMediaItems = $ObjectMediaItemsTable(this); + late final SessionsDao sessionsDao = SessionsDao(this as AppDatabase); + late final ActivitiesDao activitiesDao = ActivitiesDao(this as AppDatabase); + late final MediaItemsDao mediaItemsDao = MediaItemsDao(this as AppDatabase); + late final ObjectMediaItemsDao objectMediaItemsDao = + ObjectMediaItemsDao(this as AppDatabase); + late final SessionActivitiesDao sessionActivitiesDao = + SessionActivitiesDao(this as AppDatabase); + late final ActivityActionsDao activityActionsDao = + ActivityActionsDao(this as AppDatabase); + late final ActionsDao actionsDao = ActionsDao(this as AppDatabase); @override Iterable> get allTables => allSchemaEntities.whereType>(); diff --git a/lib/helpers/media_helpers.dart b/lib/helpers/media_helpers.dart index c6bcf4b..6e93f58 100644 --- a/lib/helpers/media_helpers.dart +++ b/lib/helpers/media_helpers.dart @@ -1,13 +1,16 @@ import 'package:flutter/material.dart'; import 'package:sendtrain/database/database.dart'; -ImageProvider findMediaByType(List media, String type) { - Iterable? found = media.where((m) => m.type == MediaType.image); +ImageProvider findMediaByType(List media, MediaType type) { + Iterable? found = media.where((m) => m.type == type); + Image image; - if (found.isNotEmpty) { - return NetworkImage(found.first.reference); - } else { - // Element is not found - return const AssetImage('assets/images/placeholder.jpg'); - } - } \ No newline at end of file + if (found.isNotEmpty) { + image = Image.network(found.first.reference); + } else { + // Element is not found + image = Image.asset('assets/images/placeholder.jpg'); + } + + return image.image; +} diff --git a/lib/helpers/widget_helpers.dart b/lib/helpers/widget_helpers.dart new file mode 100644 index 0000000..bf5ec3c --- /dev/null +++ b/lib/helpers/widget_helpers.dart @@ -0,0 +1,17 @@ +import 'package:flutter/material.dart'; +import 'package:sendtrain/database/database.dart'; +import 'package:sendtrain/widgets/media/media_details.dart'; + +showMediaDetailWidget(BuildContext context, MediaItem media) { + showModalBottomSheet( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(10.0)), + ), + context: context, + showDragHandle: true, + isScrollControlled: true, + useSafeArea: true, + builder: (BuildContext context) { + return MediaDetails(media: media); + }); +} diff --git a/lib/main.dart b/lib/main.dart index ea69153..e666920 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -6,7 +6,7 @@ import 'package:sendtrain/widgets/screens/activities_screen.dart'; import 'package:sendtrain/widgets/screens/sessions_screen.dart'; // ignore: unused_import import 'package:sendtrain/database/seed.dart'; -import 'package:sendtrain/widgets/sessions/session_creator.dart'; +import 'package:sendtrain/widgets/sessions/session_editor.dart'; class SendTrain extends StatelessWidget { const SendTrain({super.key}); @@ -108,7 +108,7 @@ class _AppState extends State { isScrollControlled: true, useSafeArea: true, builder: (BuildContext context) { - return SessionCreator(); + return SessionEditor(); }); }, label: const Text('New Session'), diff --git a/lib/widgets/activities/activity_card.dart b/lib/widgets/activities/activity_card.dart index c70a941..e09908b 100644 --- a/lib/widgets/activities/activity_card.dart +++ b/lib/widgets/activities/activity_card.dart @@ -48,7 +48,7 @@ class ActivityCardState extends State { ListTile( // visualDensity: VisualDensity(horizontal: VisualDensity.maximumDensity), leading: CardImage( - image: findMediaByType(mediaItems, 'image')), + image: findMediaByType(mediaItems, MediaType.image)), title: Consumer( builder: (context, atm, child) { if (atm.activity?.id == widget.activity.id) { diff --git a/lib/widgets/generic/elements/card_image.dart b/lib/widgets/generic/elements/card_image.dart index 6eb6518..bab402e 100644 --- a/lib/widgets/generic/elements/card_image.dart +++ b/lib/widgets/generic/elements/card_image.dart @@ -21,6 +21,7 @@ class CardImage extends StatelessWidget { image: DecorationImage( fit: BoxFit.cover, image: image, + onError: (error, stackTrace) => AssetImage('assets/images/placeholder.jpg') // color: Colors.blue, ), borderRadius: BorderRadius.all(Radius.elliptical(8, 8)), diff --git a/lib/widgets/media/media_card.dart b/lib/widgets/media/media_card.dart index 1126bd6..21e5d94 100644 --- a/lib/widgets/media/media_card.dart +++ b/lib/widgets/media/media_card.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:sendtrain/database/database.dart'; -import 'package:youtube_player_flutter/youtube_player_flutter.dart'; +import 'package:sendtrain/helpers/widget_helpers.dart'; class MediaCard extends StatelessWidget { const MediaCard({super.key, required this.media}); @@ -9,15 +9,10 @@ class MediaCard extends StatelessWidget { @override Widget build(BuildContext context) { - YoutubePlayerController controller = YoutubePlayerController( - initialVideoId: media.reference, - flags: const YoutubePlayerFlags( - autoPlay: false, mute: true, showLiveFullscreenButton: false)); - DecorationImage mediaImage(MediaItem media) { String image = ''; - if (media.type == MediaType.image) { + if (media.type == MediaType.image || media.type == MediaType.location) { image = media.reference; } else if (media.type == MediaType.youtube) { image = 'https://img.youtube.com/vi/${media.reference}/0.jpg'; @@ -26,19 +21,6 @@ class MediaCard extends StatelessWidget { return DecorationImage(image: NetworkImage(image), fit: BoxFit.cover); } - Widget mediaItem(MediaItem media) { - if (media.type == MediaType.image) { - return Image(image: NetworkImage(media.reference)); - } else if (media.type == MediaType.youtube) { - return YoutubePlayer( - controller: controller, - aspectRatio: 16 / 9, - ); - } - - return const Image(image: AssetImage('assets/images/placeholder.jpg')); - } - return Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(10), @@ -50,42 +32,7 @@ class MediaCard extends StatelessWidget { RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), shadowColor: const Color.fromARGB(0, 255, 255, 255), child: TextButton( - onPressed: () => showModalBottomSheet( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(10.0)), - ), - context: context, - showDragHandle: true, - isScrollControlled: true, - useSafeArea: true, - builder: (BuildContext context) { - return Padding( - padding: EdgeInsets.fromLTRB(15, 0, 15, 15), - child: Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - ClipRRect( - borderRadius: BorderRadius.circular(10), - child: mediaItem(media), - ), - const SizedBox(height: 15), - Padding( - padding: EdgeInsets.fromLTRB(15, 0, 15, 15), - child: Text( - media.description, - style: const TextStyle(fontSize: 20), - )), - const Divider( - indent: 20, - endIndent: 20, - ) - ])); - // const Text( - // 'Comments', - // style: TextStyle(fontSize: 20), - // ), - }), + onPressed: () => showMediaDetailWidget(context, media), child: const ListTile( title: Text(''), )))); diff --git a/lib/widgets/media/media_content.dart b/lib/widgets/media/media_content.dart new file mode 100644 index 0000000..b90df24 --- /dev/null +++ b/lib/widgets/media/media_content.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; +import 'package:sendtrain/database/database.dart'; +import 'package:youtube_player_flutter/youtube_player_flutter.dart'; + +class MediaContent extends StatelessWidget { + const MediaContent({super.key, required this.media}); + + final MediaItem media; + + @override + Widget build(BuildContext context) { + YoutubePlayerController controller = YoutubePlayerController( + initialVideoId: media.reference, + flags: const YoutubePlayerFlags( + autoPlay: false, mute: true, showLiveFullscreenButton: false)); + + if (media.type == MediaType.image || media.type == MediaType.location) { + return Image(image: NetworkImage(media.reference)); + } else if (media.type == MediaType.youtube) { + return YoutubePlayer( + controller: controller, + aspectRatio: 16 / 9, + ); + } + + return const Image(image: AssetImage('assets/images/placeholder.jpg')); + } +} diff --git a/lib/widgets/media/media_details.dart b/lib/widgets/media/media_details.dart new file mode 100644 index 0000000..7604dd0 --- /dev/null +++ b/lib/widgets/media/media_details.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; +import 'package:sendtrain/database/database.dart'; +import 'package:sendtrain/widgets/media/media_content.dart'; + +class MediaDetails extends StatelessWidget { + const MediaDetails({super.key, required this.media}); + + final MediaItem media; + + @override + Widget build(BuildContext context) { + return Padding( + padding: EdgeInsets.fromLTRB(15, 0, 15, 15), + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(10), + child: MediaContent(media: media), + ), + const SizedBox(height: 15), + Padding( + padding: EdgeInsets.fromLTRB(15, 0, 15, 15), + child: Text( + media.description, + style: const TextStyle(fontSize: 20), + )), + const Divider( + indent: 20, + endIndent: 20, + ) + ])); + } +} diff --git a/lib/widgets/sessions/session_card_full.dart b/lib/widgets/sessions/session_card_full.dart index eb80ae2..458214d 100644 --- a/lib/widgets/sessions/session_card_full.dart +++ b/lib/widgets/sessions/session_card_full.dart @@ -5,10 +5,12 @@ import 'package:sendtrain/database/database.dart'; import 'package:sendtrain/extensions/string_extensions.dart'; import 'package:sendtrain/helpers/date_time_helpers.dart'; import 'package:sendtrain/helpers/media_helpers.dart'; +import 'package:sendtrain/helpers/widget_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'; +import 'package:collection/collection.dart'; class SessionCardFull extends StatefulWidget { const SessionCardFull( @@ -22,6 +24,9 @@ class SessionCardFull extends StatefulWidget { } class _SessionCardFullState extends State { + late final List mediaItems; + late final MediaItem? sessionImage; + late final Session session; String sessionTitle(Session session) { String title = session.title.toTitleCase(); @@ -32,10 +37,16 @@ class _SessionCardFullState extends State { } @override - Widget build(BuildContext context) { - final Session session = widget.session; - final List mediaItems = widget.mediaItems; + initState() { + super.initState(); + session = widget.session; + mediaItems = widget.mediaItems; + sessionImage = mediaItems + .firstWhereOrNull((mediaItem) => mediaItem.type == MediaType.location); + } + @override + Widget build(BuildContext context) { return Card( color: (session.status == SessionStatus.started) ? Theme.of(context).colorScheme.primaryContainer @@ -44,6 +55,7 @@ class _SessionCardFullState extends State { clipBehavior: Clip.hardEdge, child: InkWell( splashColor: Colors.deepPurple, + onLongPress: () => showMediaDetailWidget(context, sessionImage!), onTap: () => showGenericDialog(SessionView(session: session), context), child: Column( @@ -52,7 +64,7 @@ class _SessionCardFullState extends State { ListTile( contentPadding: EdgeInsets.only(left: 8), leading: CardImage( - image: findMediaByType(mediaItems, 'image'), + image: findMediaByType(mediaItems, MediaType.location), padding: EdgeInsets.only(left: 5, top: 5)), title: Text(maxLines: 1, sessionTitle(session)), subtitle: Text( diff --git a/lib/widgets/sessions/session_card_small.dart b/lib/widgets/sessions/session_card_small.dart index 3609a93..370916f 100644 --- a/lib/widgets/sessions/session_card_small.dart +++ b/lib/widgets/sessions/session_card_small.dart @@ -3,8 +3,10 @@ import 'package:sendtrain/database/database.dart'; import 'package:sendtrain/extensions/string_extensions.dart'; import 'package:sendtrain/helpers/date_time_helpers.dart'; import 'package:sendtrain/helpers/media_helpers.dart'; +import 'package:sendtrain/helpers/widget_helpers.dart'; import 'package:sendtrain/widgets/builders/dialogs.dart'; import 'package:sendtrain/widgets/sessions/session_view.dart'; +import 'package:collection/collection.dart'; class SessionCardSmall extends StatefulWidget { const SessionCardSmall( @@ -18,11 +20,20 @@ class SessionCardSmall extends StatefulWidget { } class _SessionCardSmallState extends State { + late final List mediaItems; + late final MediaItem? sessionImage; + late final Session session; + + @override + initState() { + super.initState(); + session = widget.session; + mediaItems = widget.mediaItems; + sessionImage = mediaItems.firstWhereOrNull((mediaItem) => mediaItem.type == MediaType.location); + } + @override Widget build(BuildContext context) { - final Session session = widget.session; - final List mediaItems = widget.mediaItems; - return Card( color: (session.status == SessionStatus.started) ? Theme.of(context).colorScheme.primaryContainer @@ -31,7 +42,9 @@ class _SessionCardSmallState extends State { // overlayColor: MaterialStateColor(Colors.deepPurple as int), splashColor: Colors.deepPurple, borderRadius: const BorderRadius.all(Radius.elliptical(10, 10)), - onTap: () => showGenericDialog(SessionView(session: session), context), + onLongPress: () => showMediaDetailWidget(context, sessionImage!), + onTap: () => + showGenericDialog(SessionView(session: session), context), child: Container( decoration: BoxDecoration( // color: const Color.fromARGB(47, 0, 0, 0), @@ -39,7 +52,7 @@ class _SessionCardSmallState extends State { image: DecorationImage( colorFilter: ColorFilter.mode( Color.fromARGB(220, 41, 39, 39), BlendMode.hardLight), - image: findMediaByType(mediaItems, 'image'), + image: findMediaByType(mediaItems, MediaType.location), fit: BoxFit.cover), ), child: Align( diff --git a/lib/widgets/sessions/session_creator.dart b/lib/widgets/sessions/session_editor.dart similarity index 58% rename from lib/widgets/sessions/session_creator.dart rename to lib/widgets/sessions/session_editor.dart index a1e1c38..3a4204d 100644 --- a/lib/widgets/sessions/session_creator.dart +++ b/lib/widgets/sessions/session_editor.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:math'; import 'package:drift/drift.dart' hide Column; import 'package:flutter/material.dart'; @@ -14,14 +13,15 @@ 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}); +class SessionEditor extends StatefulWidget { + const SessionEditor({super.key, this.data, this.session, this.callback}); final Session? session; final Map? data; + final Function? callback; @override - State createState() => _SessionCreatorState(); + State createState() => _SessionEditorState(); } // used to pass the result of the found image back to current context... @@ -30,8 +30,10 @@ class SessionPayload { String? address; } -class _SessionCreatorState extends State { +class _SessionEditorState extends State { final GlobalKey _formKey = GlobalKey(); + late AppDatabase db; + String editorType = 'Create'; final Map sessionCreateController = { 'name': TextEditingController(), @@ -44,45 +46,78 @@ class _SessionCreatorState extends State { final SessionPayload sessionPayload = SessionPayload(); + @override + void initState() { + super.initState(); + db = Provider.of(context, listen: false); + + // if we're editing a session, we'll want to populate it with the appropriate values + if (widget.session != null) { + editorType = 'Edit'; + final Session session = widget.session!; + sessionCreateController['name']?.text = session.title; + sessionCreateController['content']?.text = session.content; + sessionCreateController['status']?.text = session.status.name; + sessionCreateController['date']?.text = + DateFormat('yyyy-MM-dd').format(session.date!); + if (session.address != null) { + sessionCreateController['address']?.text = session.address!; + } + } + } + Future createSession(context) async { Map payload = { Symbol('title'): Value(sessionCreateController['name']!.text), Symbol('content'): Value(sessionCreateController['content']!.text), - Symbol('status'): Value(SessionStatus.pending), + // we want to maintain existing status during update + Symbol('status'): widget.session != null + ? Value(widget.session!.status) + : Value(SessionStatus.pending), Symbol('date'): Value( DateTime.parse(sessionCreateController['date']!.text)), }; + // if a session exists we'll want to update it + // so the payload needs the session id + if (widget.session != null) { + payload[Symbol('id')] = Value(widget.session!.id); + } + // optional params if (sessionCreateController['address']!.text.isNotEmpty) { payload[Symbol('address')] = Value(sessionCreateController['address']!.text); } - return await SessionsDao(Provider.of(context, listen: false)) + return await SessionsDao(db) .createOrUpdate(Function.apply(SessionsCompanion.new, [], payload)); } Future createSessionMedia(context, sessionId) async { + List deletedMedia = + await MediaItemsDao(db).fromSession(sessionId) + ..where((mediaItem) => mediaItem.type == MediaType.location); + + await MediaItemsDao(db).removeAll(deletedMedia.map((m) => m.id)); + if (sessionPayload.photoUri != null) { MediaItemsCompanion mediaItem = MediaItemsCompanion( title: Value('Location Image'), description: Value(sessionPayload.address!), reference: Value(sessionPayload.photoUri!), - type: Value(MediaType.image)); + type: Value(MediaType.location)); - return await MediaItemsDao( - Provider.of(context, listen: false)) - .createOrUpdate(mediaItem).then((id) async { - ObjectMediaItemsCompanion omi = ObjectMediaItemsCompanion( - objectId: Value(sessionId), - objectType: Value(ObjectType.sessions), - mediaId: Value(id), - ); + return await MediaItemsDao(db).createOrUpdate(mediaItem).then((id) async { + ObjectMediaItemsCompanion omi = ObjectMediaItemsCompanion( + objectId: Value(sessionId), + objectType: Value(ObjectType.sessions), + mediaId: Value(id), + ); - await ObjectMediaItemsDao(Provider.of(context, listen: false)).createOrUpdate(omi); - }); + await ObjectMediaItemsDao(db).createOrUpdate(omi); + }); } } @@ -91,8 +126,6 @@ class _SessionCreatorState extends State { sessionCreateController['date']!.text = DateFormat('yyyy-MM-dd').format(DateTime.now()); - AppDatabase db = Provider.of(context, listen: false); - return Padding( padding: EdgeInsets.fromLTRB(15, 0, 15, 15), child: Form( @@ -103,7 +136,7 @@ class _SessionCreatorState extends State { children: [ Padding( padding: EdgeInsets.only(top: 10, bottom: 10), - child: Text('Create Session', + child: Text('$editorType Session', textAlign: TextAlign.center, style: Theme.of(context).textTheme.titleLarge)), FormTextInput( @@ -166,19 +199,43 @@ class _SessionCreatorState extends State { if (_formKey.currentState!.validate()) { await createSession(_formKey.currentContext) - .then((id) async => { - await createSessionMedia( - _formKey.currentContext, id), - SessionsDao(db).find(id).then( - (session) => showGenericDialog( - SessionView( - session: session), - _formKey - .currentContext!)), - Navigator.pop( - _formKey.currentContext!, - 'Submit') - }) + .then((sessionId) async { + int currentSessionId = sessionId; + + // dirft weirdly doesn't return the proper id if + // an upsert ends up bein an update, so we'll + // set the id to the provided session for an update + if (widget.session != null) { + currentSessionId = widget.session!.id; + } + + // if we've found a photo add it to media! + if (sessionPayload.photoUri != null) { + await createSessionMedia( + _formKey.currentContext, + currentSessionId); + } + + // if session is null it's new so we show the dialog + // otherwise the dialog is already open, so no need + if (widget.session == null) { + SessionsDao(db) + .find(currentSessionId) + .then((session) => + showGenericDialog( + SessionView( + session: session), + _formKey.currentContext!)); + } + + Navigator.pop( + _formKey.currentContext!, 'Submit'); + + if (widget.callback != null) { + await widget + .callback!(widget.session!.id); + } + }) } }, child: Text('Submit'))) diff --git a/lib/widgets/sessions/session_view.dart b/lib/widgets/sessions/session_view.dart index 4e88728..79620c8 100644 --- a/lib/widgets/sessions/session_view.dart +++ b/lib/widgets/sessions/session_view.dart @@ -5,9 +5,11 @@ import 'package:intl/date_symbol_data_local.dart'; import 'package:provider/provider.dart'; import 'package:sendtrain/daos/activities_dao.dart'; +import 'package:sendtrain/daos/sessions_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_editor.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'; @@ -22,13 +24,45 @@ class SessionView extends StatefulWidget { } class _SessionViewState extends State { + final _fabKey = GlobalKey(); + late Session session; + late DateFormat dateFormat; + + String title() { + String title = session.title.toTitleCase(); + + if (session.address != null) { + title = "$title @ ${session.address}"; + } + + return title; + } + + void resetState(int sessionId) async { + Session updatedSession = + await SessionsDao(Provider.of(context, listen: false)) + .find(sessionId); + + final state = _fabKey.currentState; + if (state != null) { + state.toggle(); + } + + setState(() { + session = updatedSession; + }); + } + + @override + initState() { + super.initState(); + initializeDateFormatting('en'); + dateFormat = DateFormat('yyyy-MM-dd'); + session = widget.session; + } + @override Widget build(BuildContext context) { - final Session session = widget.session; - - initializeDateFormatting('en'); - final DateFormat dateFormat = DateFormat('yyyy-MM-dd'); - return StreamBuilder>( stream: ActivitiesDao(Provider.of(context)) .watchSessionActivities(session.id), @@ -39,6 +73,7 @@ class _SessionViewState extends State { return Scaffold( floatingActionButtonLocation: ExpandableFab.location, floatingActionButton: ExpandableFab( + key: _fabKey, distance: 70, type: ExpandableFabType.up, overlayStyle: ExpandableFabOverlayStyle( @@ -49,17 +84,55 @@ class _SessionViewState extends State { FloatingActionButton.extended( icon: const Icon(Icons.history_outlined), label: Text('Restart'), - onPressed: () {}, + onPressed: () { + Session newSession = + session.copyWith(status: SessionStatus.pending); + + SessionsDao(Provider.of(context, + listen: false)) + .replace(newSession); + + final state = _fabKey.currentState; + if (state != null) { + state.toggle(); + } + }, ), FloatingActionButton.extended( icon: const Icon(Icons.done_all_outlined), label: Text('Done'), - onPressed: () {}, + onPressed: () { + Session newSession = + session.copyWith(status: SessionStatus.completed); + + SessionsDao(Provider.of(context, + listen: false)) + .replace(newSession); + + final state = _fabKey.currentState; + if (state != null) { + state.toggle(); + } + }, ), FloatingActionButton.extended( icon: const Icon(Icons.edit_outlined), label: Text('Edit'), - onPressed: () {}, + onPressed: () { + showModalBottomSheet( + 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( @@ -78,7 +151,7 @@ class _SessionViewState extends State { maxLines: 1, style: const TextStyle( fontSize: 25, fontWeight: FontWeight.bold), - session.title.toTitleCase())), + title())), SessionViewAchievements(session: session), Padding( padding: const EdgeInsets.only(left: 15, right: 15), diff --git a/lib/widgets/sessions/session_view_media.dart b/lib/widgets/sessions/session_view_media.dart index 6234cc8..63a81c2 100644 --- a/lib/widgets/sessions/session_view_media.dart +++ b/lib/widgets/sessions/session_view_media.dart @@ -13,7 +13,7 @@ class SessionViewMedia extends StatelessWidget { Widget build(BuildContext context) { return FutureBuilder>( future: MediaItemsDao(Provider.of(context)) - .fromSession(session), + .fromSession(session.id), builder: (context, snapshot) { if (snapshot.hasData) { final mediaItems = snapshot.data!;