media item and session images and location management, also refactoring and DRYing up code
This commit is contained in:
parent
5f628d6b48
commit
10332ec8be
@ -39,7 +39,7 @@ class MediaItemsDao extends DatabaseAccessor<AppDatabase>
|
|||||||
return mediaItems;
|
return mediaItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<MediaItem>> fromSession(Session session) async {
|
Future<List<MediaItem>> fromSession(int sessionId) async {
|
||||||
final result = select(db.objectMediaItems).join(
|
final result = select(db.objectMediaItems).join(
|
||||||
[
|
[
|
||||||
innerJoin(
|
innerJoin(
|
||||||
@ -49,7 +49,7 @@ class MediaItemsDao extends DatabaseAccessor<AppDatabase>
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
..where(db.objectMediaItems.objectType.equals(ObjectType.sessions.name))
|
..where(db.objectMediaItems.objectType.equals(ObjectType.sessions.name))
|
||||||
..where(db.objectMediaItems.objectId.equals(session.id));
|
..where(db.objectMediaItems.objectId.equals(sessionId));
|
||||||
|
|
||||||
final mediaItems =
|
final mediaItems =
|
||||||
(await result.get()).map((e) => e.readTable(db.mediaItems)).toList();
|
(await result.get()).map((e) => e.readTable(db.mediaItems)).toList();
|
||||||
@ -75,4 +75,9 @@ class MediaItemsDao extends DatabaseAccessor<AppDatabase>
|
|||||||
return mediaItems;
|
return mediaItems;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future removeAll(Iterable<int> mediaItemIds) {
|
||||||
|
return (delete(mediaItems)..where((table) => table.id.isIn(mediaItemIds)))
|
||||||
|
.go();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,11 +8,9 @@ class SessionsDao extends DatabaseAccessor<AppDatabase> with _$SessionsDaoMixin
|
|||||||
SessionsDao(super.db);
|
SessionsDao(super.db);
|
||||||
|
|
||||||
Future<Session> find(int id) => (select(sessions)..where((session) => session.id.equals(id) )).getSingle();
|
Future<Session> find(int id) => (select(sessions)..where((session) => session.id.equals(id) )).getSingle();
|
||||||
Future<List<Session>> all() => select(sessions).get();
|
Future<List<Session>> all() => (select(sessions)..orderBy([(session) => OrderingTerm(expression: session.createdAt, mode: OrderingMode.desc)])).get();
|
||||||
Stream<List<Session>> watch() => select(sessions).watch();
|
Stream<List<Session>> watch() => select(sessions).watch();
|
||||||
Future createOrUpdate(SessionsCompanion session) => into(sessions).insertOnConflictUpdate(session);
|
Future createOrUpdate(SessionsCompanion session) => into(sessions).insertOnConflictUpdate(session);
|
||||||
// Future replace(Session session) => update(sessions).replace(session);
|
Future replace(Session session) => update(sessions).replace(session);
|
||||||
Future remove(Session session) => delete(sessions).delete(session);
|
Future remove(Session session) => delete(sessions).delete(session);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
@ -35,7 +35,7 @@ class AppDatabase extends _$AppDatabase {
|
|||||||
AppDatabase() : super(_openConnection());
|
AppDatabase() : super(_openConnection());
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get schemaVersion => 6;
|
int get schemaVersion => 7;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
MigrationStrategy get migration {
|
MigrationStrategy get migration {
|
||||||
@ -141,7 +141,7 @@ class ObjectMediaItems extends Table {
|
|||||||
dateTime().withDefault(Variable(DateTime.now()))();
|
dateTime().withDefault(Variable(DateTime.now()))();
|
||||||
}
|
}
|
||||||
|
|
||||||
enum MediaType { youtube, image }
|
enum MediaType { youtube, image, location }
|
||||||
|
|
||||||
class MediaItems extends Table {
|
class MediaItems extends Table {
|
||||||
IntColumn get id => integer().autoIncrement()();
|
IntColumn get id => integer().autoIncrement()();
|
||||||
|
@ -2398,6 +2398,16 @@ abstract class _$AppDatabase extends GeneratedDatabase {
|
|||||||
late final $MediaItemsTable mediaItems = $MediaItemsTable(this);
|
late final $MediaItemsTable mediaItems = $MediaItemsTable(this);
|
||||||
late final $ObjectMediaItemsTable objectMediaItems =
|
late final $ObjectMediaItemsTable objectMediaItems =
|
||||||
$ObjectMediaItemsTable(this);
|
$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
|
@override
|
||||||
Iterable<TableInfo<Table, Object?>> get allTables =>
|
Iterable<TableInfo<Table, Object?>> get allTables =>
|
||||||
allSchemaEntities.whereType<TableInfo<Table, Object?>>();
|
allSchemaEntities.whereType<TableInfo<Table, Object?>>();
|
||||||
|
@ -1,13 +1,16 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:sendtrain/database/database.dart';
|
import 'package:sendtrain/database/database.dart';
|
||||||
|
|
||||||
ImageProvider findMediaByType(List<MediaItem> media, String type) {
|
ImageProvider findMediaByType(List<MediaItem> media, MediaType type) {
|
||||||
Iterable<MediaItem>? found = media.where((m) => m.type == MediaType.image);
|
Iterable<MediaItem>? found = media.where((m) => m.type == type);
|
||||||
|
Image image;
|
||||||
|
|
||||||
if (found.isNotEmpty) {
|
if (found.isNotEmpty) {
|
||||||
return NetworkImage(found.first.reference);
|
image = Image.network(found.first.reference);
|
||||||
} else {
|
} else {
|
||||||
// Element is not found
|
// Element is not found
|
||||||
return const AssetImage('assets/images/placeholder.jpg');
|
image = Image.asset('assets/images/placeholder.jpg');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return image.image;
|
||||||
}
|
}
|
17
lib/helpers/widget_helpers.dart
Normal file
17
lib/helpers/widget_helpers.dart
Normal 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);
|
||||||
|
});
|
||||||
|
}
|
@ -6,7 +6,7 @@ import 'package:sendtrain/widgets/screens/activities_screen.dart';
|
|||||||
import 'package:sendtrain/widgets/screens/sessions_screen.dart';
|
import 'package:sendtrain/widgets/screens/sessions_screen.dart';
|
||||||
// ignore: unused_import
|
// ignore: unused_import
|
||||||
import 'package:sendtrain/database/seed.dart';
|
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 {
|
class SendTrain extends StatelessWidget {
|
||||||
const SendTrain({super.key});
|
const SendTrain({super.key});
|
||||||
@ -108,7 +108,7 @@ class _AppState extends State<App> {
|
|||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
useSafeArea: true,
|
useSafeArea: true,
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return SessionCreator();
|
return SessionEditor();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
label: const Text('New Session'),
|
label: const Text('New Session'),
|
||||||
|
@ -48,7 +48,7 @@ class ActivityCardState extends State<ActivityCard> {
|
|||||||
ListTile(
|
ListTile(
|
||||||
// visualDensity: VisualDensity(horizontal: VisualDensity.maximumDensity),
|
// visualDensity: VisualDensity(horizontal: VisualDensity.maximumDensity),
|
||||||
leading: CardImage(
|
leading: CardImage(
|
||||||
image: findMediaByType(mediaItems, 'image')),
|
image: findMediaByType(mediaItems, MediaType.image)),
|
||||||
title: Consumer<ActivityTimerModel>(
|
title: Consumer<ActivityTimerModel>(
|
||||||
builder: (context, atm, child) {
|
builder: (context, atm, child) {
|
||||||
if (atm.activity?.id == widget.activity.id) {
|
if (atm.activity?.id == widget.activity.id) {
|
||||||
|
@ -21,6 +21,7 @@ class CardImage extends StatelessWidget {
|
|||||||
image: DecorationImage(
|
image: DecorationImage(
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
image: image,
|
image: image,
|
||||||
|
onError: (error, stackTrace) => AssetImage('assets/images/placeholder.jpg')
|
||||||
// color: Colors.blue,
|
// color: Colors.blue,
|
||||||
),
|
),
|
||||||
borderRadius: BorderRadius.all(Radius.elliptical(8, 8)),
|
borderRadius: BorderRadius.all(Radius.elliptical(8, 8)),
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:sendtrain/database/database.dart';
|
import 'package:sendtrain/database/database.dart';
|
||||||
import 'package:youtube_player_flutter/youtube_player_flutter.dart';
|
import 'package:sendtrain/helpers/widget_helpers.dart';
|
||||||
|
|
||||||
class MediaCard extends StatelessWidget {
|
class MediaCard extends StatelessWidget {
|
||||||
const MediaCard({super.key, required this.media});
|
const MediaCard({super.key, required this.media});
|
||||||
@ -9,15 +9,10 @@ class MediaCard extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
YoutubePlayerController controller = YoutubePlayerController(
|
|
||||||
initialVideoId: media.reference,
|
|
||||||
flags: const YoutubePlayerFlags(
|
|
||||||
autoPlay: false, mute: true, showLiveFullscreenButton: false));
|
|
||||||
|
|
||||||
DecorationImage mediaImage(MediaItem media) {
|
DecorationImage mediaImage(MediaItem media) {
|
||||||
String image = '';
|
String image = '';
|
||||||
|
|
||||||
if (media.type == MediaType.image) {
|
if (media.type == MediaType.image || media.type == MediaType.location) {
|
||||||
image = media.reference;
|
image = media.reference;
|
||||||
} else if (media.type == MediaType.youtube) {
|
} else if (media.type == MediaType.youtube) {
|
||||||
image = 'https://img.youtube.com/vi/${media.reference}/0.jpg';
|
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);
|
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(
|
return Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(10),
|
borderRadius: BorderRadius.circular(10),
|
||||||
@ -50,42 +32,7 @@ class MediaCard extends StatelessWidget {
|
|||||||
RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
||||||
shadowColor: const Color.fromARGB(0, 255, 255, 255),
|
shadowColor: const Color.fromARGB(0, 255, 255, 255),
|
||||||
child: TextButton(
|
child: TextButton(
|
||||||
onPressed: () => showModalBottomSheet<void>(
|
onPressed: () => showMediaDetailWidget(context, media),
|
||||||
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),
|
|
||||||
// ),
|
|
||||||
}),
|
|
||||||
child: const ListTile(
|
child: const ListTile(
|
||||||
title: Text(''),
|
title: Text(''),
|
||||||
))));
|
))));
|
||||||
|
28
lib/widgets/media/media_content.dart
Normal file
28
lib/widgets/media/media_content.dart
Normal 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'));
|
||||||
|
}
|
||||||
|
}
|
35
lib/widgets/media/media_details.dart
Normal file
35
lib/widgets/media/media_details.dart
Normal 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,
|
||||||
|
)
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
}
|
@ -5,10 +5,12 @@ import 'package:sendtrain/database/database.dart';
|
|||||||
import 'package:sendtrain/extensions/string_extensions.dart';
|
import 'package:sendtrain/extensions/string_extensions.dart';
|
||||||
import 'package:sendtrain/helpers/date_time_helpers.dart';
|
import 'package:sendtrain/helpers/date_time_helpers.dart';
|
||||||
import 'package:sendtrain/helpers/media_helpers.dart';
|
import 'package:sendtrain/helpers/media_helpers.dart';
|
||||||
|
import 'package:sendtrain/helpers/widget_helpers.dart';
|
||||||
import 'package:sendtrain/widgets/builders/dialogs.dart';
|
import 'package:sendtrain/widgets/builders/dialogs.dart';
|
||||||
import 'package:sendtrain/widgets/generic/elements/card_content.dart';
|
import 'package:sendtrain/widgets/generic/elements/card_content.dart';
|
||||||
import 'package:sendtrain/widgets/generic/elements/card_image.dart';
|
import 'package:sendtrain/widgets/generic/elements/card_image.dart';
|
||||||
import 'package:sendtrain/widgets/sessions/session_view.dart';
|
import 'package:sendtrain/widgets/sessions/session_view.dart';
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
|
|
||||||
class SessionCardFull extends StatefulWidget {
|
class SessionCardFull extends StatefulWidget {
|
||||||
const SessionCardFull(
|
const SessionCardFull(
|
||||||
@ -22,6 +24,9 @@ class SessionCardFull extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _SessionCardFullState extends State<SessionCardFull> {
|
class _SessionCardFullState extends State<SessionCardFull> {
|
||||||
|
late final List<MediaItem> mediaItems;
|
||||||
|
late final MediaItem? sessionImage;
|
||||||
|
late final Session session;
|
||||||
|
|
||||||
String sessionTitle(Session session) {
|
String sessionTitle(Session session) {
|
||||||
String title = session.title.toTitleCase();
|
String title = session.title.toTitleCase();
|
||||||
@ -32,10 +37,16 @@ class _SessionCardFullState extends State<SessionCardFull> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
initState() {
|
||||||
final Session session = widget.session;
|
super.initState();
|
||||||
final List<MediaItem> mediaItems = widget.mediaItems;
|
session = widget.session;
|
||||||
|
mediaItems = widget.mediaItems;
|
||||||
|
sessionImage = mediaItems
|
||||||
|
.firstWhereOrNull((mediaItem) => mediaItem.type == MediaType.location);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
return Card(
|
return Card(
|
||||||
color: (session.status == SessionStatus.started)
|
color: (session.status == SessionStatus.started)
|
||||||
? Theme.of(context).colorScheme.primaryContainer
|
? Theme.of(context).colorScheme.primaryContainer
|
||||||
@ -44,6 +55,7 @@ class _SessionCardFullState extends State<SessionCardFull> {
|
|||||||
clipBehavior: Clip.hardEdge,
|
clipBehavior: Clip.hardEdge,
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
splashColor: Colors.deepPurple,
|
splashColor: Colors.deepPurple,
|
||||||
|
onLongPress: () => showMediaDetailWidget(context, sessionImage!),
|
||||||
onTap: () =>
|
onTap: () =>
|
||||||
showGenericDialog(SessionView(session: session), context),
|
showGenericDialog(SessionView(session: session), context),
|
||||||
child: Column(
|
child: Column(
|
||||||
@ -52,7 +64,7 @@ class _SessionCardFullState extends State<SessionCardFull> {
|
|||||||
ListTile(
|
ListTile(
|
||||||
contentPadding: EdgeInsets.only(left: 8),
|
contentPadding: EdgeInsets.only(left: 8),
|
||||||
leading: CardImage(
|
leading: CardImage(
|
||||||
image: findMediaByType(mediaItems, 'image'),
|
image: findMediaByType(mediaItems, MediaType.location),
|
||||||
padding: EdgeInsets.only(left: 5, top: 5)),
|
padding: EdgeInsets.only(left: 5, top: 5)),
|
||||||
title: Text(maxLines: 1, sessionTitle(session)),
|
title: Text(maxLines: 1, sessionTitle(session)),
|
||||||
subtitle: Text(
|
subtitle: Text(
|
||||||
|
@ -3,8 +3,10 @@ import 'package:sendtrain/database/database.dart';
|
|||||||
import 'package:sendtrain/extensions/string_extensions.dart';
|
import 'package:sendtrain/extensions/string_extensions.dart';
|
||||||
import 'package:sendtrain/helpers/date_time_helpers.dart';
|
import 'package:sendtrain/helpers/date_time_helpers.dart';
|
||||||
import 'package:sendtrain/helpers/media_helpers.dart';
|
import 'package:sendtrain/helpers/media_helpers.dart';
|
||||||
|
import 'package:sendtrain/helpers/widget_helpers.dart';
|
||||||
import 'package:sendtrain/widgets/builders/dialogs.dart';
|
import 'package:sendtrain/widgets/builders/dialogs.dart';
|
||||||
import 'package:sendtrain/widgets/sessions/session_view.dart';
|
import 'package:sendtrain/widgets/sessions/session_view.dart';
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
|
|
||||||
class SessionCardSmall extends StatefulWidget {
|
class SessionCardSmall extends StatefulWidget {
|
||||||
const SessionCardSmall(
|
const SessionCardSmall(
|
||||||
@ -18,11 +20,20 @@ class SessionCardSmall extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _SessionCardSmallState extends State<SessionCardSmall> {
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final Session session = widget.session;
|
|
||||||
final List<MediaItem> mediaItems = widget.mediaItems;
|
|
||||||
|
|
||||||
return Card(
|
return Card(
|
||||||
color: (session.status == SessionStatus.started)
|
color: (session.status == SessionStatus.started)
|
||||||
? Theme.of(context).colorScheme.primaryContainer
|
? Theme.of(context).colorScheme.primaryContainer
|
||||||
@ -31,7 +42,9 @@ class _SessionCardSmallState extends State<SessionCardSmall> {
|
|||||||
// overlayColor: MaterialStateColor(Colors.deepPurple as int),
|
// overlayColor: MaterialStateColor(Colors.deepPurple as int),
|
||||||
splashColor: Colors.deepPurple,
|
splashColor: Colors.deepPurple,
|
||||||
borderRadius: const BorderRadius.all(Radius.elliptical(10, 10)),
|
borderRadius: const BorderRadius.all(Radius.elliptical(10, 10)),
|
||||||
onTap: () => showGenericDialog(SessionView(session: session), context),
|
onLongPress: () => showMediaDetailWidget(context, sessionImage!),
|
||||||
|
onTap: () =>
|
||||||
|
showGenericDialog(SessionView(session: session), context),
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
// color: const Color.fromARGB(47, 0, 0, 0),
|
// color: const Color.fromARGB(47, 0, 0, 0),
|
||||||
@ -39,7 +52,7 @@ class _SessionCardSmallState extends State<SessionCardSmall> {
|
|||||||
image: DecorationImage(
|
image: DecorationImage(
|
||||||
colorFilter: ColorFilter.mode(
|
colorFilter: ColorFilter.mode(
|
||||||
Color.fromARGB(220, 41, 39, 39), BlendMode.hardLight),
|
Color.fromARGB(220, 41, 39, 39), BlendMode.hardLight),
|
||||||
image: findMediaByType(mediaItems, 'image'),
|
image: findMediaByType(mediaItems, MediaType.location),
|
||||||
fit: BoxFit.cover),
|
fit: BoxFit.cover),
|
||||||
),
|
),
|
||||||
child: Align(
|
child: Align(
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:math';
|
|
||||||
|
|
||||||
import 'package:drift/drift.dart' hide Column;
|
import 'package:drift/drift.dart' hide Column;
|
||||||
import 'package:flutter/material.dart';
|
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/generic/elements/form_text_input.dart';
|
||||||
import 'package:sendtrain/widgets/sessions/session_view.dart';
|
import 'package:sendtrain/widgets/sessions/session_view.dart';
|
||||||
|
|
||||||
class SessionCreator extends StatefulWidget {
|
class SessionEditor extends StatefulWidget {
|
||||||
const SessionCreator({super.key, this.data, this.session});
|
const SessionEditor({super.key, this.data, this.session, this.callback});
|
||||||
|
|
||||||
final Session? session;
|
final Session? session;
|
||||||
final Map<String, dynamic>? data;
|
final Map<String, dynamic>? data;
|
||||||
|
final Function? callback;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<SessionCreator> createState() => _SessionCreatorState();
|
State<SessionEditor> createState() => _SessionEditorState();
|
||||||
}
|
}
|
||||||
|
|
||||||
// used to pass the result of the found image back to current context...
|
// used to pass the result of the found image back to current context...
|
||||||
@ -30,8 +30,10 @@ class SessionPayload {
|
|||||||
String? address;
|
String? address;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _SessionCreatorState extends State<SessionCreator> {
|
class _SessionEditorState extends State<SessionEditor> {
|
||||||
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
|
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
|
||||||
|
late AppDatabase db;
|
||||||
|
String editorType = 'Create';
|
||||||
|
|
||||||
final Map<String, TextEditingController> sessionCreateController = {
|
final Map<String, TextEditingController> sessionCreateController = {
|
||||||
'name': TextEditingController(),
|
'name': TextEditingController(),
|
||||||
@ -44,44 +46,77 @@ class _SessionCreatorState extends State<SessionCreator> {
|
|||||||
|
|
||||||
final SessionPayload sessionPayload = SessionPayload();
|
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 {
|
Future createSession(context) async {
|
||||||
Map<Symbol, Value> payload = {
|
Map<Symbol, Value> payload = {
|
||||||
Symbol('title'): Value<String>(sessionCreateController['name']!.text),
|
Symbol('title'): Value<String>(sessionCreateController['name']!.text),
|
||||||
Symbol('content'):
|
Symbol('content'):
|
||||||
Value<String>(sessionCreateController['content']!.text),
|
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>(
|
Symbol('date'): Value<DateTime>(
|
||||||
DateTime.parse(sessionCreateController['date']!.text)),
|
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
|
// optional params
|
||||||
if (sessionCreateController['address']!.text.isNotEmpty) {
|
if (sessionCreateController['address']!.text.isNotEmpty) {
|
||||||
payload[Symbol('address')] =
|
payload[Symbol('address')] =
|
||||||
Value<String>(sessionCreateController['address']!.text);
|
Value<String>(sessionCreateController['address']!.text);
|
||||||
}
|
}
|
||||||
|
|
||||||
return await SessionsDao(Provider.of<AppDatabase>(context, listen: false))
|
return await SessionsDao(db)
|
||||||
.createOrUpdate(Function.apply(SessionsCompanion.new, [], payload));
|
.createOrUpdate(Function.apply(SessionsCompanion.new, [], payload));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future createSessionMedia(context, sessionId) async {
|
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) {
|
if (sessionPayload.photoUri != null) {
|
||||||
MediaItemsCompanion mediaItem = MediaItemsCompanion(
|
MediaItemsCompanion mediaItem = MediaItemsCompanion(
|
||||||
title: Value('Location Image'),
|
title: Value('Location Image'),
|
||||||
description: Value(sessionPayload.address!),
|
description: Value(sessionPayload.address!),
|
||||||
reference: Value(sessionPayload.photoUri!),
|
reference: Value(sessionPayload.photoUri!),
|
||||||
type: Value(MediaType.image));
|
type: Value(MediaType.location));
|
||||||
|
|
||||||
return await MediaItemsDao(
|
return await MediaItemsDao(db).createOrUpdate(mediaItem).then((id) async {
|
||||||
Provider.of<AppDatabase>(context, listen: false))
|
|
||||||
.createOrUpdate(mediaItem).then((id) async {
|
|
||||||
ObjectMediaItemsCompanion omi = ObjectMediaItemsCompanion(
|
ObjectMediaItemsCompanion omi = ObjectMediaItemsCompanion(
|
||||||
objectId: Value(sessionId),
|
objectId: Value(sessionId),
|
||||||
objectType: Value(ObjectType.sessions),
|
objectType: Value(ObjectType.sessions),
|
||||||
mediaId: Value(id),
|
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 =
|
sessionCreateController['date']!.text =
|
||||||
DateFormat('yyyy-MM-dd').format(DateTime.now());
|
DateFormat('yyyy-MM-dd').format(DateTime.now());
|
||||||
|
|
||||||
AppDatabase db = Provider.of<AppDatabase>(context, listen: false);
|
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: EdgeInsets.fromLTRB(15, 0, 15, 15),
|
padding: EdgeInsets.fromLTRB(15, 0, 15, 15),
|
||||||
child: Form(
|
child: Form(
|
||||||
@ -103,7 +136,7 @@ class _SessionCreatorState extends State<SessionCreator> {
|
|||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.only(top: 10, bottom: 10),
|
padding: EdgeInsets.only(top: 10, bottom: 10),
|
||||||
child: Text('Create Session',
|
child: Text('$editorType Session',
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: Theme.of(context).textTheme.titleLarge)),
|
style: Theme.of(context).textTheme.titleLarge)),
|
||||||
FormTextInput(
|
FormTextInput(
|
||||||
@ -166,18 +199,42 @@ class _SessionCreatorState extends State<SessionCreator> {
|
|||||||
if (_formKey.currentState!.validate())
|
if (_formKey.currentState!.validate())
|
||||||
{
|
{
|
||||||
await createSession(_formKey.currentContext)
|
await createSession(_formKey.currentContext)
|
||||||
.then((id) async => {
|
.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(
|
await createSessionMedia(
|
||||||
_formKey.currentContext, id),
|
_formKey.currentContext,
|
||||||
SessionsDao(db).find(id).then(
|
currentSessionId);
|
||||||
(session) => showGenericDialog(
|
}
|
||||||
|
|
||||||
|
// 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(
|
SessionView(
|
||||||
session: session),
|
session: session),
|
||||||
_formKey
|
_formKey.currentContext!));
|
||||||
.currentContext!)),
|
}
|
||||||
|
|
||||||
Navigator.pop(
|
Navigator.pop(
|
||||||
_formKey.currentContext!,
|
_formKey.currentContext!, 'Submit');
|
||||||
'Submit')
|
|
||||||
|
if (widget.callback != null) {
|
||||||
|
await widget
|
||||||
|
.callback!(widget.session!.id);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
@ -5,9 +5,11 @@ import 'package:intl/date_symbol_data_local.dart';
|
|||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import 'package:sendtrain/daos/activities_dao.dart';
|
import 'package:sendtrain/daos/activities_dao.dart';
|
||||||
|
import 'package:sendtrain/daos/sessions_dao.dart';
|
||||||
import 'package:sendtrain/database/database.dart';
|
import 'package:sendtrain/database/database.dart';
|
||||||
import 'package:sendtrain/extensions/string_extensions.dart';
|
import 'package:sendtrain/extensions/string_extensions.dart';
|
||||||
import 'package:sendtrain/widgets/generic/elements/generic_progress_indicator.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_achievements.dart';
|
||||||
import 'package:sendtrain/widgets/sessions/session_view_activities.dart';
|
import 'package:sendtrain/widgets/sessions/session_view_activities.dart';
|
||||||
import 'package:sendtrain/widgets/sessions/session_view_media.dart';
|
import 'package:sendtrain/widgets/sessions/session_view_media.dart';
|
||||||
@ -22,13 +24,45 @@ class SessionView extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _SessionViewState extends State<SessionView> {
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final Session session = widget.session;
|
|
||||||
|
|
||||||
initializeDateFormatting('en');
|
|
||||||
final DateFormat dateFormat = DateFormat('yyyy-MM-dd');
|
|
||||||
|
|
||||||
return StreamBuilder<List<Activity>>(
|
return StreamBuilder<List<Activity>>(
|
||||||
stream: ActivitiesDao(Provider.of<AppDatabase>(context))
|
stream: ActivitiesDao(Provider.of<AppDatabase>(context))
|
||||||
.watchSessionActivities(session.id),
|
.watchSessionActivities(session.id),
|
||||||
@ -39,6 +73,7 @@ class _SessionViewState extends State<SessionView> {
|
|||||||
return Scaffold(
|
return Scaffold(
|
||||||
floatingActionButtonLocation: ExpandableFab.location,
|
floatingActionButtonLocation: ExpandableFab.location,
|
||||||
floatingActionButton: ExpandableFab(
|
floatingActionButton: ExpandableFab(
|
||||||
|
key: _fabKey,
|
||||||
distance: 70,
|
distance: 70,
|
||||||
type: ExpandableFabType.up,
|
type: ExpandableFabType.up,
|
||||||
overlayStyle: ExpandableFabOverlayStyle(
|
overlayStyle: ExpandableFabOverlayStyle(
|
||||||
@ -49,17 +84,55 @@ class _SessionViewState extends State<SessionView> {
|
|||||||
FloatingActionButton.extended(
|
FloatingActionButton.extended(
|
||||||
icon: const Icon(Icons.history_outlined),
|
icon: const Icon(Icons.history_outlined),
|
||||||
label: Text('Restart'),
|
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(
|
FloatingActionButton.extended(
|
||||||
icon: const Icon(Icons.done_all_outlined),
|
icon: const Icon(Icons.done_all_outlined),
|
||||||
label: Text('Done'),
|
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(
|
FloatingActionButton.extended(
|
||||||
icon: const Icon(Icons.edit_outlined),
|
icon: const Icon(Icons.edit_outlined),
|
||||||
label: Text('Edit'),
|
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(
|
body: Column(
|
||||||
@ -78,7 +151,7 @@ class _SessionViewState extends State<SessionView> {
|
|||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 25, fontWeight: FontWeight.bold),
|
fontSize: 25, fontWeight: FontWeight.bold),
|
||||||
session.title.toTitleCase())),
|
title())),
|
||||||
SessionViewAchievements(session: session),
|
SessionViewAchievements(session: session),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(left: 15, right: 15),
|
padding: const EdgeInsets.only(left: 15, right: 15),
|
||||||
|
@ -13,7 +13,7 @@ class SessionViewMedia extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return FutureBuilder<List<MediaItem>>(
|
return FutureBuilder<List<MediaItem>>(
|
||||||
future: MediaItemsDao(Provider.of<AppDatabase>(context))
|
future: MediaItemsDao(Provider.of<AppDatabase>(context))
|
||||||
.fromSession(session),
|
.fromSession(session.id),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.hasData) {
|
if (snapshot.hasData) {
|
||||||
final mediaItems = snapshot.data!;
|
final mediaItems = snapshot.data!;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user