media item and session images and location management, also refactoring and DRYing up code

This commit is contained in:
Joshua Burman 2024-12-31 22:41:17 -05:00
parent 5f628d6b48
commit 10332ec8be
17 changed files with 328 additions and 129 deletions

View File

@ -39,7 +39,7 @@ class MediaItemsDao extends DatabaseAccessor<AppDatabase>
return mediaItems;
}
Future<List<MediaItem>> fromSession(Session session) async {
Future<List<MediaItem>> fromSession(int sessionId) async {
final result = select(db.objectMediaItems).join(
[
innerJoin(
@ -49,7 +49,7 @@ class MediaItemsDao extends DatabaseAccessor<AppDatabase>
],
)
..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<AppDatabase>
return mediaItems;
});
}
Future removeAll(Iterable<int> mediaItemIds) {
return (delete(mediaItems)..where((table) => table.id.isIn(mediaItemIds)))
.go();
}
}

View File

@ -8,11 +8,9 @@ class SessionsDao extends DatabaseAccessor<AppDatabase> with _$SessionsDaoMixin
SessionsDao(super.db);
Future<Session> find(int id) => (select(sessions)..where((session) => session.id.equals(id) )).getSingle();
Future<List<Session>> all() => select(sessions).get();
Future<List<Session>> all() => (select(sessions)..orderBy([(session) => OrderingTerm(expression: session.createdAt, mode: OrderingMode.desc)])).get();
Stream<List<Session>> 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);
}

View File

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

View File

@ -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<TableInfo<Table, Object?>> get allTables =>
allSchemaEntities.whereType<TableInfo<Table, Object?>>();

View File

@ -1,13 +1,16 @@
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);
ImageProvider findMediaByType(List<MediaItem> media, MediaType type) {
Iterable<MediaItem>? 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');
}
}
if (found.isNotEmpty) {
image = Image.network(found.first.reference);
} else {
// Element is not found
image = Image.asset('assets/images/placeholder.jpg');
}
return image.image;
}

View File

@ -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<void>(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(10.0)),
),
context: context,
showDragHandle: true,
isScrollControlled: true,
useSafeArea: true,
builder: (BuildContext context) {
return MediaDetails(media: media);
});
}

View File

@ -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<App> {
isScrollControlled: true,
useSafeArea: true,
builder: (BuildContext context) {
return SessionCreator();
return SessionEditor();
});
},
label: const Text('New Session'),

View File

@ -48,7 +48,7 @@ class ActivityCardState extends State<ActivityCard> {
ListTile(
// visualDensity: VisualDensity(horizontal: VisualDensity.maximumDensity),
leading: CardImage(
image: findMediaByType(mediaItems, 'image')),
image: findMediaByType(mediaItems, MediaType.image)),
title: Consumer<ActivityTimerModel>(
builder: (context, atm, child) {
if (atm.activity?.id == widget.activity.id) {

View File

@ -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)),

View File

@ -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<void>(
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: <Widget>[
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(''),
))));

View File

@ -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'));
}
}

View File

@ -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: <Widget>[
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,
)
]));
}
}

View File

@ -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<SessionCardFull> {
late final List<MediaItem> 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<SessionCardFull> {
}
@override
Widget build(BuildContext context) {
final Session session = widget.session;
final List<MediaItem> 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<SessionCardFull> {
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<SessionCardFull> {
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(

View File

@ -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<SessionCardSmall> {
late final List<MediaItem> 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<MediaItem> mediaItems = widget.mediaItems;
return Card(
color: (session.status == SessionStatus.started)
? Theme.of(context).colorScheme.primaryContainer
@ -31,7 +42,9 @@ class _SessionCardSmallState extends State<SessionCardSmall> {
// 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<SessionCardSmall> {
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(

View File

@ -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<String, dynamic>? data;
final Function? callback;
@override
State<SessionCreator> createState() => _SessionCreatorState();
State<SessionEditor> 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<SessionCreator> {
class _SessionEditorState extends State<SessionEditor> {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
late AppDatabase db;
String editorType = 'Create';
final Map<String, TextEditingController> sessionCreateController = {
'name': TextEditingController(),
@ -44,45 +46,78 @@ class _SessionCreatorState extends State<SessionCreator> {
final SessionPayload sessionPayload = SessionPayload();
@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.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<Symbol, Value> payload = {
Symbol('title'): Value<String>(sessionCreateController['name']!.text),
Symbol('content'):
Value<String>(sessionCreateController['content']!.text),
Symbol('status'): Value<SessionStatus>(SessionStatus.pending),
// we want to maintain existing status during update
Symbol('status'): widget.session != null
? Value<SessionStatus>(widget.session!.status)
: Value<SessionStatus>(SessionStatus.pending),
Symbol('date'): Value<DateTime>(
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<int>(widget.session!.id);
}
// optional params
if (sessionCreateController['address']!.text.isNotEmpty) {
payload[Symbol('address')] =
Value<String>(sessionCreateController['address']!.text);
}
return await SessionsDao(Provider.of<AppDatabase>(context, listen: false))
return await SessionsDao(db)
.createOrUpdate(Function.apply(SessionsCompanion.new, [], payload));
}
Future createSessionMedia(context, sessionId) async {
List<MediaItem> 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<AppDatabase>(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<AppDatabase>(context, listen: false)).createOrUpdate(omi);
});
await ObjectMediaItemsDao(db).createOrUpdate(omi);
});
}
}
@ -91,8 +126,6 @@ class _SessionCreatorState extends State<SessionCreator> {
sessionCreateController['date']!.text =
DateFormat('yyyy-MM-dd').format(DateTime.now());
AppDatabase db = Provider.of<AppDatabase>(context, listen: false);
return Padding(
padding: EdgeInsets.fromLTRB(15, 0, 15, 15),
child: Form(
@ -103,7 +136,7 @@ class _SessionCreatorState extends State<SessionCreator> {
children: <Widget>[
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<SessionCreator> {
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')))

View File

@ -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<SessionView> {
final _fabKey = GlobalKey<ExpandableFabState>();
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<AppDatabase>(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<List<Activity>>(
stream: ActivitiesDao(Provider.of<AppDatabase>(context))
.watchSessionActivities(session.id),
@ -39,6 +73,7 @@ class _SessionViewState extends State<SessionView> {
return Scaffold(
floatingActionButtonLocation: ExpandableFab.location,
floatingActionButton: ExpandableFab(
key: _fabKey,
distance: 70,
type: ExpandableFabType.up,
overlayStyle: ExpandableFabOverlayStyle(
@ -49,17 +84,55 @@ class _SessionViewState extends State<SessionView> {
FloatingActionButton.extended(
icon: const Icon(Icons.history_outlined),
label: Text('Restart'),
onPressed: () {},
onPressed: () {
Session newSession =
session.copyWith(status: SessionStatus.pending);
SessionsDao(Provider.of<AppDatabase>(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<AppDatabase>(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<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(
@ -78,7 +151,7 @@ class _SessionViewState extends State<SessionView> {
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),

View File

@ -13,7 +13,7 @@ class SessionViewMedia extends StatelessWidget {
Widget build(BuildContext context) {
return FutureBuilder<List<MediaItem>>(
future: MediaItemsDao(Provider.of<AppDatabase>(context))
.fromSession(session),
.fromSession(session.id),
builder: (context, snapshot) {
if (snapshot.hasData) {
final mediaItems = snapshot.data!;