db changes, seed changes, media view work for local images, and video prep, initial achievement work

This commit is contained in:
Joshua Burman 2025-01-02 13:29:13 -05:00
parent e78788d67a
commit 48f716cdb0
15 changed files with 2286 additions and 37 deletions

View File

@ -1,6 +1,7 @@
<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.gps" android:required="false" />
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:label="sendtrain"
android:name="${applicationName}"

View File

@ -35,7 +35,7 @@ class AppDatabase extends _$AppDatabase {
AppDatabase() : super(_openConnection());
@override
int get schemaVersion => 9;
int get schemaVersion => 10;
@override
MigrationStrategy get migration {
@ -141,7 +141,7 @@ class ObjectMediaItems extends Table {
dateTime().withDefault(Variable(DateTime.now()))();
}
enum MediaType { youtube, image, location, localImage }
enum MediaType { youtube, image, location, localImage, localVideo }
class MediaItems extends Table {
IntColumn get id => integer().autoIncrement()();

View File

@ -1355,6 +1355,139 @@ final class Schema9 extends i0.VersionedSchema {
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({
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
@ -1364,6 +1497,7 @@ i0.MigrationStepWithVersion migrationSteps({
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 {
switch (currentVersion) {
@ -1407,6 +1541,11 @@ i0.MigrationStepWithVersion migrationSteps({
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:
throw ArgumentError.value('Unknown migration from $currentVersion');
}
@ -1422,6 +1561,7 @@ i1.OnUpgrade stepByStep({
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(
step: migrationSteps(
@ -1433,4 +1573,5 @@ i1.OnUpgrade stepByStep({
from6To7: from6To7,
from7To8: from7To8,
from8To9: from8To9,
from9To10: from9To10,
));

File diff suppressed because one or more lines are too long

View File

@ -66,6 +66,7 @@ Future<void> seedDb(AppDatabase database) async {
title: sessionValue[0],
content: sessionValue[1],
status: status,
address: sessionValue[2],
date: Value(DateTime.now())))
.then((sessionId) async {
// activities things
@ -175,14 +176,14 @@ Future<void> seedDb(AppDatabase database) async {
description:
'5155 Harvester Rd #1, Burlington, ON L7L 6V2, Canada',
reference:
'https://lh3.googleusercontent.com/places/ANXAkqHz0IeMrJnqjBwQJvYVHv9qSp0huWPCBcdeMZds66wpLofxGAIk3KrYFD2ShEZzqm1A-GO7BfmO3OtRdjSlnO6DAHgyDv_C_7w=s4800-w800',
'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.activities));
objectType: ObjectType.sessions));
});
});
}

View File

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

View File

@ -1,14 +1,55 @@
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
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/helpers/widget_helpers.dart';
import 'package:sendtrain/widgets/builders/dialogs.dart';
import 'package:video_player/video_player.dart';
class MediaCard extends StatelessWidget {
const MediaCard({super.key, required this.media});
class MediaCard extends StatefulWidget {
const MediaCard({super.key, required this.media, this.callback});
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
Widget build(BuildContext context) {
@ -20,7 +61,10 @@ class MediaCard extends StatelessWidget {
} else if (media.type == MediaType.localImage) {
image = Image.memory(base64Decode(media.reference)).image;
} else if (media.type == MediaType.youtube) {
image = NetworkImage('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: image, fit: BoxFit.cover);
@ -37,6 +81,18 @@ class MediaCard extends StatelessWidget {
RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
shadowColor: const Color.fromARGB(0, 255, 255, 255),
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),
child: const ListTile(
title: Text(''),

View File

@ -25,6 +25,7 @@ class MediaDetails extends StatelessWidget {
media.description,
style: const TextStyle(fontSize: 20),
)),
const Divider(
indent: 20,
endIndent: 20,

View File

@ -47,7 +47,11 @@ class _SessionCardSmallState extends State<SessionCardSmall> {
// overlayColor: MaterialStateColor(Colors.deepPurple as int),
splashColor: Colors.deepPurple,
borderRadius: const BorderRadius.all(Radius.elliptical(10, 10)),
onLongPress: () => showMediaDetailWidget(context, sessionImage!),
onLongPress: () {
if (sessionImage != null) {
showMediaDetailWidget(context, sessionImage);
}
},
onTap: () =>
showGenericDialog(SessionView(session: session), context),
child: Container(

View File

@ -1,11 +1,11 @@
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:mime/mime.dart';
import 'package:provider/provider.dart';
import 'package:sendtrain/daos/media_items_dao.dart';
import 'package:sendtrain/daos/object_media_items_dao.dart';
@ -193,13 +193,13 @@ class _SessionEditorState extends State<SessionEditor> {
borderSide: BorderSide.none,
borderRadius: BorderRadius.circular(12),
),
labelText: 'Select Media (optional)',
labelText: 'Media (optional)',
),
controller: sessionCreateController['media'],
onTap: () async {
FilePickerResult? result = await FilePicker.platform
.pickFiles(
allowMultiple: true, type: FileType.media);
allowMultiple: true, type: FileType.image);
if (result != null) {
List<PlatformFile> files = result.files;
@ -246,14 +246,21 @@ class _SessionEditorState extends State<SessionEditor> {
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.localImage);
mediaType);
}
}

View File

@ -34,13 +34,18 @@ class SessionViewAchievements extends StatelessWidget {
final sessionActivities = snapshot.data!;
final achievements = getAchievements(sessionActivities);
return Column(
children: [
Padding(
padding: const EdgeInsets.only(bottom: 10),
child: SizedBox(
height: 40,
child: ListView.builder(
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,
@ -49,13 +54,22 @@ class SessionViewAchievements extends StatelessWidget {
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: () {},
avatar: const Icon(Icons.check_circle_outline),
label: Text(
maxLines: 1, achievements[index].toTitleCase()),
onPressed: () {
// remove achievements
},
));
},
))),
);
}
return Column(
children: [
Padding(
padding: const EdgeInsets.only(bottom: 10),
child: SizedBox(height: 40, child: content)),
],
);
} else {

View File

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

View File

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

View File

@ -12,6 +12,7 @@ import 'schema_v6.dart' as v6;
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 {
@override
@ -35,10 +36,12 @@ class GeneratedHelper implements SchemaInstantiationHelper {
return v8.DatabaseAtV8(db);
case 9:
return v9.DatabaseAtV9(db);
case 10:
return v10.DatabaseAtV10(db);
default:
throw MissingSchemaException(version, versions);
}
}
static const versions = const [1, 2, 3, 4, 5, 6, 7, 8, 9];
static const versions = const [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
}

File diff suppressed because it is too large Load Diff