modified db for local images, added file list saving functionality

This commit is contained in:
Joshua Burman 2025-01-01 23:42:46 -05:00
parent e36d2a837a
commit e78788d67a
17 changed files with 4478 additions and 87 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -1088,6 +1088,273 @@ final class Schema7 extends i0.VersionedSchema {
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);
i0.MigrationStepWithVersion migrationSteps({
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
@ -1095,6 +1362,8 @@ i0.MigrationStepWithVersion migrationSteps({
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, Schema7 schema) from6To7,
required Future<void> Function(i1.Migrator m, Schema8 schema) from7To8,
required Future<void> Function(i1.Migrator m, Schema9 schema) from8To9,
}) {
return (currentVersion, database) async {
switch (currentVersion) {
@ -1128,6 +1397,16 @@ i0.MigrationStepWithVersion migrationSteps({
final migrator = i1.Migrator(database, schema);
await from6To7(migrator, schema);
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;
default:
throw ArgumentError.value('Unknown migration from $currentVersion');
}
@ -1141,6 +1420,8 @@ i1.OnUpgrade stepByStep({
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, Schema7 schema) from6To7,
required Future<void> Function(i1.Migrator m, Schema8 schema) from7To8,
required Future<void> Function(i1.Migrator m, Schema9 schema) from8To9,
}) =>
i0.VersionedSchema.stepByStepHelper(
step: migrationSteps(
@ -1150,4 +1431,6 @@ i1.OnUpgrade stepByStep({
from4To5: from4To5,
from5To6: from5To6,
from6To7: from6To7,
from7To8: from7To8,
from8To9: from8To9,
));

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
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.'
'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 Bloc'
],
[
'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.'
'Climbers Rcok Inc.'
],
[
'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.'
'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.'
'Up the Bloc'
],
];
@ -162,6 +167,23 @@ Future<void> seedDb(AppDatabase database) async {
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/ANXAkqHz0IeMrJnqjBwQJvYVHv9qSp0huWPCBcdeMZds66wpLofxGAIk3KrYFD2ShEZzqm1A-GO7BfmO3OtRdjSlnO6DAHgyDv_C_7w=s4800-w800',
type: MediaType.location))
.then((mediaId) async {
await database.into(database.objectMediaItems).insert(
ObjectMediaItemsCompanion.insert(
objectId: sessionId,
mediaId: mediaId,
objectType: ObjectType.activities));
});
});
}
}

View File

@ -1,3 +1,6 @@
import 'dart:convert';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:sendtrain/database/database.dart';
import 'package:sendtrain/helpers/widget_helpers.dart';
@ -10,15 +13,17 @@ class MediaCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
DecorationImage mediaImage(MediaItem media) {
String image = '';
dynamic image;
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) {
image = 'https://img.youtube.com/vi/${media.reference}/0.jpg';
image = NetworkImage('https://img.youtube.com/vi/${media.reference}/0.jpg');
}
return DecorationImage(image: NetworkImage(image), fit: BoxFit.cover);
return DecorationImage(image: image, fit: BoxFit.cover);
}
return Container(

View File

@ -1,3 +1,5 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:sendtrain/database/database.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) {
return Image(image: NetworkImage(media.reference));
} else if (media.type == MediaType.localImage) {
return Image.memory(base64Decode(media.reference));
} else if (media.type == MediaType.youtube) {
return YoutubePlayer(
controller: controller,

View File

@ -11,9 +11,8 @@ class MediaDetails extends StatelessWidget {
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.fromLTRB(15, 0, 15, 15),
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
child: ListView(
shrinkWrap: true,
children: <Widget>[
ClipRRect(
borderRadius: BorderRadius.circular(10),

View File

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

View File

@ -20,20 +20,25 @@ class SessionCardSmall extends StatefulWidget {
}
class _SessionCardSmallState extends State<SessionCardSmall> {
late final List<MediaItem> mediaItems;
late final MediaItem? sessionImage;
late final Session session;
// 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
// 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;
final MediaItem? sessionImage = mediaItems
.firstWhereOrNull((mediaItem) => mediaItem.type == MediaType.location);
return Card(
color: (session.status == SessionStatus.started)
? Theme.of(context).colorScheme.primaryContainer

View File

@ -1,6 +1,9 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:drift/drift.dart' hide Column;
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
@ -28,6 +31,7 @@ class SessionEditor extends StatefulWidget {
class SessionPayload {
String? photoUri;
String? address;
List<PlatformFile>? files;
}
class _SessionEditorState extends State<SessionEditor> {
@ -95,32 +99,43 @@ class _SessionEditorState extends State<SessionEditor> {
.createOrUpdate(Function.apply(SessionsCompanion.new, [], payload));
}
Future createSessionMedia(context, sessionId) async {
Future deleteSessionMedia(int sessionId, MediaType mediaType) async {
List<MediaItem> deletedMedia =
await MediaItemsDao(db).fromSession(sessionId)
..where((mediaItem) => mediaItem.type == MediaType.location);
(await MediaItemsDao(db).fromSession(sessionId))
.where((mediaItem) => mediaItem.type == mediaType)
.toList();
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.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);
});
for (int i = 0; i < deletedMedia.length; i++) {
await MediaItemsDao(db).remove(deletedMedia[i]);
}
}
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
Widget build(BuildContext context) {
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(
sessionController: sessionCreateController['address']!,
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: 'Select Media (optional)',
),
controller: sessionCreateController['media'],
onTap: () async {
FilePickerResult? result = await FilePicker.platform
.pickFiles(
allowMultiple: true, type: FileType.media);
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: [
Padding(
padding: EdgeInsets.only(top: 10),
@ -211,9 +228,33 @@ class _SessionEditorState extends State<SessionEditor> {
// if we've found a photo add it to media!
if (sessionPayload.photoUri != null) {
await deleteSessionMedia(
currentSessionId,
MediaType.location);
await createSessionMedia(
_formKey.currentContext,
currentSessionId);
'Location Image',
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];
Uint8List fileBytes =
await file.xFile.readAsBytes();
await createSessionMedia(
'Local Media',
currentSessionId,
file.name,
base64Encode(fileBytes),
MediaType.localImage);
}
}
// if session is null it's new so we show the dialog

View File

@ -10,6 +10,8 @@ import 'schema_v4.dart' as v4;
import 'schema_v5.dart' as v5;
import 'schema_v6.dart' as v6;
import 'schema_v7.dart' as v7;
import 'schema_v8.dart' as v8;
import 'schema_v9.dart' as v9;
class GeneratedHelper implements SchemaInstantiationHelper {
@override
@ -29,10 +31,14 @@ class GeneratedHelper implements SchemaInstantiationHelper {
return v6.DatabaseAtV6(db);
case 7:
return v7.DatabaseAtV7(db);
case 8:
return v8.DatabaseAtV8(db);
case 9:
return v9.DatabaseAtV9(db);
default:
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];
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff