Compare commits

...

2 Commits

24 changed files with 6754 additions and 114 deletions

View File

@ -1,6 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-feature android:name="android.hardware.location.network" android:required="false" /> <uses-feature android:name="android.hardware.location.network" android:required="false" />
<uses-feature android:name="android.hardware.location.gps" android:required="false" /> <uses-feature android:name="android.hardware.location.gps" android:required="false" />
<uses-permission android:name="android.permission.INTERNET"/>
<application <application
android:label="sendtrain" android:label="sendtrain"
android:name="${applicationName}" android:name="${applicationName}"

View File

@ -76,6 +76,7 @@ class MediaItemsDao extends DatabaseAccessor<AppDatabase>
}); });
} }
Future remove(MediaItem mediaItem) => delete(mediaItems).delete(mediaItem);
Future removeAll(Iterable<int> mediaItemIds) { Future removeAll(Iterable<int> mediaItemIds) {
return (delete(mediaItems)..where((table) => table.id.isIn(mediaItemIds))) return (delete(mediaItems)..where((table) => table.id.isIn(mediaItemIds)))
.go(); .go();

View File

@ -8,7 +8,7 @@ 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)..orderBy([(session) => OrderingTerm(expression: session.createdAt, mode: OrderingMode.desc)])).get(); Future<List<Session>> all() => select(sessions).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);

View File

@ -35,7 +35,7 @@ class AppDatabase extends _$AppDatabase {
AppDatabase() : super(_openConnection()); AppDatabase() : super(_openConnection());
@override @override
int get schemaVersion => 7; int get schemaVersion => 10;
@override @override
MigrationStrategy get migration { MigrationStrategy get migration {
@ -141,13 +141,13 @@ class ObjectMediaItems extends Table {
dateTime().withDefault(Variable(DateTime.now()))(); dateTime().withDefault(Variable(DateTime.now()))();
} }
enum MediaType { youtube, image, location } enum MediaType { youtube, image, location, localImage, localVideo }
class MediaItems extends Table { class MediaItems extends Table {
IntColumn get id => integer().autoIncrement()(); IntColumn get id => integer().autoIncrement()();
TextColumn get title => text().withLength(min: 3, max: 32)(); TextColumn get title => text().withLength(min: 3, max: 32)();
TextColumn get description => text().named('body')(); TextColumn get description => text().named('body')();
TextColumn get reference => text().withLength(min: 3, max: 256)(); TextColumn get reference => text()();
TextColumn get type => textEnum<MediaType>()(); TextColumn get type => textEnum<MediaType>()();
DateTimeColumn get createdAt => DateTimeColumn get createdAt =>
dateTime().withDefault(Variable(DateTime.now()))(); dateTime().withDefault(Variable(DateTime.now()))();

View File

@ -1764,10 +1764,7 @@ class $MediaItemsTable extends MediaItems
@override @override
late final GeneratedColumn<String> reference = GeneratedColumn<String>( late final GeneratedColumn<String> reference = GeneratedColumn<String>(
'reference', aliasedName, false, 'reference', aliasedName, false,
additionalChecks: type: DriftSqlType.string, requiredDuringInsert: true);
GeneratedColumn.checkTextLength(minTextLength: 3, maxTextLength: 256),
type: DriftSqlType.string,
requiredDuringInsert: true);
static const VerificationMeta _typeMeta = const VerificationMeta('type'); static const VerificationMeta _typeMeta = const VerificationMeta('type');
@override @override
late final GeneratedColumnWithTypeConverter<MediaType, String> type = late final GeneratedColumnWithTypeConverter<MediaType, String> type =

View File

@ -1088,6 +1088,406 @@ final class Schema7 extends i0.VersionedSchema {
alias: null); alias: null);
} }
final class Schema8 extends i0.VersionedSchema {
Schema8({required super.database}) : super(version: 8);
@override
late final List<i1.DatabaseSchemaEntity> entities = [
sessions,
activities,
sessionActivities,
actions,
activityActions,
mediaItems,
objectMediaItems,
];
late final Shape11 sessions = Shape11(
source: i0.VersionedTable(
entityName: 'sessions',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_0,
_column_1,
_column_2,
_column_3,
_column_20,
_column_4,
_column_5,
],
attachedDatabase: database,
),
alias: null);
late final Shape1 activities = Shape1(
source: i0.VersionedTable(
entityName: 'activities',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_0,
_column_1,
_column_6,
_column_2,
_column_7,
_column_5,
],
attachedDatabase: database,
),
alias: null);
late final Shape9 sessionActivities = Shape9(
source: i0.VersionedTable(
entityName: 'session_activities',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_0,
_column_21,
_column_22,
_column_19,
_column_10,
_column_11,
_column_5,
],
attachedDatabase: database,
),
alias: null);
late final Shape3 actions = Shape3(
source: i0.VersionedTable(
entityName: 'actions',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_0,
_column_1,
_column_2,
_column_12,
_column_5,
],
attachedDatabase: database,
),
alias: null);
late final Shape10 activityActions = Shape10(
source: i0.VersionedTable(
entityName: 'activity_actions',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_0,
_column_22,
_column_23,
_column_19,
_column_5,
],
attachedDatabase: database,
),
alias: null);
late final Shape5 mediaItems = Shape5(
source: i0.VersionedTable(
entityName: 'media_items',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_0,
_column_1,
_column_2,
_column_14,
_column_6,
_column_5,
],
attachedDatabase: database,
),
alias: null);
late final Shape6 objectMediaItems = Shape6(
source: i0.VersionedTable(
entityName: 'object_media_items',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_0,
_column_15,
_column_16,
_column_24,
_column_5,
],
attachedDatabase: database,
),
alias: null);
}
final class Schema9 extends i0.VersionedSchema {
Schema9({required super.database}) : super(version: 9);
@override
late final List<i1.DatabaseSchemaEntity> entities = [
sessions,
activities,
sessionActivities,
actions,
activityActions,
mediaItems,
objectMediaItems,
];
late final Shape11 sessions = Shape11(
source: i0.VersionedTable(
entityName: 'sessions',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_0,
_column_1,
_column_2,
_column_3,
_column_20,
_column_4,
_column_5,
],
attachedDatabase: database,
),
alias: null);
late final Shape1 activities = Shape1(
source: i0.VersionedTable(
entityName: 'activities',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_0,
_column_1,
_column_6,
_column_2,
_column_7,
_column_5,
],
attachedDatabase: database,
),
alias: null);
late final Shape9 sessionActivities = Shape9(
source: i0.VersionedTable(
entityName: 'session_activities',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_0,
_column_21,
_column_22,
_column_19,
_column_10,
_column_11,
_column_5,
],
attachedDatabase: database,
),
alias: null);
late final Shape3 actions = Shape3(
source: i0.VersionedTable(
entityName: 'actions',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_0,
_column_1,
_column_2,
_column_12,
_column_5,
],
attachedDatabase: database,
),
alias: null);
late final Shape10 activityActions = Shape10(
source: i0.VersionedTable(
entityName: 'activity_actions',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_0,
_column_22,
_column_23,
_column_19,
_column_5,
],
attachedDatabase: database,
),
alias: null);
late final Shape5 mediaItems = Shape5(
source: i0.VersionedTable(
entityName: 'media_items',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_0,
_column_1,
_column_2,
_column_25,
_column_6,
_column_5,
],
attachedDatabase: database,
),
alias: null);
late final Shape6 objectMediaItems = Shape6(
source: i0.VersionedTable(
entityName: 'object_media_items',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_0,
_column_15,
_column_16,
_column_24,
_column_5,
],
attachedDatabase: database,
),
alias: null);
}
i1.GeneratedColumn<String> _column_25(String aliasedName) =>
i1.GeneratedColumn<String>('reference', aliasedName, false,
type: i1.DriftSqlType.string);
final class Schema10 extends i0.VersionedSchema {
Schema10({required super.database}) : super(version: 10);
@override
late final List<i1.DatabaseSchemaEntity> entities = [
sessions,
activities,
sessionActivities,
actions,
activityActions,
mediaItems,
objectMediaItems,
];
late final Shape11 sessions = Shape11(
source: i0.VersionedTable(
entityName: 'sessions',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_0,
_column_1,
_column_2,
_column_3,
_column_20,
_column_4,
_column_5,
],
attachedDatabase: database,
),
alias: null);
late final Shape1 activities = Shape1(
source: i0.VersionedTable(
entityName: 'activities',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_0,
_column_1,
_column_6,
_column_2,
_column_7,
_column_5,
],
attachedDatabase: database,
),
alias: null);
late final Shape9 sessionActivities = Shape9(
source: i0.VersionedTable(
entityName: 'session_activities',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_0,
_column_21,
_column_22,
_column_19,
_column_10,
_column_11,
_column_5,
],
attachedDatabase: database,
),
alias: null);
late final Shape3 actions = Shape3(
source: i0.VersionedTable(
entityName: 'actions',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_0,
_column_1,
_column_2,
_column_12,
_column_5,
],
attachedDatabase: database,
),
alias: null);
late final Shape10 activityActions = Shape10(
source: i0.VersionedTable(
entityName: 'activity_actions',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_0,
_column_22,
_column_23,
_column_19,
_column_5,
],
attachedDatabase: database,
),
alias: null);
late final Shape5 mediaItems = Shape5(
source: i0.VersionedTable(
entityName: 'media_items',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_0,
_column_1,
_column_2,
_column_25,
_column_6,
_column_5,
],
attachedDatabase: database,
),
alias: null);
late final Shape6 objectMediaItems = Shape6(
source: i0.VersionedTable(
entityName: 'object_media_items',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_0,
_column_15,
_column_16,
_column_24,
_column_5,
],
attachedDatabase: database,
),
alias: null);
}
i0.MigrationStepWithVersion migrationSteps({ i0.MigrationStepWithVersion migrationSteps({
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2, required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3, required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
@ -1095,6 +1495,9 @@ i0.MigrationStepWithVersion migrationSteps({
required Future<void> Function(i1.Migrator m, Schema5 schema) from4To5, required Future<void> Function(i1.Migrator m, Schema5 schema) from4To5,
required Future<void> Function(i1.Migrator m, Schema6 schema) from5To6, required Future<void> Function(i1.Migrator m, Schema6 schema) from5To6,
required Future<void> Function(i1.Migrator m, Schema7 schema) from6To7, required Future<void> Function(i1.Migrator m, Schema7 schema) from6To7,
required Future<void> Function(i1.Migrator m, Schema8 schema) from7To8,
required Future<void> Function(i1.Migrator m, Schema9 schema) from8To9,
required Future<void> Function(i1.Migrator m, Schema10 schema) from9To10,
}) { }) {
return (currentVersion, database) async { return (currentVersion, database) async {
switch (currentVersion) { switch (currentVersion) {
@ -1128,6 +1531,21 @@ i0.MigrationStepWithVersion migrationSteps({
final migrator = i1.Migrator(database, schema); final migrator = i1.Migrator(database, schema);
await from6To7(migrator, schema); await from6To7(migrator, schema);
return 7; return 7;
case 7:
final schema = Schema8(database: database);
final migrator = i1.Migrator(database, schema);
await from7To8(migrator, schema);
return 8;
case 8:
final schema = Schema9(database: database);
final migrator = i1.Migrator(database, schema);
await from8To9(migrator, schema);
return 9;
case 9:
final schema = Schema10(database: database);
final migrator = i1.Migrator(database, schema);
await from9To10(migrator, schema);
return 10;
default: default:
throw ArgumentError.value('Unknown migration from $currentVersion'); throw ArgumentError.value('Unknown migration from $currentVersion');
} }
@ -1141,6 +1559,9 @@ i1.OnUpgrade stepByStep({
required Future<void> Function(i1.Migrator m, Schema5 schema) from4To5, required Future<void> Function(i1.Migrator m, Schema5 schema) from4To5,
required Future<void> Function(i1.Migrator m, Schema6 schema) from5To6, required Future<void> Function(i1.Migrator m, Schema6 schema) from5To6,
required Future<void> Function(i1.Migrator m, Schema7 schema) from6To7, required Future<void> Function(i1.Migrator m, Schema7 schema) from6To7,
required Future<void> Function(i1.Migrator m, Schema8 schema) from7To8,
required Future<void> Function(i1.Migrator m, Schema9 schema) from8To9,
required Future<void> Function(i1.Migrator m, Schema10 schema) from9To10,
}) => }) =>
i0.VersionedSchema.stepByStepHelper( i0.VersionedSchema.stepByStepHelper(
step: migrationSteps( step: migrationSteps(
@ -1150,4 +1571,7 @@ i1.OnUpgrade stepByStep({
from4To5: from4To5, from4To5: from4To5,
from5To6: from5To6, from5To6: from5To6,
from6To7: from6To7, from6To7: from6To7,
from7To8: from7To8,
from8To9: from8To9,
from9To10: from9To10,
)); ));

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -7,24 +7,29 @@ Future<void> seedDb(AppDatabase database) async {
// seed data setup // seed data setup
final List<List> sessionValues = [ final List<List> sessionValues = [
[ [
'Projecting @ Climbers Rock', 'Projecting',
'Beta pully beta beta pinch one arm crimpy. Futuristic pinch, dyno dynamic drop knee climb. Climbing ondra slopey onsight beta ondra power endurance.' 'Beta pully beta beta pinch one arm crimpy. Futuristic pinch, dyno dynamic drop knee climb. Climbing ondra slopey onsight beta ondra power endurance.'
'Climbers Rock Inc.'
], ],
[ [
'Moonboard @ Boardroom', 'Moonboard',
'Beta pully beta beta pinch one arm crimpy. Futuristic pinch, dyno dynamic drop knee climb. Climbing ondra slopey onsight beta ondra power endurance.' 'Beta pully beta beta pinch one arm crimpy. Futuristic pinch, dyno dynamic drop knee climb. Climbing ondra slopey onsight beta ondra power endurance.'
'Beta Bloc'
], ],
[ [
'Off-Wall Training', 'Off-Wall Training',
'Beta pully beta beta pinch one arm crimpy. Futuristic pinch, dyno dynamic drop knee climb. Climbing ondra slopey onsight beta ondra power endurance.' 'Beta pully beta beta pinch one arm crimpy. Futuristic pinch, dyno dynamic drop knee climb. Climbing ondra slopey onsight beta ondra power endurance.'
'Climbers Rcok Inc.'
], ],
[ [
'Climbing Outdoors', 'Climbing Outdoors',
'Beta pully beta beta pinch one arm crimpy. Futuristic pinch, dyno dynamic drop knee climb. Climbing ondra slopey onsight beta ondra power endurance.' 'Beta pully beta beta pinch one arm crimpy. Futuristic pinch, dyno dynamic drop knee climb. Climbing ondra slopey onsight beta ondra power endurance.'
'Gravity Hamilton'
], ],
[ [
'Volume Session @ Gravity', 'Volume Session',
'Beta pully beta beta pinch one arm crimpy. Futuristic pinch, dyno dynamic drop knee climb. Climbing ondra slopey onsight beta ondra power endurance.' 'Beta pully beta beta pinch one arm crimpy. Futuristic pinch, dyno dynamic drop knee climb. Climbing ondra slopey onsight beta ondra power endurance.'
'Up the Bloc'
], ],
]; ];
@ -61,6 +66,7 @@ Future<void> seedDb(AppDatabase database) async {
title: sessionValue[0], title: sessionValue[0],
content: sessionValue[1], content: sessionValue[1],
status: status, status: status,
address: sessionValue[2],
date: Value(DateTime.now()))) date: Value(DateTime.now())))
.then((sessionId) async { .then((sessionId) async {
// activities things // activities things
@ -162,6 +168,23 @@ Future<void> seedDb(AppDatabase database) async {
objectType: ObjectType.sessions)); objectType: ObjectType.sessions));
}); });
} }
await database
.into(database.mediaItems)
.insert(MediaItemsCompanion.insert(
title: 'Locations details',
description:
'5155 Harvester Rd #1, Burlington, ON L7L 6V2, Canada',
reference:
'https://lh3.googleusercontent.com/places/ANXAkqHwtb5oRMGG3haJkaHeTxdTI1lQ17RgvkCXwzA1dGV53BXPbHrdXIs1mLC_-4exyRW8dbYhMOeiOCHJqGeVBx-dNtABZAl9tQA=s4800-w800',
type: MediaType.location))
.then((mediaId) async {
await database.into(database.objectMediaItems).insert(
ObjectMediaItemsCompanion.insert(
objectId: sessionId,
mediaId: mediaId,
objectType: ObjectType.sessions));
});
}); });
} }
} }

View File

@ -89,7 +89,7 @@ class _AppState extends State<App> {
NavigationDestination( NavigationDestination(
icon: Icon(Icons.sports), label: "Sessions"), icon: Icon(Icons.sports), label: "Sessions"),
NavigationDestination( NavigationDestination(
icon: Icon(Icons.landscape), label: "Activities"), icon: Icon(Icons.sports_gymnastics_rounded), label: "Activities"),
NavigationDestination( NavigationDestination(
icon: Icon(Icons.calendar_month_rounded), label: "Plan"), icon: Icon(Icons.calendar_month_rounded), label: "Plan"),
NavigationDestination( NavigationDestination(

View File

@ -1,24 +1,73 @@
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:sendtrain/daos/media_items_dao.dart';
import 'package:sendtrain/database/database.dart'; import 'package:sendtrain/database/database.dart';
import 'package:sendtrain/helpers/widget_helpers.dart'; import 'package:sendtrain/helpers/widget_helpers.dart';
import 'package:sendtrain/widgets/builders/dialogs.dart';
import 'package:video_player/video_player.dart';
class MediaCard extends StatelessWidget { class MediaCard extends StatefulWidget {
const MediaCard({super.key, required this.media}); const MediaCard({super.key, required this.media, this.callback});
final MediaItem media; final MediaItem media;
final Function? callback;
@override
State<MediaCard> createState() => _MediaCardState();
}
class _MediaCardState extends State<MediaCard> {
// late VideoPlayerController _controller;
late MediaItem media;
late Function? callback;
@override
void initState() {
super.initState();
media = widget.media;
callback = widget.callback;
// _controller = VideoPlayerController.asset(dataSource)
// ..initialize().then((_) {
// setState(() {}); //when your thumbnail will show.
// });
}
// @override
// void dispose() {
// super.dispose();
// _controller.dispose();
// }
// Future<VideoPlayerController> createVideoPlayer() async {
// final File file =
// await ImgB64Decoder.fileFromB64String(widget.encodedBytes);
// final VideoPlayerController controller = VideoPlayerController.file(file);
// await controller.initialize();
// await controller.setLooping(true);
// return controller;
// }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
DecorationImage mediaImage(MediaItem media) { DecorationImage mediaImage(MediaItem media) {
String image = ''; dynamic image;
if (media.type == MediaType.image || media.type == MediaType.location) { if (media.type == MediaType.image || media.type == MediaType.location) {
image = media.reference; image = NetworkImage(media.reference);
} else if (media.type == MediaType.localImage) {
image = Image.memory(base64Decode(media.reference)).image;
} else if (media.type == MediaType.youtube) { } else if (media.type == MediaType.youtube) {
image = 'https://img.youtube.com/vi/${media.reference}/0.jpg'; image =
NetworkImage('https://img.youtube.com/vi/${media.reference}/0.jpg');
} else if (media.type == MediaType.localVideo) {
} }
return DecorationImage(image: NetworkImage(image), fit: BoxFit.cover); return DecorationImage(image: image, fit: BoxFit.cover);
} }
return Container( return Container(
@ -32,6 +81,18 @@ 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(
onLongPress: () => showRemovalDialog(
'Media Removal',
'Would you like to permanently remove this media from the current session?',
context,
MediaItemsDao(Provider.of<AppDatabase>(context,
listen: false)),
media)
.then((result) {
if (callback != null) {
callback!();
}
}),
onPressed: () => showMediaDetailWidget(context, media), onPressed: () => showMediaDetailWidget(context, media),
child: const ListTile( child: const ListTile(
title: Text(''), title: Text(''),

View File

@ -1,3 +1,5 @@
import 'dart:convert';
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:youtube_player_flutter/youtube_player_flutter.dart';
@ -16,6 +18,8 @@ class MediaContent extends StatelessWidget {
if (media.type == MediaType.image || media.type == MediaType.location) { if (media.type == MediaType.image || media.type == MediaType.location) {
return Image(image: NetworkImage(media.reference)); return Image(image: NetworkImage(media.reference));
} else if (media.type == MediaType.localImage) {
return Image.memory(base64Decode(media.reference));
} else if (media.type == MediaType.youtube) { } else if (media.type == MediaType.youtube) {
return YoutubePlayer( return YoutubePlayer(
controller: controller, controller: controller,

View File

@ -11,9 +11,8 @@ class MediaDetails extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Padding( return Padding(
padding: EdgeInsets.fromLTRB(15, 0, 15, 15), padding: EdgeInsets.fromLTRB(15, 0, 15, 15),
child: Column( child: ListView(
mainAxisSize: MainAxisSize.min, shrinkWrap: true,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[ children: <Widget>[
ClipRRect( ClipRRect(
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
@ -26,6 +25,7 @@ class MediaDetails extends StatelessWidget {
media.description, media.description,
style: const TextStyle(fontSize: 20), style: const TextStyle(fontSize: 20),
)), )),
const Divider( const Divider(
indent: 20, indent: 20,
endIndent: 20, endIndent: 20,

View File

@ -24,9 +24,9 @@ class SessionCardFull extends StatefulWidget {
} }
class _SessionCardFullState extends State<SessionCardFull> { class _SessionCardFullState extends State<SessionCardFull> {
late final List<MediaItem> mediaItems; // late final List<MediaItem> mediaItems;
late final MediaItem? sessionImage; // late final MediaItem? sessionImage;
late final Session session; // late final Session session;
String sessionTitle(Session session) { String sessionTitle(Session session) {
String title = session.title.toTitleCase(); String title = session.title.toTitleCase();
@ -36,17 +36,22 @@ class _SessionCardFullState extends State<SessionCardFull> {
return title; return title;
} }
@override // @override
initState() { // initState() {
super.initState(); // super.initState();
session = widget.session; // session = widget.session;
mediaItems = widget.mediaItems; // mediaItems = widget.mediaItems;
sessionImage = mediaItems // sessionImage = mediaItems
.firstWhereOrNull((mediaItem) => mediaItem.type == MediaType.location); // .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;
final MediaItem? sessionImage = mediaItems
.firstWhereOrNull((mediaItem) => mediaItem.type == MediaType.location);
return Card( return Card(
color: (session.status == SessionStatus.started) color: (session.status == SessionStatus.started)
? Theme.of(context).colorScheme.primaryContainer ? Theme.of(context).colorScheme.primaryContainer

View File

@ -20,20 +20,25 @@ class SessionCardSmall extends StatefulWidget {
} }
class _SessionCardSmallState extends State<SessionCardSmall> { class _SessionCardSmallState extends State<SessionCardSmall> {
late final List<MediaItem> mediaItems; // late final List<MediaItem> mediaItems;
late final MediaItem? sessionImage; // late final MediaItem? sessionImage;
late final Session session; // late final Session session;
@override // @override
initState() { // initState() {
super.initState(); // super.initState();
session = widget.session; // session = widget.session;
mediaItems = widget.mediaItems; // mediaItems = widget.mediaItems;
sessionImage = mediaItems.firstWhereOrNull((mediaItem) => mediaItem.type == MediaType.location); // 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;
final MediaItem? sessionImage = mediaItems
.firstWhereOrNull((mediaItem) => mediaItem.type == MediaType.location);
return Card( return Card(
color: (session.status == SessionStatus.started) color: (session.status == SessionStatus.started)
? Theme.of(context).colorScheme.primaryContainer ? Theme.of(context).colorScheme.primaryContainer
@ -42,7 +47,11 @@ 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)),
onLongPress: () => showMediaDetailWidget(context, sessionImage!), onLongPress: () {
if (sessionImage != null) {
showMediaDetailWidget(context, sessionImage);
}
},
onTap: () => onTap: () =>
showGenericDialog(SessionView(session: session), context), showGenericDialog(SessionView(session: session), context),
child: Container( child: Container(

View File

@ -1,8 +1,11 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert';
import 'package:drift/drift.dart' hide Column; import 'package:drift/drift.dart' hide Column;
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:mime/mime.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:sendtrain/daos/media_items_dao.dart'; import 'package:sendtrain/daos/media_items_dao.dart';
import 'package:sendtrain/daos/object_media_items_dao.dart'; import 'package:sendtrain/daos/object_media_items_dao.dart';
@ -28,6 +31,7 @@ class SessionEditor extends StatefulWidget {
class SessionPayload { class SessionPayload {
String? photoUri; String? photoUri;
String? address; String? address;
List<PlatformFile>? files;
} }
class _SessionEditorState extends State<SessionEditor> { class _SessionEditorState extends State<SessionEditor> {
@ -95,32 +99,43 @@ class _SessionEditorState extends State<SessionEditor> {
.createOrUpdate(Function.apply(SessionsCompanion.new, [], payload)); .createOrUpdate(Function.apply(SessionsCompanion.new, [], payload));
} }
Future createSessionMedia(context, sessionId) async { Future deleteSessionMedia(int sessionId, MediaType mediaType) async {
List<MediaItem> deletedMedia = List<MediaItem> deletedMedia =
await MediaItemsDao(db).fromSession(sessionId) (await MediaItemsDao(db).fromSession(sessionId))
..where((mediaItem) => mediaItem.type == MediaType.location); .where((mediaItem) => mediaItem.type == mediaType)
.toList();
await MediaItemsDao(db).removeAll(deletedMedia.map((m) => m.id)); for (int i = 0; i < deletedMedia.length; i++) {
await MediaItemsDao(db).remove(deletedMedia[i]);
if (sessionPayload.photoUri != null) {
MediaItemsCompanion mediaItem = MediaItemsCompanion(
title: Value('Location Image'),
description: Value(sessionPayload.address!),
reference: Value(sessionPayload.photoUri!),
type: Value(MediaType.location));
return await MediaItemsDao(db).createOrUpdate(mediaItem).then((id) async {
ObjectMediaItemsCompanion omi = ObjectMediaItemsCompanion(
objectId: Value(sessionId),
objectType: Value(ObjectType.sessions),
mediaId: Value(id),
);
await ObjectMediaItemsDao(db).createOrUpdate(omi);
});
} }
} }
Future createSessionMedia(
title,
sessionId,
description,
reference,
mediaType,
) async {
// if (sessionPayload.photoUri != null) {
MediaItemsCompanion mediaItem = MediaItemsCompanion(
title: Value(title),
description: Value(description),
reference: Value(reference.toString()),
type: Value(mediaType));
return await MediaItemsDao(db).createOrUpdate(mediaItem).then((id) async {
ObjectMediaItemsCompanion omi = ObjectMediaItemsCompanion(
objectId: Value(sessionId),
objectType: Value(ObjectType.sessions),
mediaId: Value(id),
);
await ObjectMediaItemsDao(db).createOrUpdate(omi);
});
// }
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
sessionCreateController['date']!.text = sessionCreateController['date']!.text =
@ -164,33 +179,35 @@ class _SessionEditorState extends State<SessionEditor> {
} }
}); });
}), }),
// Padding(
// padding: EdgeInsets.only(top: 10, bottom: 10),
// child: TextFormField(
// readOnly: true,
// decoration: InputDecoration(
// prefixIcon: Icon(Icons.image_rounded),
// filled: true,
// border: OutlineInputBorder(
// borderSide: BorderSide.none,
// borderRadius: BorderRadius.circular(12),
// ),
// labelText: 'Select Media (optional)',
// ),
// controller: sessionCreateController['media'],
// onTap: () async {
// FilePickerResult? result = await FilePicker.platform
// .pickFiles(allowMultiple: true);
// if (result != null) {
// List<File> files = result.paths
// .map((path) => File(path!))
// .toList();
// }
// })),
FormSearchInput( FormSearchInput(
sessionController: sessionCreateController['address']!, sessionController: sessionCreateController['address']!,
optionalPayload: sessionPayload), optionalPayload: sessionPayload),
Padding(
padding: EdgeInsets.only(top: 10, bottom: 10),
child: TextFormField(
readOnly: true,
decoration: InputDecoration(
prefixIcon: Icon(Icons.image_rounded),
filled: true,
border: OutlineInputBorder(
borderSide: BorderSide.none,
borderRadius: BorderRadius.circular(12),
),
labelText: 'Media (optional)',
),
controller: sessionCreateController['media'],
onTap: () async {
FilePickerResult? result = await FilePicker.platform
.pickFiles(
allowMultiple: true, type: FileType.image);
if (result != null) {
List<PlatformFile> files = result.files;
sessionCreateController['media']!.text =
files.map((file) => file.name).toString();
sessionPayload.files = files;
}
})),
Row(mainAxisAlignment: MainAxisAlignment.end, children: [ Row(mainAxisAlignment: MainAxisAlignment.end, children: [
Padding( Padding(
padding: EdgeInsets.only(top: 10), padding: EdgeInsets.only(top: 10),
@ -211,9 +228,40 @@ class _SessionEditorState extends State<SessionEditor> {
// if we've found a photo add it to media! // if we've found a photo add it to media!
if (sessionPayload.photoUri != null) { if (sessionPayload.photoUri != null) {
await deleteSessionMedia(
currentSessionId,
MediaType.location);
await createSessionMedia( await createSessionMedia(
_formKey.currentContext, 'Location Image',
currentSessionId); currentSessionId,
sessionPayload.address,
sessionPayload.photoUri,
MediaType.location);
}
// if we've selected files to save, save them
if (sessionPayload.files != null) {
for (int i = 0;
i < sessionPayload.files!.length;
i++) {
PlatformFile file =
sessionPayload.files![i];
String? type = lookupMimeType(file.path!)!.split('/').first;
Uint8List fileBytes =
await file.xFile.readAsBytes();
MediaType mediaType = MediaType.localImage;
if (type == "video") {
mediaType = MediaType.localVideo;
}
await createSessionMedia(
'Local Media',
currentSessionId,
file.name,
base64Encode(fileBytes),
mediaType);
}
} }
// if session is null it's new so we show the dialog // if session is null it's new so we show the dialog

View File

@ -34,28 +34,42 @@ class SessionViewAchievements extends StatelessWidget {
final sessionActivities = snapshot.data!; final sessionActivities = snapshot.data!;
final achievements = getAchievements(sessionActivities); final achievements = getAchievements(sessionActivities);
Widget content;
if (achievements.isEmpty) {
content = Padding(
padding: const EdgeInsets.only(left: 10, right: 5),
child: ActionChip(
visualDensity: VisualDensity.compact,
avatar: const Icon(Icons.check_circle_outline),
label: Text(maxLines: 1, 'Add Achievements!'),
onPressed: () {},
));
} else {
content = ListView.builder(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.fromLTRB(10, 0, 10, 0),
itemCount: achievements.length,
itemBuilder: (BuildContext context, int index) {
return Padding(
padding: const EdgeInsets.only(right: 5),
child: ActionChip(
visualDensity: VisualDensity.compact,
avatar: const Icon(Icons.check_circle_outline),
label: Text(
maxLines: 1, achievements[index].toTitleCase()),
onPressed: () {
// remove achievements
},
));
},
);
}
return Column( return Column(
children: [ children: [
Padding( Padding(
padding: const EdgeInsets.only(bottom: 10), padding: const EdgeInsets.only(bottom: 10),
child: SizedBox( child: SizedBox(height: 40, child: content)),
height: 40,
child: ListView.builder(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.fromLTRB(10, 0, 10, 0),
itemCount: achievements.length,
itemBuilder: (BuildContext context, int index) {
return Padding(
padding: const EdgeInsets.only(right: 5),
child: ActionChip(
visualDensity: VisualDensity.compact,
avatar:
const Icon(Icons.check_circle_outline),
label: Text(maxLines: 1, achievements[index].toTitleCase()),
onPressed: () {},
));
},
))),
], ],
); );
} else { } else {

View File

@ -4,22 +4,31 @@ import 'package:sendtrain/daos/media_items_dao.dart';
import 'package:sendtrain/database/database.dart'; import 'package:sendtrain/database/database.dart';
import 'package:sendtrain/widgets/media/media_card.dart'; import 'package:sendtrain/widgets/media/media_card.dart';
class SessionViewMedia extends StatelessWidget { class SessionViewMedia extends StatefulWidget {
const SessionViewMedia({super.key, required this.session}); const SessionViewMedia({super.key, required this.session});
final Session session; final Session session;
@override
State<SessionViewMedia> createState() => _SessionViewMediaState();
}
class _SessionViewMediaState extends State<SessionViewMedia> {
void resetState() {
setState(() {});
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return FutureBuilder<List<MediaItem>>( return StreamBuilder<List<MediaItem>>(
future: MediaItemsDao(Provider.of<AppDatabase>(context)) stream: MediaItemsDao(Provider.of<AppDatabase>(context))
.fromSession(session.id), .fromSession(widget.session.id).asStream(),
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.hasData) { if (snapshot.hasData) {
final mediaItems = snapshot.data!; final mediaItems = snapshot.data!;
List<Widget> mediaCards = List.generate( List<Widget> mediaCards = List.generate(
mediaItems.length, (i) => MediaCard(media: mediaItems[i])); mediaItems.length, (i) => MediaCard(media: mediaItems[i], callback: resetState));
return Column( return Column(
children: [ children: [

View File

@ -48,6 +48,8 @@ dependencies:
file_picker: ^8.1.7 file_picker: ^8.1.7
http: ^1.2.2 http: ^1.2.2
uuid: ^4.5.1 uuid: ^4.5.1
mime: ^2.0.0
video_player: ^2.9.2
flutter_launcher_name: flutter_launcher_name:
name: "SendTrain" name: "SendTrain"

View File

@ -10,6 +10,9 @@ import 'schema_v4.dart' as v4;
import 'schema_v5.dart' as v5; import 'schema_v5.dart' as v5;
import 'schema_v6.dart' as v6; import 'schema_v6.dart' as v6;
import 'schema_v7.dart' as v7; import 'schema_v7.dart' as v7;
import 'schema_v8.dart' as v8;
import 'schema_v9.dart' as v9;
import 'schema_v10.dart' as v10;
class GeneratedHelper implements SchemaInstantiationHelper { class GeneratedHelper implements SchemaInstantiationHelper {
@override @override
@ -29,10 +32,16 @@ class GeneratedHelper implements SchemaInstantiationHelper {
return v6.DatabaseAtV6(db); return v6.DatabaseAtV6(db);
case 7: case 7:
return v7.DatabaseAtV7(db); return v7.DatabaseAtV7(db);
case 8:
return v8.DatabaseAtV8(db);
case 9:
return v9.DatabaseAtV9(db);
case 10:
return v10.DatabaseAtV10(db);
default: default:
throw MissingSchemaException(version, versions); throw MissingSchemaException(version, versions);
} }
} }
static const versions = const [1, 2, 3, 4, 5, 6, 7]; static const versions = const [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff