15 Commits

Author SHA1 Message Date
32826abcea achievement crud complete 2025-01-03 18:20:11 -05:00
2206720810 migration to move achievements to session, prep for achievement and media management 2025-01-02 19:50:29 -05:00
48f716cdb0 db changes, seed changes, media view work for local images, and video prep, initial achievement work 2025-01-02 13:29:13 -05:00
e78788d67a modified db for local images, added file list saving functionality 2025-01-01 23:42:46 -05:00
e36d2a837a migrate 7 2024-12-31 22:42:26 -05:00
10332ec8be media item and session images and location management, also refactoring and DRYing up code 2024-12-31 22:41:17 -05:00
5f628d6b48 location image search and population for new session 2024-12-30 01:54:51 -05:00
afe633e697 further refactoring and location search dev 2024-12-28 12:41:57 -05:00
8e0ec614a0 further refactoring, conversion to stream 2024-12-27 20:59:48 -05:00
fa374a5bc2 further import issues 2024-12-27 16:13:42 -05:00
26d9386812 import statement issues 2024-12-27 16:11:04 -05:00
722a152130 big refactor #1 2024-12-27 15:59:09 -05:00
cd8da31f4b basic session create and delete 2024-12-26 01:20:55 -05:00
029f037f90 session and activity delete 2024-12-24 22:38:25 -05:00
3c2f2e9bae Merge pull request 'DAO' (#4) from dao into main
Reviewed-on: #4
2024-12-24 14:00:49 -08:00
74 changed files with 21703 additions and 782 deletions

1
android/app/proguard-rules.pro vendored Normal file
View File

@ -0,0 +1 @@
-keep class androidx.lifecycle.DefaultLifecycleObserver

View File

@ -1,4 +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}"
@ -17,12 +20,12 @@
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
@ -31,6 +34,8 @@
android:name="flutterEmbedding"
android:value="2" />
</application>
<meta-data android:name="com.google.android.geo.API_KEY"
android:value="AIzaSyBCjMCEAyyNVpsnVYvZj6VL1mmB98Vd6AE" />
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
@ -38,8 +43,9 @@
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
<action android:name="android.intent.action.PROCESS_TEXT" />
<data android:mimeType="text/plain" />
</intent>
</queries>
</manifest>
</manifest>

View File

@ -1,5 +1,6 @@
import UIKit
import Flutter
import GoogleMaps
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
@ -7,7 +8,8 @@ import Flutter
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GMSServices.provideAPIKey("AIzaSyBCjMCEAyyNVpsnVYvZj6VL1mmB98Vd6AE")
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
}

View File

@ -2,6 +2,13 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>remote-notification</string>
</array>
<key>NSPhotoLibraryUsageDescription</key>
<string>The Photo library is used when selecting a photo to upload as media for a Session</string>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>CFBundleDevelopmentRegion</key>

View File

@ -4,7 +4,8 @@ import 'package:sendtrain/database/database.dart';
part 'activities_dao.g.dart';
@DriftAccessor(tables: [Activities])
class ActivitiesDao extends DatabaseAccessor<AppDatabase> with _$ActivitiesDaoMixin {
class ActivitiesDao extends DatabaseAccessor<AppDatabase>
with _$ActivitiesDaoMixin {
ActivitiesDao(super.db);
Future<List<Activity>> all() async {
@ -12,24 +13,71 @@ class ActivitiesDao extends DatabaseAccessor<AppDatabase> with _$ActivitiesDaoMi
}
Future<Activity> find(int id) async {
return await (select(activities)..where((activity) => activity.id.equals(id) )).getSingle();
return await (select(activities)
..where((activity) => activity.id.equals(id)))
.getSingle();
}
Future<List<Activity>> sessionActivities(int id) async {
final result = select(db.sessionActivities).join(
Future remove(Activity activity) => delete(activities).delete(activity);
Future<List<Activity>> activitiesFromSession(int id) async {
final result = select(db.sessionActivities).join(
[
innerJoin(
db.activities,
db.activities.id
.equalsExp(db.sessionActivities.activityId),
db.activities.id.equalsExp(db.sessionActivities.activityId),
),
],
)..where(db.sessionActivities.sessionId.equals(id));
final activities = (await result.get())
.map((e) => e.readTable(db.activities))
.toList();
final activities =
(await result.get()).map((e) => e.readTable(db.activities)).toList();
return activities;
}
}
Stream<List<Activity>> watchSessionActivities(int id) {
final query = select(db.sessionActivities).join(
[
innerJoin(
db.activities,
db.activities.id.equalsExp(db.sessionActivities.activityId),
),
],
)..where(db.sessionActivities.sessionId.equals(id));
return query.watch().map((rows){
final activities =
(rows).map((e) => e.readTable(db.activities)).toList();
return activities;
});
}
// MultiSelectable<SessionActivity> _selectableSessionActivities(int id) {
// // return select(db.sessionActivities)..limit(1, offset: 1);
// // final query = select(db.sessionActivities).join(
// // [
// // innerJoin(
// // db.activities,
// // db.activities.id.equalsExp(db.sessionActivities.activityId),
// // ),
// // ],
// // )..where(db.sessionActivities.sessionId.equals(id));
// // return query;
// final query = select(db.sessionActivities)..where((row) => row.sessionId.equals(id));
// query.join(
// [
// innerJoin(
// db.activities,
// db.activities.id.equalsExp(db.sessionActivities.activityId),
// ),
// ],
// );
// return query;
// }
}

View File

@ -4,15 +4,21 @@ import 'package:sendtrain/database/database.dart';
part 'media_items_dao.g.dart';
@DriftAccessor(tables: [MediaItems])
class MediaItemsDao extends DatabaseAccessor<AppDatabase> with _$MediaItemsDaoMixin {
class MediaItemsDao extends DatabaseAccessor<AppDatabase>
with _$MediaItemsDaoMixin {
MediaItemsDao(super.db);
Future createOrUpdate(MediaItemsCompanion mediaItem) =>
into(mediaItems).insertOnConflictUpdate(mediaItem);
Future<List<MediaItem>> all() async {
return await select(mediaItems).get();
}
Future<MediaItem> find(int id) async {
return await (select(mediaItems)..where((mediaItem) => mediaItem.id.equals(id) )).getSingle();
return await (select(mediaItems)
..where((mediaItem) => mediaItem.id.equals(id)))
.getSingle();
}
Future<List<MediaItem>> fromActivity(Activity activity) async {
@ -24,18 +30,16 @@ class MediaItemsDao extends DatabaseAccessor<AppDatabase> with _$MediaItemsDaoMi
),
],
)
..where(
db.objectMediaItems.objectType.equals(ObjectType.activities.name))
..where(db.objectMediaItems.objectType.equals(ObjectType.activities.name))
..where(db.objectMediaItems.objectId.equals(activity.id));
final mediaItems = (await result.get())
.map((e) => e.readTable(db.mediaItems))
.toList();
final mediaItems =
(await result.get()).map((e) => e.readTable(db.mediaItems)).toList();
return mediaItems;
}
Future<List<MediaItem>> fromSession(Session session) async {
Future<List<MediaItem>> fromSession(int sessionId) async {
final result = select(db.objectMediaItems).join(
[
innerJoin(
@ -44,14 +48,37 @@ class MediaItemsDao extends DatabaseAccessor<AppDatabase> with _$MediaItemsDaoMi
),
],
)
..where(
db.objectMediaItems.objectType.equals(ObjectType.sessions.name))
..where(db.objectMediaItems.objectId.equals(session.id));
..where(db.objectMediaItems.objectType.equals(ObjectType.sessions.name))
..where(db.objectMediaItems.objectId.equals(sessionId));
final mediaItems = (await result.get())
.map((e) => e.readTable(db.mediaItems))
.toList();
final mediaItems =
(await result.get()).map((e) => e.readTable(db.mediaItems)).toList();
return mediaItems;
}
Stream<List<MediaItem>> watchSessionMediaItems(int id) {
final query = select(db.objectMediaItems).join(
[
innerJoin(
db.mediaItems,
db.mediaItems.id.equalsExp(db.objectMediaItems.mediaId),
),
],
)
..where(db.objectMediaItems.objectType.equals(ObjectType.sessions.name))
..where(db.objectMediaItems.objectId.equals(id));
return query.watch().map((rows) {
final mediaItems = (rows).map((e) => e.readTable(db.mediaItems)).toList();
return mediaItems;
});
}
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

@ -0,0 +1,19 @@
import 'package:drift/drift.dart';
import 'package:sendtrain/database/database.dart';
part 'object_media_items_dao.g.dart';
@DriftAccessor(tables: [ObjectMediaItems])
class ObjectMediaItemsDao extends DatabaseAccessor<AppDatabase> with _$ObjectMediaItemsDaoMixin {
ObjectMediaItemsDao(super.db);
Future createOrUpdate(ObjectMediaItemsCompanion objectMediaItem) => into(objectMediaItems).insertOnConflictUpdate(objectMediaItem);
Future<List<ObjectMediaItem>> all() async {
return await select(objectMediaItems).get();
}
Future<ObjectMediaItem> find(int id) async {
return await (select(objectMediaItems)..where((objectMediaItem) => objectMediaItem.id.equals(id) )).getSingle();
}
}

View File

@ -0,0 +1,10 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'object_media_items_dao.dart';
// ignore_for_file: type=lint
mixin _$ObjectMediaItemsDaoMixin on DatabaseAccessor<AppDatabase> {
$MediaItemsTable get mediaItems => attachedDatabase.mediaItems;
$ObjectMediaItemsTable get objectMediaItems =>
attachedDatabase.objectMediaItems;
}

View File

@ -7,11 +7,11 @@ part 'sessions_dao.g.dart';
class SessionsDao extends DatabaseAccessor<AppDatabase> with _$SessionsDaoMixin {
SessionsDao(super.db);
Future<List<Session>> all() async {
return await select(sessions).get();
}
Future<Session> find(int id) async {
return await (select(sessions)..where((session) => session.id.equals(id) )).getSingle();
}
Future<Session> find(int id) => (select(sessions)..where((session) => session.id.equals(id) )).getSingle();
Stream<Session> watchSession(int id) => (select(sessions)..where((session) => session.id.equals(id) )).watchSingle();
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);
Future remove(Session session) => delete(sessions).delete(session);
}

View File

@ -4,6 +4,7 @@ import 'package:sendtrain/daos/actions_dao.dart';
import 'package:sendtrain/daos/activities_dao.dart';
import 'package:sendtrain/daos/activity_actions_dao.dart';
import 'package:sendtrain/daos/media_items_dao.dart';
import 'package:sendtrain/daos/object_media_items_dao.dart';
import 'package:sendtrain/daos/session_activities_dao.dart';
import 'package:sendtrain/daos/sessions_dao.dart';
import 'package:sendtrain/database/seed.dart';
@ -22,6 +23,7 @@ part 'database.g.dart';
SessionsDao,
ActivitiesDao,
MediaItemsDao,
ObjectMediaItemsDao,
SessionActivitiesDao,
ActivityActionsDao,
ActionsDao
@ -33,7 +35,7 @@ class AppDatabase extends _$AppDatabase {
AppDatabase() : super(_openConnection());
@override
int get schemaVersion => 4;
int get schemaVersion => 13;
@override
MigrationStrategy get migration {
@ -64,6 +66,8 @@ class Sessions extends Table {
TextColumn get title => text().withLength(min: 3, max: 32)();
TextColumn get content => text().named('body')();
TextColumn get status => textEnum<SessionStatus>()();
TextColumn get achievements => text().nullable()();
TextColumn get address => text().withLength(min: 3, max: 256).nullable()();
DateTimeColumn get date => dateTime().nullable()();
DateTimeColumn get createdAt =>
dateTime().withDefault(Variable(DateTime.now()))();
@ -71,11 +75,10 @@ class Sessions extends Table {
class SessionActivities extends Table {
IntColumn get id => integer().autoIncrement()();
IntColumn get sessionId => integer().references(Sessions, #id)();
IntColumn get activityId => integer().references(Activities, #id)();
IntColumn get sessionId => integer().references(Sessions, #id, onDelete: KeyAction.cascade)();
IntColumn get activityId => integer().references(Activities, #id, onDelete: KeyAction.cascade)();
IntColumn get position => integer()();
TextColumn get results => text().nullable()();
TextColumn get achievements => text().nullable()();
DateTimeColumn get createdAt =>
dateTime().withDefault(Variable(DateTime.now()))();
}
@ -107,8 +110,8 @@ class Activities extends Table {
class ActivityActions extends Table {
IntColumn get id => integer().autoIncrement()();
IntColumn get activityId => integer().references(Activities, #id)();
IntColumn get actionId => integer().references(Actions, #id)();
IntColumn get activityId => integer().references(Activities, #id, onDelete: KeyAction.cascade)();
IntColumn get actionId => integer().references(Actions, #id, onDelete: KeyAction.cascade)();
IntColumn get position => integer()();
DateTimeColumn get createdAt =>
dateTime().withDefault(Variable(DateTime.now()))();
@ -133,18 +136,18 @@ class ObjectMediaItems extends Table {
IntColumn get id => integer().autoIncrement()();
IntColumn get objectId => integer()();
TextColumn get objectType => textEnum<ObjectType>()();
IntColumn get mediaId => integer().references(MediaItems, #id)();
IntColumn get mediaId => integer().references(MediaItems, #id, onDelete: KeyAction.cascade)();
DateTimeColumn get createdAt =>
dateTime().withDefault(Variable(DateTime.now()))();
}
enum MediaType { youtube, image }
enum MediaType { youtube, image, location, localImage, localVideo }
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

@ -37,6 +37,21 @@ class $SessionsTable extends Sessions with TableInfo<$SessionsTable, Session> {
GeneratedColumn<String>('status', aliasedName, false,
type: DriftSqlType.string, requiredDuringInsert: true)
.withConverter<SessionStatus>($SessionsTable.$converterstatus);
static const VerificationMeta _achievementsMeta =
const VerificationMeta('achievements');
@override
late final GeneratedColumn<String> achievements = GeneratedColumn<String>(
'achievements', aliasedName, true,
type: DriftSqlType.string, requiredDuringInsert: false);
static const VerificationMeta _addressMeta =
const VerificationMeta('address');
@override
late final GeneratedColumn<String> address = GeneratedColumn<String>(
'address', aliasedName, true,
additionalChecks:
GeneratedColumn.checkTextLength(minTextLength: 3, maxTextLength: 256),
type: DriftSqlType.string,
requiredDuringInsert: false);
static const VerificationMeta _dateMeta = const VerificationMeta('date');
@override
late final GeneratedColumn<DateTime> date = GeneratedColumn<DateTime>(
@ -52,7 +67,7 @@ class $SessionsTable extends Sessions with TableInfo<$SessionsTable, Session> {
defaultValue: Variable(DateTime.now()));
@override
List<GeneratedColumn> get $columns =>
[id, title, content, status, date, createdAt];
[id, title, content, status, achievements, address, date, createdAt];
@override
String get aliasedName => _alias ?? actualTableName;
@override
@ -79,6 +94,16 @@ class $SessionsTable extends Sessions with TableInfo<$SessionsTable, Session> {
context.missing(_contentMeta);
}
context.handle(_statusMeta, const VerificationResult.success());
if (data.containsKey('achievements')) {
context.handle(
_achievementsMeta,
achievements.isAcceptableOrUnknown(
data['achievements']!, _achievementsMeta));
}
if (data.containsKey('address')) {
context.handle(_addressMeta,
address.isAcceptableOrUnknown(data['address']!, _addressMeta));
}
if (data.containsKey('date')) {
context.handle(
_dateMeta, date.isAcceptableOrUnknown(data['date']!, _dateMeta));
@ -105,6 +130,10 @@ class $SessionsTable extends Sessions with TableInfo<$SessionsTable, Session> {
status: $SessionsTable.$converterstatus.fromSql(attachedDatabase
.typeMapping
.read(DriftSqlType.string, data['${effectivePrefix}status'])!),
achievements: attachedDatabase.typeMapping
.read(DriftSqlType.string, data['${effectivePrefix}achievements']),
address: attachedDatabase.typeMapping
.read(DriftSqlType.string, data['${effectivePrefix}address']),
date: attachedDatabase.typeMapping
.read(DriftSqlType.dateTime, data['${effectivePrefix}date']),
createdAt: attachedDatabase.typeMapping
@ -126,6 +155,8 @@ class Session extends DataClass implements Insertable<Session> {
final String title;
final String content;
final SessionStatus status;
final String? achievements;
final String? address;
final DateTime? date;
final DateTime createdAt;
const Session(
@ -133,6 +164,8 @@ class Session extends DataClass implements Insertable<Session> {
required this.title,
required this.content,
required this.status,
this.achievements,
this.address,
this.date,
required this.createdAt});
@override
@ -145,6 +178,12 @@ class Session extends DataClass implements Insertable<Session> {
map['status'] =
Variable<String>($SessionsTable.$converterstatus.toSql(status));
}
if (!nullToAbsent || achievements != null) {
map['achievements'] = Variable<String>(achievements);
}
if (!nullToAbsent || address != null) {
map['address'] = Variable<String>(address);
}
if (!nullToAbsent || date != null) {
map['date'] = Variable<DateTime>(date);
}
@ -158,6 +197,12 @@ class Session extends DataClass implements Insertable<Session> {
title: Value(title),
content: Value(content),
status: Value(status),
achievements: achievements == null && nullToAbsent
? const Value.absent()
: Value(achievements),
address: address == null && nullToAbsent
? const Value.absent()
: Value(address),
date: date == null && nullToAbsent ? const Value.absent() : Value(date),
createdAt: Value(createdAt),
);
@ -172,6 +217,8 @@ class Session extends DataClass implements Insertable<Session> {
content: serializer.fromJson<String>(json['content']),
status: $SessionsTable.$converterstatus
.fromJson(serializer.fromJson<String>(json['status'])),
achievements: serializer.fromJson<String?>(json['achievements']),
address: serializer.fromJson<String?>(json['address']),
date: serializer.fromJson<DateTime?>(json['date']),
createdAt: serializer.fromJson<DateTime>(json['createdAt']),
);
@ -185,6 +232,8 @@ class Session extends DataClass implements Insertable<Session> {
'content': serializer.toJson<String>(content),
'status': serializer
.toJson<String>($SessionsTable.$converterstatus.toJson(status)),
'achievements': serializer.toJson<String?>(achievements),
'address': serializer.toJson<String?>(address),
'date': serializer.toJson<DateTime?>(date),
'createdAt': serializer.toJson<DateTime>(createdAt),
};
@ -195,6 +244,8 @@ class Session extends DataClass implements Insertable<Session> {
String? title,
String? content,
SessionStatus? status,
Value<String?> achievements = const Value.absent(),
Value<String?> address = const Value.absent(),
Value<DateTime?> date = const Value.absent(),
DateTime? createdAt}) =>
Session(
@ -202,6 +253,9 @@ class Session extends DataClass implements Insertable<Session> {
title: title ?? this.title,
content: content ?? this.content,
status: status ?? this.status,
achievements:
achievements.present ? achievements.value : this.achievements,
address: address.present ? address.value : this.address,
date: date.present ? date.value : this.date,
createdAt: createdAt ?? this.createdAt,
);
@ -211,6 +265,10 @@ class Session extends DataClass implements Insertable<Session> {
title: data.title.present ? data.title.value : this.title,
content: data.content.present ? data.content.value : this.content,
status: data.status.present ? data.status.value : this.status,
achievements: data.achievements.present
? data.achievements.value
: this.achievements,
address: data.address.present ? data.address.value : this.address,
date: data.date.present ? data.date.value : this.date,
createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt,
);
@ -223,6 +281,8 @@ class Session extends DataClass implements Insertable<Session> {
..write('title: $title, ')
..write('content: $content, ')
..write('status: $status, ')
..write('achievements: $achievements, ')
..write('address: $address, ')
..write('date: $date, ')
..write('createdAt: $createdAt')
..write(')'))
@ -230,7 +290,8 @@ class Session extends DataClass implements Insertable<Session> {
}
@override
int get hashCode => Object.hash(id, title, content, status, date, createdAt);
int get hashCode => Object.hash(
id, title, content, status, achievements, address, date, createdAt);
@override
bool operator ==(Object other) =>
identical(this, other) ||
@ -239,6 +300,8 @@ class Session extends DataClass implements Insertable<Session> {
other.title == this.title &&
other.content == this.content &&
other.status == this.status &&
other.achievements == this.achievements &&
other.address == this.address &&
other.date == this.date &&
other.createdAt == this.createdAt);
}
@ -248,6 +311,8 @@ class SessionsCompanion extends UpdateCompanion<Session> {
final Value<String> title;
final Value<String> content;
final Value<SessionStatus> status;
final Value<String?> achievements;
final Value<String?> address;
final Value<DateTime?> date;
final Value<DateTime> createdAt;
const SessionsCompanion({
@ -255,6 +320,8 @@ class SessionsCompanion extends UpdateCompanion<Session> {
this.title = const Value.absent(),
this.content = const Value.absent(),
this.status = const Value.absent(),
this.achievements = const Value.absent(),
this.address = const Value.absent(),
this.date = const Value.absent(),
this.createdAt = const Value.absent(),
});
@ -263,6 +330,8 @@ class SessionsCompanion extends UpdateCompanion<Session> {
required String title,
required String content,
required SessionStatus status,
this.achievements = const Value.absent(),
this.address = const Value.absent(),
this.date = const Value.absent(),
this.createdAt = const Value.absent(),
}) : title = Value(title),
@ -273,6 +342,8 @@ class SessionsCompanion extends UpdateCompanion<Session> {
Expression<String>? title,
Expression<String>? content,
Expression<String>? status,
Expression<String>? achievements,
Expression<String>? address,
Expression<DateTime>? date,
Expression<DateTime>? createdAt,
}) {
@ -281,6 +352,8 @@ class SessionsCompanion extends UpdateCompanion<Session> {
if (title != null) 'title': title,
if (content != null) 'body': content,
if (status != null) 'status': status,
if (achievements != null) 'achievements': achievements,
if (address != null) 'address': address,
if (date != null) 'date': date,
if (createdAt != null) 'created_at': createdAt,
});
@ -291,6 +364,8 @@ class SessionsCompanion extends UpdateCompanion<Session> {
Value<String>? title,
Value<String>? content,
Value<SessionStatus>? status,
Value<String?>? achievements,
Value<String?>? address,
Value<DateTime?>? date,
Value<DateTime>? createdAt}) {
return SessionsCompanion(
@ -298,6 +373,8 @@ class SessionsCompanion extends UpdateCompanion<Session> {
title: title ?? this.title,
content: content ?? this.content,
status: status ?? this.status,
achievements: achievements ?? this.achievements,
address: address ?? this.address,
date: date ?? this.date,
createdAt: createdAt ?? this.createdAt,
);
@ -319,6 +396,12 @@ class SessionsCompanion extends UpdateCompanion<Session> {
map['status'] =
Variable<String>($SessionsTable.$converterstatus.toSql(status.value));
}
if (achievements.present) {
map['achievements'] = Variable<String>(achievements.value);
}
if (address.present) {
map['address'] = Variable<String>(address.value);
}
if (date.present) {
map['date'] = Variable<DateTime>(date.value);
}
@ -335,6 +418,8 @@ class SessionsCompanion extends UpdateCompanion<Session> {
..write('title: $title, ')
..write('content: $content, ')
..write('status: $status, ')
..write('achievements: $achievements, ')
..write('address: $address, ')
..write('date: $date, ')
..write('createdAt: $createdAt')
..write(')'))
@ -714,8 +799,8 @@ class $SessionActivitiesTable extends SessionActivities
'session_id', aliasedName, false,
type: DriftSqlType.int,
requiredDuringInsert: true,
defaultConstraints:
GeneratedColumn.constraintIsAlways('REFERENCES sessions (id)'));
defaultConstraints: GeneratedColumn.constraintIsAlways(
'REFERENCES sessions (id) ON DELETE CASCADE'));
static const VerificationMeta _activityIdMeta =
const VerificationMeta('activityId');
@override
@ -723,8 +808,8 @@ class $SessionActivitiesTable extends SessionActivities
'activity_id', aliasedName, false,
type: DriftSqlType.int,
requiredDuringInsert: true,
defaultConstraints:
GeneratedColumn.constraintIsAlways('REFERENCES activities (id)'));
defaultConstraints: GeneratedColumn.constraintIsAlways(
'REFERENCES activities (id) ON DELETE CASCADE'));
static const VerificationMeta _positionMeta =
const VerificationMeta('position');
@override
@ -737,12 +822,6 @@ class $SessionActivitiesTable extends SessionActivities
late final GeneratedColumn<String> results = GeneratedColumn<String>(
'results', aliasedName, true,
type: DriftSqlType.string, requiredDuringInsert: false);
static const VerificationMeta _achievementsMeta =
const VerificationMeta('achievements');
@override
late final GeneratedColumn<String> achievements = GeneratedColumn<String>(
'achievements', aliasedName, true,
type: DriftSqlType.string, requiredDuringInsert: false);
static const VerificationMeta _createdAtMeta =
const VerificationMeta('createdAt');
@override
@ -753,7 +832,7 @@ class $SessionActivitiesTable extends SessionActivities
defaultValue: Variable(DateTime.now()));
@override
List<GeneratedColumn> get $columns =>
[id, sessionId, activityId, position, results, achievements, createdAt];
[id, sessionId, activityId, position, results, createdAt];
@override
String get aliasedName => _alias ?? actualTableName;
@override
@ -791,12 +870,6 @@ class $SessionActivitiesTable extends SessionActivities
context.handle(_resultsMeta,
results.isAcceptableOrUnknown(data['results']!, _resultsMeta));
}
if (data.containsKey('achievements')) {
context.handle(
_achievementsMeta,
achievements.isAcceptableOrUnknown(
data['achievements']!, _achievementsMeta));
}
if (data.containsKey('created_at')) {
context.handle(_createdAtMeta,
createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta));
@ -820,8 +893,6 @@ class $SessionActivitiesTable extends SessionActivities
.read(DriftSqlType.int, data['${effectivePrefix}position'])!,
results: attachedDatabase.typeMapping
.read(DriftSqlType.string, data['${effectivePrefix}results']),
achievements: attachedDatabase.typeMapping
.read(DriftSqlType.string, data['${effectivePrefix}achievements']),
createdAt: attachedDatabase.typeMapping
.read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!,
);
@ -839,7 +910,6 @@ class SessionActivity extends DataClass implements Insertable<SessionActivity> {
final int activityId;
final int position;
final String? results;
final String? achievements;
final DateTime createdAt;
const SessionActivity(
{required this.id,
@ -847,7 +917,6 @@ class SessionActivity extends DataClass implements Insertable<SessionActivity> {
required this.activityId,
required this.position,
this.results,
this.achievements,
required this.createdAt});
@override
Map<String, Expression> toColumns(bool nullToAbsent) {
@ -859,9 +928,6 @@ class SessionActivity extends DataClass implements Insertable<SessionActivity> {
if (!nullToAbsent || results != null) {
map['results'] = Variable<String>(results);
}
if (!nullToAbsent || achievements != null) {
map['achievements'] = Variable<String>(achievements);
}
map['created_at'] = Variable<DateTime>(createdAt);
return map;
}
@ -875,9 +941,6 @@ class SessionActivity extends DataClass implements Insertable<SessionActivity> {
results: results == null && nullToAbsent
? const Value.absent()
: Value(results),
achievements: achievements == null && nullToAbsent
? const Value.absent()
: Value(achievements),
createdAt: Value(createdAt),
);
}
@ -891,7 +954,6 @@ class SessionActivity extends DataClass implements Insertable<SessionActivity> {
activityId: serializer.fromJson<int>(json['activityId']),
position: serializer.fromJson<int>(json['position']),
results: serializer.fromJson<String?>(json['results']),
achievements: serializer.fromJson<String?>(json['achievements']),
createdAt: serializer.fromJson<DateTime>(json['createdAt']),
);
}
@ -904,7 +966,6 @@ class SessionActivity extends DataClass implements Insertable<SessionActivity> {
'activityId': serializer.toJson<int>(activityId),
'position': serializer.toJson<int>(position),
'results': serializer.toJson<String?>(results),
'achievements': serializer.toJson<String?>(achievements),
'createdAt': serializer.toJson<DateTime>(createdAt),
};
}
@ -915,7 +976,6 @@ class SessionActivity extends DataClass implements Insertable<SessionActivity> {
int? activityId,
int? position,
Value<String?> results = const Value.absent(),
Value<String?> achievements = const Value.absent(),
DateTime? createdAt}) =>
SessionActivity(
id: id ?? this.id,
@ -923,8 +983,6 @@ class SessionActivity extends DataClass implements Insertable<SessionActivity> {
activityId: activityId ?? this.activityId,
position: position ?? this.position,
results: results.present ? results.value : this.results,
achievements:
achievements.present ? achievements.value : this.achievements,
createdAt: createdAt ?? this.createdAt,
);
SessionActivity copyWithCompanion(SessionActivitiesCompanion data) {
@ -935,9 +993,6 @@ class SessionActivity extends DataClass implements Insertable<SessionActivity> {
data.activityId.present ? data.activityId.value : this.activityId,
position: data.position.present ? data.position.value : this.position,
results: data.results.present ? data.results.value : this.results,
achievements: data.achievements.present
? data.achievements.value
: this.achievements,
createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt,
);
}
@ -950,15 +1005,14 @@ class SessionActivity extends DataClass implements Insertable<SessionActivity> {
..write('activityId: $activityId, ')
..write('position: $position, ')
..write('results: $results, ')
..write('achievements: $achievements, ')
..write('createdAt: $createdAt')
..write(')'))
.toString();
}
@override
int get hashCode => Object.hash(
id, sessionId, activityId, position, results, achievements, createdAt);
int get hashCode =>
Object.hash(id, sessionId, activityId, position, results, createdAt);
@override
bool operator ==(Object other) =>
identical(this, other) ||
@ -968,7 +1022,6 @@ class SessionActivity extends DataClass implements Insertable<SessionActivity> {
other.activityId == this.activityId &&
other.position == this.position &&
other.results == this.results &&
other.achievements == this.achievements &&
other.createdAt == this.createdAt);
}
@ -978,7 +1031,6 @@ class SessionActivitiesCompanion extends UpdateCompanion<SessionActivity> {
final Value<int> activityId;
final Value<int> position;
final Value<String?> results;
final Value<String?> achievements;
final Value<DateTime> createdAt;
const SessionActivitiesCompanion({
this.id = const Value.absent(),
@ -986,7 +1038,6 @@ class SessionActivitiesCompanion extends UpdateCompanion<SessionActivity> {
this.activityId = const Value.absent(),
this.position = const Value.absent(),
this.results = const Value.absent(),
this.achievements = const Value.absent(),
this.createdAt = const Value.absent(),
});
SessionActivitiesCompanion.insert({
@ -995,7 +1046,6 @@ class SessionActivitiesCompanion extends UpdateCompanion<SessionActivity> {
required int activityId,
required int position,
this.results = const Value.absent(),
this.achievements = const Value.absent(),
this.createdAt = const Value.absent(),
}) : sessionId = Value(sessionId),
activityId = Value(activityId),
@ -1006,7 +1056,6 @@ class SessionActivitiesCompanion extends UpdateCompanion<SessionActivity> {
Expression<int>? activityId,
Expression<int>? position,
Expression<String>? results,
Expression<String>? achievements,
Expression<DateTime>? createdAt,
}) {
return RawValuesInsertable({
@ -1015,7 +1064,6 @@ class SessionActivitiesCompanion extends UpdateCompanion<SessionActivity> {
if (activityId != null) 'activity_id': activityId,
if (position != null) 'position': position,
if (results != null) 'results': results,
if (achievements != null) 'achievements': achievements,
if (createdAt != null) 'created_at': createdAt,
});
}
@ -1026,7 +1074,6 @@ class SessionActivitiesCompanion extends UpdateCompanion<SessionActivity> {
Value<int>? activityId,
Value<int>? position,
Value<String?>? results,
Value<String?>? achievements,
Value<DateTime>? createdAt}) {
return SessionActivitiesCompanion(
id: id ?? this.id,
@ -1034,7 +1081,6 @@ class SessionActivitiesCompanion extends UpdateCompanion<SessionActivity> {
activityId: activityId ?? this.activityId,
position: position ?? this.position,
results: results ?? this.results,
achievements: achievements ?? this.achievements,
createdAt: createdAt ?? this.createdAt,
);
}
@ -1057,9 +1103,6 @@ class SessionActivitiesCompanion extends UpdateCompanion<SessionActivity> {
if (results.present) {
map['results'] = Variable<String>(results.value);
}
if (achievements.present) {
map['achievements'] = Variable<String>(achievements.value);
}
if (createdAt.present) {
map['created_at'] = Variable<DateTime>(createdAt.value);
}
@ -1074,7 +1117,6 @@ class SessionActivitiesCompanion extends UpdateCompanion<SessionActivity> {
..write('activityId: $activityId, ')
..write('position: $position, ')
..write('results: $results, ')
..write('achievements: $achievements, ')
..write('createdAt: $createdAt')
..write(')'))
.toString();
@ -1402,8 +1444,8 @@ class $ActivityActionsTable extends ActivityActions
'activity_id', aliasedName, false,
type: DriftSqlType.int,
requiredDuringInsert: true,
defaultConstraints:
GeneratedColumn.constraintIsAlways('REFERENCES activities (id)'));
defaultConstraints: GeneratedColumn.constraintIsAlways(
'REFERENCES activities (id) ON DELETE CASCADE'));
static const VerificationMeta _actionIdMeta =
const VerificationMeta('actionId');
@override
@ -1411,8 +1453,8 @@ class $ActivityActionsTable extends ActivityActions
'action_id', aliasedName, false,
type: DriftSqlType.int,
requiredDuringInsert: true,
defaultConstraints:
GeneratedColumn.constraintIsAlways('REFERENCES actions (id)'));
defaultConstraints: GeneratedColumn.constraintIsAlways(
'REFERENCES actions (id) ON DELETE CASCADE'));
static const VerificationMeta _positionMeta =
const VerificationMeta('position');
@override
@ -1722,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 =
@ -2070,8 +2109,8 @@ class $ObjectMediaItemsTable extends ObjectMediaItems
'media_id', aliasedName, false,
type: DriftSqlType.int,
requiredDuringInsert: true,
defaultConstraints:
GeneratedColumn.constraintIsAlways('REFERENCES media_items (id)'));
defaultConstraints: GeneratedColumn.constraintIsAlways(
'REFERENCES media_items (id) ON DELETE CASCADE'));
static const VerificationMeta _createdAtMeta =
const VerificationMeta('createdAt');
@override
@ -2359,6 +2398,8 @@ abstract class _$AppDatabase extends GeneratedDatabase {
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 =
@ -2377,6 +2418,46 @@ abstract class _$AppDatabase extends GeneratedDatabase {
mediaItems,
objectMediaItems
];
@override
StreamQueryUpdateRules get streamUpdateRules => const StreamQueryUpdateRules(
[
WritePropagation(
on: TableUpdateQuery.onTableName('sessions',
limitUpdateKind: UpdateKind.delete),
result: [
TableUpdate('session_activities', kind: UpdateKind.delete),
],
),
WritePropagation(
on: TableUpdateQuery.onTableName('activities',
limitUpdateKind: UpdateKind.delete),
result: [
TableUpdate('session_activities', kind: UpdateKind.delete),
],
),
WritePropagation(
on: TableUpdateQuery.onTableName('activities',
limitUpdateKind: UpdateKind.delete),
result: [
TableUpdate('activity_actions', kind: UpdateKind.delete),
],
),
WritePropagation(
on: TableUpdateQuery.onTableName('actions',
limitUpdateKind: UpdateKind.delete),
result: [
TableUpdate('activity_actions', kind: UpdateKind.delete),
],
),
WritePropagation(
on: TableUpdateQuery.onTableName('media_items',
limitUpdateKind: UpdateKind.delete),
result: [
TableUpdate('object_media_items', kind: UpdateKind.delete),
],
),
],
);
}
typedef $$SessionsTableCreateCompanionBuilder = SessionsCompanion Function({
@ -2384,6 +2465,8 @@ typedef $$SessionsTableCreateCompanionBuilder = SessionsCompanion Function({
required String title,
required String content,
required SessionStatus status,
Value<String?> achievements,
Value<String?> address,
Value<DateTime?> date,
Value<DateTime> createdAt,
});
@ -2392,6 +2475,8 @@ typedef $$SessionsTableUpdateCompanionBuilder = SessionsCompanion Function({
Value<String> title,
Value<String> content,
Value<SessionStatus> status,
Value<String?> achievements,
Value<String?> address,
Value<DateTime?> date,
Value<DateTime> createdAt,
});
@ -2441,6 +2526,12 @@ class $$SessionsTableFilterComposer
column: $table.status,
builder: (column) => ColumnWithTypeConverterFilters(column));
ColumnFilters<String> get achievements => $composableBuilder(
column: $table.achievements, builder: (column) => ColumnFilters(column));
ColumnFilters<String> get address => $composableBuilder(
column: $table.address, builder: (column) => ColumnFilters(column));
ColumnFilters<DateTime> get date => $composableBuilder(
column: $table.date, builder: (column) => ColumnFilters(column));
@ -2490,6 +2581,13 @@ class $$SessionsTableOrderingComposer
ColumnOrderings<String> get status => $composableBuilder(
column: $table.status, builder: (column) => ColumnOrderings(column));
ColumnOrderings<String> get achievements => $composableBuilder(
column: $table.achievements,
builder: (column) => ColumnOrderings(column));
ColumnOrderings<String> get address => $composableBuilder(
column: $table.address, builder: (column) => ColumnOrderings(column));
ColumnOrderings<DateTime> get date => $composableBuilder(
column: $table.date, builder: (column) => ColumnOrderings(column));
@ -2518,6 +2616,12 @@ class $$SessionsTableAnnotationComposer
GeneratedColumnWithTypeConverter<SessionStatus, String> get status =>
$composableBuilder(column: $table.status, builder: (column) => column);
GeneratedColumn<String> get achievements => $composableBuilder(
column: $table.achievements, builder: (column) => column);
GeneratedColumn<String> get address =>
$composableBuilder(column: $table.address, builder: (column) => column);
GeneratedColumn<DateTime> get date =>
$composableBuilder(column: $table.date, builder: (column) => column);
@ -2574,6 +2678,8 @@ class $$SessionsTableTableManager extends RootTableManager<
Value<String> title = const Value.absent(),
Value<String> content = const Value.absent(),
Value<SessionStatus> status = const Value.absent(),
Value<String?> achievements = const Value.absent(),
Value<String?> address = const Value.absent(),
Value<DateTime?> date = const Value.absent(),
Value<DateTime> createdAt = const Value.absent(),
}) =>
@ -2582,6 +2688,8 @@ class $$SessionsTableTableManager extends RootTableManager<
title: title,
content: content,
status: status,
achievements: achievements,
address: address,
date: date,
createdAt: createdAt,
),
@ -2590,6 +2698,8 @@ class $$SessionsTableTableManager extends RootTableManager<
required String title,
required String content,
required SessionStatus status,
Value<String?> achievements = const Value.absent(),
Value<String?> address = const Value.absent(),
Value<DateTime?> date = const Value.absent(),
Value<DateTime> createdAt = const Value.absent(),
}) =>
@ -2598,6 +2708,8 @@ class $$SessionsTableTableManager extends RootTableManager<
title: title,
content: content,
status: status,
achievements: achievements,
address: address,
date: date,
createdAt: createdAt,
),
@ -2997,7 +3109,6 @@ typedef $$SessionActivitiesTableCreateCompanionBuilder
required int activityId,
required int position,
Value<String?> results,
Value<String?> achievements,
Value<DateTime> createdAt,
});
typedef $$SessionActivitiesTableUpdateCompanionBuilder
@ -3007,7 +3118,6 @@ typedef $$SessionActivitiesTableUpdateCompanionBuilder
Value<int> activityId,
Value<int> position,
Value<String?> results,
Value<String?> achievements,
Value<DateTime> createdAt,
});
@ -3061,9 +3171,6 @@ class $$SessionActivitiesTableFilterComposer
ColumnFilters<String> get results => $composableBuilder(
column: $table.results, builder: (column) => ColumnFilters(column));
ColumnFilters<String> get achievements => $composableBuilder(
column: $table.achievements, builder: (column) => ColumnFilters(column));
ColumnFilters<DateTime> get createdAt => $composableBuilder(
column: $table.createdAt, builder: (column) => ColumnFilters(column));
@ -3126,10 +3233,6 @@ class $$SessionActivitiesTableOrderingComposer
ColumnOrderings<String> get results => $composableBuilder(
column: $table.results, builder: (column) => ColumnOrderings(column));
ColumnOrderings<String> get achievements => $composableBuilder(
column: $table.achievements,
builder: (column) => ColumnOrderings(column));
ColumnOrderings<DateTime> get createdAt => $composableBuilder(
column: $table.createdAt, builder: (column) => ColumnOrderings(column));
@ -3192,9 +3295,6 @@ class $$SessionActivitiesTableAnnotationComposer
GeneratedColumn<String> get results =>
$composableBuilder(column: $table.results, builder: (column) => column);
GeneratedColumn<String> get achievements => $composableBuilder(
column: $table.achievements, builder: (column) => column);
GeneratedColumn<DateTime> get createdAt =>
$composableBuilder(column: $table.createdAt, builder: (column) => column);
@ -3269,7 +3369,6 @@ class $$SessionActivitiesTableTableManager extends RootTableManager<
Value<int> activityId = const Value.absent(),
Value<int> position = const Value.absent(),
Value<String?> results = const Value.absent(),
Value<String?> achievements = const Value.absent(),
Value<DateTime> createdAt = const Value.absent(),
}) =>
SessionActivitiesCompanion(
@ -3278,7 +3377,6 @@ class $$SessionActivitiesTableTableManager extends RootTableManager<
activityId: activityId,
position: position,
results: results,
achievements: achievements,
createdAt: createdAt,
),
createCompanionCallback: ({
@ -3287,7 +3385,6 @@ class $$SessionActivitiesTableTableManager extends RootTableManager<
required int activityId,
required int position,
Value<String?> results = const Value.absent(),
Value<String?> achievements = const Value.absent(),
Value<DateTime> createdAt = const Value.absent(),
}) =>
SessionActivitiesCompanion.insert(
@ -3296,7 +3393,6 @@ class $$SessionActivitiesTableTableManager extends RootTableManager<
activityId: activityId,
position: position,
results: results,
achievements: achievements,
createdAt: createdAt,
),
withReferenceMapper: (p0) => p0

File diff suppressed because it is too large Load Diff

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

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

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
final List<List> sessionValues = [
[
'Projecting @ Climbers Rock',
'Beta pully beta beta pinch one arm crimpy. Futuristic pinch, dyno dynamic drop knee climb. Climbing ondra slopey onsight beta ondra power endurance.'
'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',
'Beta pully beta beta pinch one arm crimpy. Futuristic pinch, dyno dynamic drop knee climb. Climbing ondra slopey onsight beta ondra power endurance.'
'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.'
'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.'
'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',
'Beta pully beta beta pinch one arm crimpy. Futuristic pinch, dyno dynamic drop knee climb. Climbing ondra slopey onsight beta ondra power endurance.'
'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'
],
];
@ -61,6 +66,8 @@ Future<void> seedDb(AppDatabase database) async {
title: sessionValue[0],
content: sessionValue[1],
status: status,
address: Value(sessionValue[2]),
achievements: Value("[\"achievement 1\", \"achievement 2\", \"achievement 3\"]"),
date: Value(DateTime.now())))
.then((sessionId) async {
// activities things
@ -84,7 +91,6 @@ Future<void> seedDb(AppDatabase database) async {
activityId: activityId,
position: j,
results: Value("results json, will need to test"),
achievements: Value("comma, seperated, items"),
));
// actions
@ -162,6 +168,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/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

@ -0,0 +1,11 @@
import 'package:intl/intl.dart';
final DateFormat dateFormat = DateFormat('yyyy-MM-dd');
String formattedTime(int timeInSecond) {
int sec = timeInSecond % 60;
int min = (timeInSecond / 60).floor();
String minute = min.toString().length <= 1 ? "0$min" : "$min";
String second = sec.toString().length <= 1 ? "0$sec" : "$sec";
return "$minute:$second";
}

View File

@ -0,0 +1,16 @@
import 'package:flutter/material.dart';
import 'package:sendtrain/database/database.dart';
ImageProvider findMediaByType(List<MediaItem> media, MediaType type) {
Iterable<MediaItem>? found = media.where((m) => m.type == type);
Image image;
if (found.isNotEmpty) {
image = Image.network(found.first.reference);
} else {
// Element is not found
image = Image.asset('assets/images/placeholder.jpg');
}
return image.image;
}

View File

@ -0,0 +1,21 @@
import 'package:flutter/material.dart';
import 'package:sendtrain/database/database.dart';
import 'package:sendtrain/widgets/media/media_details.dart';
showMediaDetailWidget(BuildContext context, MediaItem media) {
showEditorSheet(context, MediaDetails(media: media));
}
showEditorSheet(BuildContext context, Widget widget) {
showModalBottomSheet<void>(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.only(topLeft: Radius.circular(10.0), topRight: Radius.circular(10.0)),
),
context: context,
showDragHandle: true,
isScrollControlled: true,
useSafeArea: true,
builder: (BuildContext context) {
return widget;
});
}

View File

@ -1,20 +1,37 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:sendtrain/database/database.dart';
import 'package:sendtrain/helpers/widget_helpers.dart';
import 'package:sendtrain/models/activity_timer_model.dart';
import 'package:sendtrain/screens/activities_screen.dart';
import 'package:sendtrain/screens/sessions_screen.dart';
import 'package:sendtrain/widgets/screens/activities_screen.dart';
import 'package:sendtrain/widgets/screens/sessions_screen.dart';
// ignore: unused_import
import 'package:sendtrain/database/seed.dart';
import 'package:sendtrain/widgets/sessions/session_editor.dart';
class SendTrain extends StatelessWidget {
const SendTrain({super.key});
@override
Widget build(BuildContext context) {
final ThemeData themeData = ThemeData.dark(useMaterial3: true);
return MaterialApp(
title: "Sendtrain",
theme: ThemeData.dark(useMaterial3: true),
theme: themeData.copyWith(
filledButtonTheme: FilledButtonThemeData(
style: FilledButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
),
floatingActionButtonTheme: FloatingActionButtonThemeData(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12))),
inputDecorationTheme: InputDecorationTheme(
fillColor: themeData.colorScheme.surface,
),
),
home: const App());
}
}
@ -73,7 +90,8 @@ 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(
@ -83,11 +101,11 @@ class _AppState extends State<App> {
]),
floatingActionButton: FloatingActionButton.extended(
onPressed: () {
// Add your onPressed code here!
showEditorSheet(context, SessionEditor());
},
label: const Text('New Session'),
icon: const Icon(Icons.add_chart),
backgroundColor: Colors.deepPurple,
// backgroundColor: Colors.deepPurple,
));
}
}

View File

@ -9,7 +9,7 @@ class ActivityTimerModel with ChangeNotifier {
int _actionCounter = 0;
Activity? _activity;
List _sets = [];
List _actions = [];
// List _actions = [];
int _currentActionNum = 0;
int _currentSetNum = 0;
Timer? _periodicTimer;
@ -37,7 +37,7 @@ class ActivityTimerModel with ChangeNotifier {
_activity = activity;
// only one action for now
_sets = json.decode(actions[0].set);
_actions = actions;
// _actions = actions;
_currentActionNum = 0;
_currentSetNum = 0;
setActionCount();

View File

@ -0,0 +1,99 @@
import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart';
import 'package:uuid/uuid.dart';
class GooglePlacesService {
final sessionToken = Uuid().v4();
final apiKey = "AIzaSyBCjMCEAyyNVpsnVYvZj6VL1mmB98Vd6AE";
final client = Client();
void finish() {
client.close();
}
Future<List<Suggestion>?> fetchSuggestions(String input, String lang) async {
var headers = {
'Content-Type': 'application/json',
'X-Goog-Api-Key': apiKey,
"Access-Control-Allow-Origin": "*",
'X-Goog-FieldMask':
'places.displayName,places.id,places.formattedAddress,places.photos'
};
var request = Request('POST',
Uri.parse('https://places.googleapis.com/v1/places:searchText'));
request.body = json.encode({"textQuery": input});
request.headers.addAll(headers);
StreamedResponse response = await request.send();
if (response.statusCode == 200) {
final result = json.decode(await response.stream.bytesToString());
if (result.isNotEmpty) {
return result['places']
.map<Suggestion>((p) => Suggestion(
placeId: p['id'],
description: p['displayName']['text'],
address: p['formattedAddress'],
imageReferences: p['photos']))
.toList();
} else {
return null;
}
} else {
throw Exception(response.reasonPhrase);
}
}
Future fetchPhoto(String name) async {
var headers = {
"Access-Control-Allow-Origin": "*",
};
var request = Request('GET',
Uri.parse('https://places.googleapis.com/v1/$name/media?key=$apiKey&maxWidthPx=800&skipHttpRedirect=true')
);
request.headers.addAll(headers);
StreamedResponse response = await request.send();
if (response.statusCode == 200) {
final result = json.decode(await response.stream.bytesToString());
if (result.isNotEmpty) {
return result;
} else {
return null;
}
} else {
throw Exception(response.reasonPhrase);
}
}
}
class Suggestion {
final String placeId;
final String description;
final String address;
final List<dynamic>? imageReferences;
Suggestion(
{required this.placeId,
required this.description,
required this.address,
this.imageReferences});
@override
String toString() {
return 'Suggestion(description: $description, placeId: $placeId)';
}
Map toJson() => {
'placeId': placeId,
'name': description,
'address': address,
'imageReferences': imageReferences
};
}

View File

@ -0,0 +1,68 @@
import 'dart:async';
typedef _Debounceable<S, T> = Future<S?> Function(T parameter);
class Debouncer {
Debouncer(this._duration, this._callback);
final Duration _duration;
final dynamic _callback;
late final _Debounceable<dynamic, String> _debouncedSearch = _debounce<dynamic, String>(_callback);
/// Returns a new function that is a debounced version of the given function.
///
/// This means that the original function will be called only after no calls
/// have been made for the given Duration.
_Debounceable<S, T> _debounce<S, T>(_Debounceable<S?, T> function) {
_DebounceTimer? debounceTimer;
return (T parameter) async {
if (debounceTimer != null && !debounceTimer!.isCompleted) {
debounceTimer!.cancel();
}
debounceTimer = _DebounceTimer(_duration);
try {
await debounceTimer!.future;
} on _CancelException {
return null;
}
return function(parameter);
};
}
process(data) {
return _debouncedSearch(data);
}
}
// A wrapper around Timer used for debouncing.
class _DebounceTimer {
final Duration debounceDuration;
_DebounceTimer(
this.debounceDuration
) {
_timer = Timer(debounceDuration, _onComplete);
}
late final Timer _timer;
final Completer<void> _completer = Completer<void>();
void _onComplete() {
_completer.complete();
}
Future<void> get future => _completer.future;
bool get isCompleted => _completer.isCompleted;
void cancel() {
_timer.cancel();
_completer.completeError(const _CancelException());
}
}
// An exception indicating that the timer was canceled.
class _CancelException implements Exception {
const _CancelException();
}

View File

@ -0,0 +1,74 @@
import 'dart:convert';
import 'package:drift/drift.dart' hide Column;
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:sendtrain/daos/sessions_dao.dart';
import 'package:sendtrain/database/database.dart';
import 'package:sendtrain/widgets/generic/elements/form_text_input.dart';
// class AchievementEditor extends StatefulWidget {
// const AchievementEditor({super.key, required this.session, this.callback});
// final Session session;
// final Function? callback;
// @override
// State<AchievementEditor> createState() => _AchievementEditorState();
// }
// class _AchievementEditorState extends State<AchievementEditor> {
class AchievementEditor extends StatelessWidget {
AchievementEditor({super.key, required this.session, this.callback});
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
final TextEditingController tec = TextEditingController();
final Session session;
final Function? callback;
@override
Widget build(
BuildContext context,
) {
return Padding(
padding: EdgeInsets.fromLTRB(15, 0, 15, 15),
child: Form(
key: _formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Padding(
padding: EdgeInsets.only(top: 10, bottom: 10),
child: Text('Create Achievement',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.titleLarge)),
FormTextInput(controller: tec, title: 'Achievement'),
Row(mainAxisAlignment: MainAxisAlignment.end, children: [
Padding(
padding: EdgeInsets.only(top: 10),
child: FilledButton(
child: Text('Submit'),
onPressed: () async {
session.achievements;
List achievements =
json.decode(session.achievements!);
achievements.add(tec.text);
Session updatedSession = session.copyWith(
achievements:
Value<String>(json.encode(achievements)));
SessionsDao(Provider.of<AppDatabase>(context,
listen: false))
.replace(updatedSession);
Navigator.pop(_formKey.currentContext!, 'Submit');
if (callback != null) {
await callback!();
}
}))
])
])));
}
}

View File

@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
import '../widgets/activity_type_filter.dart';
import 'activity_type_filter.dart';
class ActivitiesHeader extends StatefulWidget {
const ActivitiesHeader({super.key});

View File

@ -0,0 +1,100 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:sendtrain/daos/activities_dao.dart';
import 'package:sendtrain/daos/media_items_dao.dart';
import 'package:sendtrain/database/database.dart';
import 'package:sendtrain/extensions/string_extensions.dart';
import 'package:sendtrain/helpers/date_time_helpers.dart';
import 'package:sendtrain/helpers/media_helpers.dart';
import 'package:sendtrain/models/activity_timer_model.dart';
import 'package:sendtrain/widgets/activities/activity_view.dart';
import 'package:sendtrain/widgets/builders/dialogs.dart';
import 'package:sendtrain/widgets/generic/elements/card_image.dart';
import 'package:sendtrain/widgets/generic/elements/generic_progress_indicator.dart';
class ActivityCard extends StatefulWidget {
final Activity activity;
const ActivityCard({super.key, required this.activity});
@override
State<ActivityCard> createState() => ActivityCardState();
}
class ActivityCardState extends State<ActivityCard> {
@override
Widget build(BuildContext context) {
final ActivityTimerModel atm = Provider.of<ActivityTimerModel>(context);
return FutureBuilder<List<MediaItem>>(
future: MediaItemsDao(Provider.of<AppDatabase>(context))
.fromActivity(widget.activity),
builder: (context, snapshot) {
if (snapshot.hasData) {
List<MediaItem> mediaItems = snapshot.data!;
return Card(
color: atm.activity?.id == widget.activity.id
? Theme.of(context).colorScheme.primaryContainer
: Theme.of(context).colorScheme.surfaceContainerLow,
clipBehavior: Clip.hardEdge,
child: InkWell(
onTap: () => showGenericDialog(
ActivityView(activity: widget.activity), context),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ListTile(
// visualDensity: VisualDensity(horizontal: VisualDensity.maximumDensity),
leading: CardImage(
image:
findMediaByType(mediaItems, MediaType.image)),
title: Consumer<ActivityTimerModel>(
builder: (context, atm, child) {
if (atm.activity?.id == widget.activity.id) {
return Text(
maxLines: 1,
"${widget.activity.title.toTitleCase()} (${formattedTime(atm.totalTime)})");
} else {
return Text(
maxLines: 1,
widget.activity.title.toTitleCase());
}
},
),
subtitle:
Text(maxLines: 2, widget.activity.description),
contentPadding: EdgeInsets.only(left: 13),
trailing: Flex(
direction: Axis.vertical,
mainAxisSize: MainAxisSize.max,
children: [
IconButton(
padding: EdgeInsets.all(0),
alignment: Alignment.topCenter,
visualDensity: VisualDensity.compact,
icon: Icon(Icons.close_rounded),
onPressed: () {
showRemovalDialog(
'Activity Removal',
'Would you like to permanently remove this activity from the current session?',
context, () {
ActivitiesDao(Provider.of<AppDatabase>(
context,
listen: false))
.remove(widget.activity);
}).then((result) {
setState(() {});
});
},
)
])),
],
)),
);
} else {
return GenericProgressIndicator();
}
});
}
}

View File

@ -5,10 +5,10 @@ import 'package:sendtrain/daos/actions_dao.dart';
import 'package:sendtrain/database/database.dart';
import 'package:sendtrain/extensions/string_extensions.dart';
import 'package:sendtrain/models/activity_timer_model.dart';
import 'package:sendtrain/widgets/activity_action_view.dart';
import 'package:sendtrain/widgets/activity_view_categories.dart';
import 'package:sendtrain/widgets/activity_view_media.dart';
import 'package:sendtrain/widgets/activity_view_types.dart';
import 'package:sendtrain/widgets/activities/activity_action_view.dart';
import 'package:sendtrain/widgets/activities/activity_view_categories.dart';
import 'package:sendtrain/widgets/activities/activity_view_media.dart';
import 'package:sendtrain/widgets/activities/activity_view_types.dart';
class ActivityView extends StatefulWidget {
const ActivityView(
@ -44,6 +44,16 @@ class _ActivityViewState extends State<ActivityView> {
blur: 10,
),
children: [
FloatingActionButton.extended(
icon: const Icon(Icons.upload_outlined),
label: Text('Upload Media'),
onPressed: () {},
),
FloatingActionButton.extended(
icon: const Icon(Icons.note_add_outlined),
label: Text('Add Note'),
onPressed: () {},
),
FloatingActionButton.extended(
icon: const Icon(Icons.history_outlined),
label: Text('Restart'),
@ -54,11 +64,6 @@ class _ActivityViewState extends State<ActivityView> {
label: Text('Done'),
onPressed: () {},
),
FloatingActionButton.extended(
icon: const Icon(Icons.edit_outlined),
label: Text('Edit'),
onPressed: () {},
),
]),
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,

View File

@ -2,7 +2,7 @@ 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/widgets/media_card.dart';
import 'package:sendtrain/widgets/media/media_card.dart';
class ActivityViewMedia extends StatelessWidget {
const ActivityViewMedia({super.key, required this.activity});

View File

@ -1,150 +0,0 @@
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/extensions/string_extensions.dart';
import 'package:sendtrain/models/activity_timer_model.dart';
import 'package:sendtrain/widgets/activity_view.dart';
class ActivityCard extends StatefulWidget {
final Activity activity;
const ActivityCard({super.key, required this.activity});
@override
State<ActivityCard> createState() => ActivityCardState();
}
class ActivityCardState extends State<ActivityCard> {
String formattedTime(int timeInSecond) {
int sec = timeInSecond % 60;
int min = (timeInSecond / 60).floor();
String minute = min.toString().length <= 1 ? "0$min" : "$min";
String second = sec.toString().length <= 1 ? "0$sec" : "$sec";
return "$minute:$second";
}
@override
Widget build(BuildContext context) {
final ActivityTimerModel atm =
Provider.of<ActivityTimerModel>(context, listen: false);
return FutureBuilder<List<MediaItem>>(
future: MediaItemsDao(Provider.of<AppDatabase>(context))
.fromActivity(widget.activity),
builder: (context, snapshot) {
if (snapshot.hasData) {
List<MediaItem> mediaItems = snapshot.data!;
return Card(
color: atm.activity?.id == widget.activity.id
? Theme.of(context).colorScheme.primaryContainer
: Theme.of(context).colorScheme.surfaceContainerLow,
clipBehavior: Clip.hardEdge,
child: InkWell(
onTap: () => showGeneralDialog(
barrierColor: Colors.black.withOpacity(0.5),
transitionDuration: const Duration(milliseconds: 220),
transitionBuilder: (BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child) {
Animation<Offset> custom = Tween<Offset>(
begin: const Offset(0.0, 1.0),
end: const Offset(0.0, 0.0))
.animate(animation);
return SlideTransition(
position: custom,
child: Dialog.fullscreen(
child: ActivityView(activity: widget.activity)));
},
barrierDismissible: true,
barrierLabel: '',
context: context,
pageBuilder: (context, animation1, animation2) {
return Container();
}),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ListTile(
// visualDensity: VisualDensity(horizontal: VisualDensity.maximumDensity),
leading: Padding(
padding: const EdgeInsets.fromLTRB(0, 0, 0, 0),
child: Container(
// padding: EdgeInsets.only(top: 5, bottom: 5),
width: 60,
decoration: BoxDecoration(
image: DecorationImage(
fit: BoxFit.fill,
image:
findMediaByType(mediaItems, 'image')),
// color: Colors.blue,
borderRadius: const BorderRadius.all(
Radius.elliptical(8, 8)),
),
)),
title: Consumer<ActivityTimerModel>(
builder: (context, atm, child) {
if (atm.activity?.id == widget.activity.id) {
return Text(
maxLines: 1,
"${widget.activity.title.toTitleCase()} (${formattedTime(atm.totalTime)})");
} else {
return Text(maxLines: 1, widget.activity.title.toTitleCase());
}
},
),
subtitle: Text(maxLines: 2, widget.activity.description),
trailing: IconButton(
visualDensity: VisualDensity.compact,
icon: Icon(Icons.close_rounded),
onPressed: () {
showAdaptiveDialog(
context: context,
builder: (BuildContext context) => AlertDialog(
title: const Text('Activity Removal'),
content: const Text(
'Would you like to permanently remove this activity from the current session?'),
actions: <Widget>[
TextButton(
onPressed: () =>
Navigator.pop(context, 'Cancel'),
child: const Text('Cancel'),
),
TextButton(
onPressed: () =>
Navigator.pop(context, 'OK'),
child: const Text('OK'),
),
],
),
);
},
)),
],
)),
);
} else {
return Container(
alignment: Alignment.center,
child: SizedBox(
height: 50.0,
width: 50.0,
child: CircularProgressIndicator(),
));
}
});
}
ImageProvider findMediaByType(List<MediaItem> media, String type) {
Iterable<MediaItem>? found = media.where((m) => m.type == MediaType.image);
if (found.isNotEmpty) {
return NetworkImage(found.first.reference);
} else {
// Element is not found
return const AssetImage('assets/images/placeholder.jpg');
}
}
}

View File

@ -0,0 +1,57 @@
import 'package:flutter/material.dart';
Future showGenericDialog(dynamic object, BuildContext parentContext) {
return showGeneralDialog(
barrierColor: Colors.black.withOpacity(0.5),
transitionDuration: const Duration(milliseconds: 220),
transitionBuilder: (BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) {
Animation<Offset> custom = Tween<Offset>(
begin: const Offset(0.0, 1.0), end: const Offset(0.0, 0.0))
.animate(animation);
return SlideTransition(
position: custom, child: Dialog.fullscreen(child: object));
},
barrierDismissible: true,
barrierLabel: '',
context: parentContext,
pageBuilder: (context, animation1, animation2) {
return Container();
});
}
Future showCrudDialog(String title, String content, BuildContext context,
[Function? callback]) {
return showAdaptiveDialog(
context: context,
builder: (BuildContext context) => AlertDialog(
title: Text(title),
content: Text(content),
actions: <Widget>[
TextButton(
onPressed: () => {
Navigator.pop(context, 'Cancel'),
},
child: const Text('Cancel'),
),
TextButton(
onPressed: () => {
if (callback != null) {callback()},
Navigator.pop(context, 'OK')
},
child: const Text('OK'),
),
],
),
);
}
Future showRemovalDialog(String title, String content, BuildContext context,
[Function? callback]) {
return showCrudDialog(title, content, context, callback);
}
Future showUpdateDialog(String title, String content, BuildContext context,
[Function? callback]) {
return showCrudDialog(title, content, context, callback);
}

View File

@ -0,0 +1,19 @@
import 'package:flutter/material.dart';
class CardContent extends StatelessWidget {
const CardContent({super.key, required this.content});
final String content;
@override
Widget build(BuildContext context) {
return ListTile(
contentPadding: const EdgeInsets.fromLTRB(15, 0, 15, 15),
title: Text(
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontWeight: FontWeight.w300),
content),
);
}
}

View File

@ -0,0 +1,31 @@
import 'package:flutter/material.dart';
enum SizeAxis { width, height }
class CardImage extends StatelessWidget {
const CardImage({super.key, required this.image, this.padding, this.size});
final ImageProvider<Object> image;
final EdgeInsets? padding;
final Map<SizeAxis, double>? size;
@override
Widget build(BuildContext context) {
return Padding(
padding: padding ?? const EdgeInsets.fromLTRB(0, 0, 0, 0),
child: Container(
width: size?[SizeAxis.width] ?? 60,
height: size?[SizeAxis.height] ?? 60,
decoration: BoxDecoration(
image: DecorationImage(
fit: BoxFit.cover,
image: image,
onError: (error, stackTrace) => AssetImage('assets/images/placeholder.jpg')
// color: Colors.blue,
),
borderRadius: BorderRadius.all(Radius.elliptical(8, 8)),
),
));
}
}

View File

@ -0,0 +1,105 @@
import 'dart:async';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:sendtrain/services/apis/google_places_service.dart';
import 'package:sendtrain/services/functional/debouncer.dart';
import 'package:sendtrain/widgets/generic/elements/form_text_input.dart';
class FormSearchInput extends StatefulWidget {
const FormSearchInput(
{super.key, required this.sessionController, this.optionalPayload});
final TextEditingController sessionController;
final dynamic optionalPayload;
@override
State<FormSearchInput> createState() => _FormSearchInputState();
}
class _FormSearchInputState extends State<FormSearchInput> {
String? _currentQuery;
final service = GooglePlacesService();
// The most recent suggestions received from the API.
late Iterable<Widget> _lastOptions = <Widget>[];
late final Debouncer debouncer;
// Calls the "remote" API to search with the given query. Returns null when
// the call has been made obsolete.
Future<Iterable<Suggestion>?> _search(String query) async {
_currentQuery = query;
// In a real application, there should be some error handling here.
// final Iterable<String> options = await _FakeAPI.search(_currentQuery!);
if (query.isNotEmpty) {
final List<Suggestion>? suggestions =
await service.fetchSuggestions(_currentQuery!, 'en');
// If another search happened after this one, throw away these options.
if (_currentQuery != query) {
return null;
}
_currentQuery = null;
return suggestions?.map((suggestion) => suggestion);
} else {
return null;
}
}
@override
void initState() {
super.initState();
debouncer = Debouncer(Duration(milliseconds: 50), _search);
}
@override
Widget build(BuildContext context) {
return SearchAnchor(
builder: (BuildContext context, SearchController controller) {
return FormTextInput(
controller: widget.sessionController,
title: 'Location (optional)',
icon: Icon(Icons.search_rounded),
maxLines: 2,
requiresValidation: false,
onTap: () {
controller.openView();
});
}, suggestionsBuilder:
(BuildContext context, SearchController controller) async {
final List<Suggestion>? options =
(await debouncer.process(controller.text))?.toList();
if (options == null) {
return _lastOptions;
}
_lastOptions = List<ListTile>.generate(options.length, (int index) {
final Suggestion item = options[index];
return ListTile(
title: Text(item.description),
onTap: () async {
// widget.optionalPayload = service.fetchPhoto(json.decode(item.image));
if (item.imageReferences != null) {
// get a random photo item from the returned result
Map<String, dynamic> photo = item.imageReferences![
Random().nextInt(item.imageReferences!.length)];
await service.fetchPhoto(photo['name']).then((photoMap) {
widget.optionalPayload.photoUri = photoMap['photoUri'];
});
}
widget.optionalPayload.address = item.address;
widget.sessionController.text = item.description;
service.finish();
controller.closeView(item.description);
},
);
});
return _lastOptions;
});
}
}

View File

@ -0,0 +1,56 @@
import 'package:flutter/material.dart';
class FormTextInput extends StatelessWidget {
const FormTextInput(
{super.key,
required this.controller,
required this.title,
this.icon,
this.maxLines,
this.minLines,
this.onTap,
this.requiresValidation=true});
final TextEditingController controller;
final String title;
final int? maxLines;
final int? minLines;
final Icon? icon;
final dynamic onTap;
final bool requiresValidation;
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.only(top: 10, bottom: 10),
child: TextFormField(
minLines: minLines ?? 1,
maxLines: maxLines ?? 1,
controller: controller,
decoration: InputDecoration(
filled: true,
prefixIcon: icon ?? Icon(Icons.draw_rounded),
border: OutlineInputBorder(
borderSide: BorderSide.none,
borderRadius: BorderRadius.circular(12)),
labelText: title,
),
validator: (String? value) {
if (requiresValidation == true) {
if (value == null || value.isEmpty) {
return 'Please enter some text';
}
if (value.length < 3) {
return 'Please enter a minimum of 3 characters';
}
}
return null;
},
onTap: () {
if (onTap != null) {
onTap();
}
}));
}
}

View File

@ -0,0 +1,16 @@
import 'package:flutter/material.dart';
class GenericProgressIndicator extends StatelessWidget {
const GenericProgressIndicator({super.key});
@override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.center,
child: SizedBox(
height: 50.0,
width: 50.0,
child: CircularProgressIndicator(),
));
}
}

View File

@ -0,0 +1,64 @@
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, this.callback});
final MediaItem media;
final Function? callback;
@override
Widget build(BuildContext context) {
DecorationImage mediaImage(MediaItem media) {
dynamic image;
if (media.type == MediaType.image || media.type == MediaType.location) {
image = NetworkImage(media.reference);
} 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');
} else if (media.type == MediaType.localVideo) {}
return DecorationImage(image: image, fit: BoxFit.cover);
}
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
image: mediaImage(media),
),
child: Card(
color: Colors.transparent,
shape:
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))
.remove(media);
}).then((result) {
if (callback != null) {
callback!();
}
}),
onPressed: () => showMediaDetailWidget(context, media),
child: const ListTile(
title: Text(''),
))));
}
}

View File

@ -0,0 +1,32 @@
import 'dart:convert';
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.localImage) {
return Image.memory(base64Decode(media.reference));
} else if (media.type == MediaType.youtube) {
return YoutubePlayer(
controller: controller,
aspectRatio: 16 / 9,
);
}
return const Image(image: AssetImage('assets/images/placeholder.jpg'));
}
}

View File

@ -0,0 +1,35 @@
import 'package:flutter/material.dart';
import 'package:sendtrain/database/database.dart';
import 'package:sendtrain/widgets/media/media_content.dart';
class MediaDetails extends StatelessWidget {
const MediaDetails({super.key, required this.media});
final MediaItem media;
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.fromLTRB(15, 0, 15, 15),
child: ListView(
shrinkWrap: true,
children: <Widget>[
ClipRRect(
borderRadius: BorderRadius.circular(10),
child: MediaContent(media: media),
),
const SizedBox(height: 15),
Padding(
padding: EdgeInsets.fromLTRB(15, 0, 15, 15),
child: Text(
media.description,
style: const TextStyle(fontSize: 20),
)),
const Divider(
indent: 20,
endIndent: 20,
)
]));
}
}

View File

@ -1,93 +0,0 @@
import 'package:flutter/material.dart';
import 'package:sendtrain/database/database.dart';
import 'package:youtube_player_flutter/youtube_player_flutter.dart';
class MediaCard extends StatelessWidget {
const MediaCard({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));
DecorationImage mediaImage(MediaItem media) {
String image = '';
if (media.type == MediaType.image) {
image = media.reference;
} else if (media.type == MediaType.youtube) {
image = 'https://img.youtube.com/vi/${media.reference}/0.jpg';
}
return DecorationImage(image: NetworkImage(image), fit: BoxFit.cover);
}
Widget mediaItem(MediaItem media) {
if (media.type == MediaType.image) {
return Image(image: NetworkImage(media.reference));
} else if (media.type == MediaType.youtube) {
return YoutubePlayer(
controller: controller,
aspectRatio: 16 / 9,
);
}
return const Image(image: AssetImage('assets/images/placeholder.jpg'));
}
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
image: mediaImage(media),
),
child: Card(
color: Colors.transparent,
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
shadowColor: const Color.fromARGB(0, 255, 255, 255),
child: TextButton(
onPressed: () => showDialog<String>(
context: context,
builder: (BuildContext context) => Dialog.fullscreen(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
mediaItem(media),
const SizedBox(height: 15),
Text(
media.description,
style: const TextStyle(fontSize: 20),
),
const Divider(
indent: 20,
endIndent: 20,
color: Colors.deepPurple,
),
// const Text(
// 'Comments',
// style: TextStyle(fontSize: 20),
// ),
const SizedBox(height: 15),
TextButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text('Close'),
),
],
),
),
),
),
child: const ListTile(
title: Text(''),
))));
}
}

View File

@ -3,8 +3,8 @@ import 'package:sendtrain/classes/activity_action.dart';
import 'package:sendtrain/database/database.dart' hide ActivityAction;
import 'package:sendtrain/models/activity_model.dart';
import '../widgets/activities_header.dart';
import '../widgets/activity_card.dart';
// import '../widgets/activities/activities_header.dart';
// import '../widgets/activities/activity_card.dart';
class ActivitiesScreen extends StatefulWidget {
const ActivitiesScreen({super.key});

View File

@ -1,31 +1,64 @@
// import 'package:drift/drift.dart' hide Column;
import 'package:drift/drift.dart' hide Column;
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:sendtrain/daos/sessions_dao.dart';
import 'package:sendtrain/database/database.dart';
import '../widgets/session_card.dart';
import 'package:sendtrain/widgets/generic/elements/generic_progress_indicator.dart';
import 'package:sendtrain/widgets/sessions/session_card.dart';
import 'package:collection/collection.dart';
class SessionsScreen extends StatelessWidget {
class SessionsScreen extends StatefulWidget {
const SessionsScreen({super.key});
@override
State<SessionsScreen> createState() => _SessionsScreenState();
}
class _SessionsScreenState extends State<SessionsScreen> {
Widget getSessionCard(session) {
if (session != null) {
return SessionCard(session: session);
} else {
return Padding(
padding: EdgeInsets.all(15),
child: Icon(Icons.do_not_disturb_alt_outlined));
}
}
@override
Widget build(BuildContext context) {
return FutureBuilder<List<Session>>(
future: SessionsDao(Provider.of<AppDatabase>(context)).all(),
SessionsDao dao = SessionsDao(Provider.of<AppDatabase>(context));
return StreamBuilder<List<Session>>(
stream: dao.watch(),
builder: (context, snapshot) {
if (snapshot.hasData && snapshot.data!.isNotEmpty) {
final sessions = snapshot.data!;
final pending = sessions.where((session) =>
session.status == SessionStatus.completed ||
session.status == SessionStatus.missed);
final upcoming = sessions.firstWhere(
final upcoming = sessions.firstWhereOrNull(
(session) => session.status == SessionStatus.pending);
final current = sessions.firstWhere(
final current = sessions.firstWhereOrNull(
(session) => session.status == SessionStatus.started);
if (current == null && upcoming != null) {
dao.createOrUpdate(SessionsCompanion(
id: Value(upcoming.id),
title: Value(upcoming.title),
content: Value(upcoming.content),
status: Value(SessionStatus.started),
address: Value(upcoming.address),
date: Value(upcoming.date)
));
}
List<Widget> previousSessions = List.generate(pending.length,
(i) => SessionCard(type: 1, session: pending.elementAt(i)));
Widget upcomingSession = SessionCard(session: upcoming);
Widget currentSession = SessionCard(session: current);
Widget upcomingSession = getSessionCard(upcoming);
Widget currentSession = getSessionCard(current);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
@ -63,13 +96,7 @@ class SessionsScreen extends StatelessWidget {
],
);
} else {
return Container(
alignment: Alignment.center,
child: SizedBox(
height: 50.0,
width: 50.0,
child: CircularProgressIndicator(),
));
return GenericProgressIndicator();
}
});
}

View File

@ -1,164 +0,0 @@
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:intl/date_symbol_data_local.dart';
import 'package:sendtrain/database/database.dart' hide ActivityAction;
import 'package:sendtrain/extensions/string_extensions.dart';
import 'package:sendtrain/widgets/session_view.dart';
class SessionCard extends StatelessWidget {
final int type;
final Session session;
const SessionCard({super.key, this.type = 0, required this.session});
@override
Widget build(BuildContext context) {
initializeDateFormatting('en');
final DateFormat dateFormat = DateFormat('yyyy-MM-dd');
Color color = (session.status == SessionStatus.started)
? Theme.of(context).colorScheme.primaryContainer
: Theme.of(context).colorScheme.surfaceContainerLow;
if (type == 0) {
return Card(
color: color,
margin: const EdgeInsets.fromLTRB(15, 15, 15, 0),
clipBehavior: Clip.hardEdge,
child: InkWell(
splashColor: Colors.deepPurple,
onTap: () => showGeneralDialog(
barrierColor: Colors.black.withOpacity(0.5),
transitionDuration: const Duration(milliseconds: 220),
transitionBuilder: (BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child) {
Animation<Offset> custom = Tween<Offset>(
begin: const Offset(0.0, 1.0),
end: const Offset(0.0, 0.0))
.animate(animation);
return SlideTransition(
position: custom,
child: Dialog.fullscreen(child: SessionView(session: session)));
},
barrierDismissible: true,
barrierLabel: '',
context: context,
pageBuilder: (context, animation1, animation2) {
return Container();
}),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ListTile(
leading: Padding(
padding: const EdgeInsets.fromLTRB(0, 8, 0, 0),
child: Container(
width: 60,
decoration: const BoxDecoration(
image: DecorationImage(
fit: BoxFit.cover,
image:
AssetImage('assets/images/placeholder.jpg')),
// color: Colors.blue,
borderRadius:
BorderRadius.all(Radius.elliptical(10, 10)),
),
)),
title: Text(maxLines: 1, session.title.toTitleCase()),
subtitle: Text(maxLines: 1, dateFormat.format(session.date as DateTime)),
trailing: IconButton(
visualDensity: VisualDensity.compact,
icon: Icon(Icons.close_rounded),
onPressed: () {
showAdaptiveDialog(
context: context,
builder: (BuildContext context) => AlertDialog(
title: const Text('Session Removal'),
content: const Text(
'Would you like to permanently remove this session?'),
actions: <Widget>[
TextButton(
onPressed: () => Navigator.pop(context, 'Cancel'),
child: const Text('Cancel'),
),
TextButton(
onPressed: () => Navigator.pop(context, 'OK'),
child: const Text('OK'),
),
],
),
);
},
),
),
ListTile(
contentPadding: const EdgeInsets.fromLTRB(15, 0, 15, 15),
title: Text(
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontWeight: FontWeight.w300),
session.content),
),
],
)),
);
} else {
return Card(
color: color,
child: InkWell(
// overlayColor: MaterialStateColor(Colors.deepPurple as int),
splashColor: Colors.deepPurple,
borderRadius: const BorderRadius.all(Radius.elliptical(10, 10)),
onTap: () => showGeneralDialog(
// barrierColor: Colors.black.withOpacity(0.5),
transitionDuration: const Duration(milliseconds: 220),
transitionBuilder: (BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child) {
Animation<Offset> custom = Tween<Offset>(
begin: const Offset(0.0, 1.0),
end: const Offset(0.0, 0.0))
.animate(animation);
return SlideTransition(
position: custom,
child:
Dialog.fullscreen(child: SessionView(session: session)));
},
barrierDismissible: true,
barrierLabel: '',
context: context,
pageBuilder: (context, animation1, animation2) {
return Container();
}),
child: Container(
decoration: BoxDecoration(
// color: const Color.fromARGB(47, 0, 0, 0),
borderRadius: BorderRadius.circular(10),
image: const DecorationImage(
colorFilter: ColorFilter.mode(
Color.fromARGB(220, 41, 39, 39),
BlendMode.hardLight),
image: AssetImage('assets/images/placeholder.jpg'),
fit: BoxFit.cover),
),
child: Align(
alignment: Alignment.center,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ListTile(
title: Text(
maxLines: 3,
session.title.toTitleCase(),
textAlign: TextAlign.center),
subtitle: Text(
maxLines: 1,
dateFormat.format(session.date as DateTime),
textAlign: TextAlign.center),
),
])))));
}
}
}

View File

@ -1,106 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_expandable_fab/flutter_expandable_fab.dart';
import 'package:intl/intl.dart';
import 'package:intl/date_symbol_data_local.dart';
import 'package:provider/provider.dart';
import 'package:sendtrain/daos/activities_dao.dart';
import 'package:sendtrain/database/database.dart';
import 'package:sendtrain/extensions/string_extensions.dart';
import 'package:sendtrain/widgets/session_view_achievements.dart';
import 'package:sendtrain/widgets/session_view_activities.dart';
import 'package:sendtrain/widgets/session_view_media.dart';
class SessionView extends StatelessWidget {
const SessionView({super.key, required this.session});
final Session session;
@override
Widget build(BuildContext context) {
initializeDateFormatting('en');
final DateFormat dateFormat = DateFormat('yyyy-MM-dd');
return FutureBuilder<List<Activity>>(
future: ActivitiesDao(Provider.of<AppDatabase>(context)).sessionActivities(session.id),
builder: (context, snapshot) {
if (snapshot.hasData) {
final activities = snapshot.data!;
return Scaffold(
floatingActionButtonLocation: ExpandableFab.location,
floatingActionButton: ExpandableFab(
distance: 70,
type: ExpandableFabType.up,
overlayStyle: ExpandableFabOverlayStyle(
color: Colors.black.withOpacity(0.5),
blur: 10,
),
children: [
FloatingActionButton.extended(
icon: const Icon(Icons.history_outlined),
label: Text('Restart'),
onPressed: () {},
),
FloatingActionButton.extended(
icon: const Icon(Icons.done_all_outlined),
label: Text('Done'),
onPressed: () {},
),
FloatingActionButton.extended(
icon: const Icon(Icons.edit_outlined),
label: Text('Edit'),
onPressed: () {},
),
]),
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
AppBar(
centerTitle: true,
title: Text(
'Session @ ${dateFormat.format(session.date as DateTime)}',
style: const TextStyle(fontSize: 15)),
),
Padding(
padding: const EdgeInsets.only(
left: 15, right: 20, top: 15, bottom: 10),
child: Text(
maxLines: 1,
style: const TextStyle(
fontSize: 25, fontWeight: FontWeight.bold),
session.title.toTitleCase())),
SessionViewAchievements(session: session),
Padding(
padding: const EdgeInsets.only(left: 15, right: 15),
child: Text(
style: const TextStyle(fontSize: 15),
session.content)),
const Padding(
padding: EdgeInsets.fromLTRB(15, 30, 0, 10),
child: Text(
style: TextStyle(
fontSize: 20, fontWeight: FontWeight.bold),
'Media:')),
SessionViewMedia(session: session),
const Padding(
padding: EdgeInsets.fromLTRB(15, 30, 0, 10),
child: Text(
style: TextStyle(
fontSize: 20, fontWeight: FontWeight.bold),
'Activites:')),
SessionViewActivities(
activities: activities),
],
));
} else {
return Container(
alignment: Alignment.center,
child: SizedBox(
height: 50.0,
width: 50.0,
child: CircularProgressIndicator(),
));
}
});
}
}

View File

@ -1,68 +0,0 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:sendtrain/daos/session_activities_dao.dart';
import 'package:sendtrain/database/database.dart';
import 'package:sendtrain/extensions/string_extensions.dart';
class SessionViewAchievements extends StatelessWidget {
const SessionViewAchievements({super.key, required this.session});
final Session session;
List<String> getAchievements(List<SessionActivity> sessionActivities) {
List<String> achievements = [];
for (int i = 0; i < sessionActivities.length; i++) {
final SessionActivity sessionActivity = sessionActivities[i];
final List? saAchievments = sessionActivity.achievements?.split(',');
if (saAchievments != null) {
saAchievments.forEach((achievement) => achievements.add(achievement));
}
}
return achievements;
}
@override
Widget build(BuildContext context) {
return FutureBuilder<List<SessionActivity>>(
future: SessionActivitiesDao(Provider.of<AppDatabase>(context))
.fromSessionId(session.id),
builder: (context, snapshot) {
if (snapshot.hasData) {
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(
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 {
return Padding(
padding: EdgeInsets.all(15),
child: CircularProgressIndicator());
}
});
}
}

View File

@ -1,23 +0,0 @@
import 'package:flutter/material.dart';
import 'package:sendtrain/database/database.dart';
import 'package:sendtrain/widgets/activity_card.dart';
class SessionViewActivities extends StatelessWidget {
const SessionViewActivities({super.key, required this.activities });
final List<Activity> activities;
@override
Widget build(BuildContext context) {
return Expanded(
child: ListView.builder(
// shrinkWrap: true,
padding: const EdgeInsets.fromLTRB(10, 0, 10, 0),
itemCount: activities.length,
itemBuilder: (BuildContext context, int index) {
return ActivityCard(
activity: activities[index]);
},
));
}
}

View File

@ -0,0 +1,44 @@
import 'package:flutter/material.dart';
import 'package:intl/date_symbol_data_local.dart';
import 'package:provider/provider.dart';
import 'package:sendtrain/daos/media_items_dao.dart';
import 'package:sendtrain/database/database.dart' hide ActivityAction;
import 'package:sendtrain/widgets/generic/elements/generic_progress_indicator.dart';
import 'package:sendtrain/widgets/sessions/session_card_full.dart';
import 'package:sendtrain/widgets/sessions/session_card_small.dart';
class SessionCard extends StatefulWidget {
final int type;
final Session session;
const SessionCard({super.key, this.type = 0, required this.session});
@override
State<SessionCard> createState() => _SessionCardState();
}
class _SessionCardState extends State<SessionCard> {
@override
Widget build(BuildContext context) {
final int type = widget.type;
final Session session = widget.session;
initializeDateFormatting('en');
return StreamBuilder<List<MediaItem>>(
stream: MediaItemsDao(Provider.of<AppDatabase>(context))
.watchSessionMediaItems(session.id),
builder: (context, snapshot) {
if (snapshot.hasData) {
List<MediaItem> mediaItems = snapshot.data!;
if (type == 0) {
return SessionCardFull(session: session, mediaItems: mediaItems);
} else {
return SessionCardSmall(session: session, mediaItems: mediaItems);
}
} else {
return GenericProgressIndicator();
}
});
}
}

View File

@ -0,0 +1,100 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:sendtrain/daos/sessions_dao.dart';
import 'package:sendtrain/database/database.dart';
import 'package:sendtrain/extensions/string_extensions.dart';
import 'package:sendtrain/helpers/date_time_helpers.dart';
import 'package:sendtrain/helpers/media_helpers.dart';
import 'package:sendtrain/helpers/widget_helpers.dart';
import 'package:sendtrain/widgets/builders/dialogs.dart';
import 'package:sendtrain/widgets/generic/elements/card_content.dart';
import 'package:sendtrain/widgets/generic/elements/card_image.dart';
import 'package:sendtrain/widgets/sessions/session_view.dart';
import 'package:collection/collection.dart';
class SessionCardFull extends StatefulWidget {
const SessionCardFull(
{super.key, required this.session, required this.mediaItems});
final Session session;
final List<MediaItem> mediaItems;
@override
State<SessionCardFull> createState() => _SessionCardFullState();
}
class _SessionCardFullState extends State<SessionCardFull> {
// late final List<MediaItem> mediaItems;
// late final MediaItem? sessionImage;
// late final Session session;
String sessionTitle(Session session) {
String title = session.title.toTitleCase();
if (session.address != null) title = "$title @ ${session.address}";
return title;
}
// @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
: Theme.of(context).colorScheme.surfaceContainerLow,
margin: const EdgeInsets.fromLTRB(15, 15, 15, 0),
clipBehavior: Clip.hardEdge,
child: InkWell(
splashColor: Colors.deepPurple,
onLongPress: () => showMediaDetailWidget(context, sessionImage!),
onTap: () =>
showGenericDialog(SessionView(session: session), context),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ListTile(
contentPadding: EdgeInsets.only(left: 8),
leading: CardImage(
image: findMediaByType(mediaItems, MediaType.location),
padding: EdgeInsets.only(left: 5, top: 5)),
title: Text(maxLines: 1, sessionTitle(session)),
subtitle: Text(
maxLines: 1, dateFormat.format(session.date as DateTime)),
trailing: IconButton(
padding: EdgeInsets.all(0),
alignment: Alignment.topCenter,
icon: Icon(Icons.close_rounded),
onPressed: () {
showRemovalDialog(
'Session Removal',
'Would you like to permanently remove this session?',
context, () {
SessionsDao(
Provider.of<AppDatabase>(context, listen: false))
.remove(session);
}).then((result) {
setState(() {});
});
},
),
),
CardContent(content: session.content)
],
)),
);
}
}

View File

@ -0,0 +1,84 @@
import 'package:flutter/material.dart';
import 'package:sendtrain/database/database.dart';
import 'package:sendtrain/extensions/string_extensions.dart';
import 'package:sendtrain/helpers/date_time_helpers.dart';
import 'package:sendtrain/helpers/media_helpers.dart';
import 'package:sendtrain/helpers/widget_helpers.dart';
import 'package:sendtrain/widgets/builders/dialogs.dart';
import 'package:sendtrain/widgets/sessions/session_view.dart';
import 'package:collection/collection.dart';
class SessionCardSmall extends StatefulWidget {
const SessionCardSmall(
{super.key, required this.session, required this.mediaItems});
final Session session;
final List<MediaItem> mediaItems;
@override
State<SessionCardSmall> createState() => _SessionCardSmallState();
}
class _SessionCardSmallState extends State<SessionCardSmall> {
// late final List<MediaItem> mediaItems;
// late final MediaItem? sessionImage;
// late final Session session;
// @override
// initState() {
// super.initState();
// session = widget.session;
// mediaItems = widget.mediaItems;
// sessionImage = mediaItems.firstWhereOrNull((mediaItem) => mediaItem.type == MediaType.location);
// }
@override
Widget build(BuildContext context) {
final Session session = widget.session;
final List<MediaItem> mediaItems = widget.mediaItems;
final MediaItem? sessionImage = mediaItems
.firstWhereOrNull((mediaItem) => mediaItem.type == MediaType.location);
return Card(
color: (session.status == SessionStatus.started)
? Theme.of(context).colorScheme.primaryContainer
: Theme.of(context).colorScheme.surfaceContainerLow,
child: InkWell(
// overlayColor: MaterialStateColor(Colors.deepPurple as int),
splashColor: Colors.deepPurple,
borderRadius: const BorderRadius.all(Radius.elliptical(10, 10)),
onLongPress: () {
if (sessionImage != null) {
showMediaDetailWidget(context, sessionImage);
}
},
onTap: () =>
showGenericDialog(SessionView(session: session), context),
child: Container(
decoration: BoxDecoration(
// color: const Color.fromARGB(47, 0, 0, 0),
borderRadius: BorderRadius.circular(10),
image: DecorationImage(
colorFilter: ColorFilter.mode(
Color.fromARGB(220, 41, 39, 39), BlendMode.hardLight),
image: findMediaByType(mediaItems, MediaType.location),
fit: BoxFit.cover),
),
child: Align(
alignment: Alignment.center,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ListTile(
title: Text(
maxLines: 3,
session.title.toTitleCase(),
textAlign: TextAlign.center),
subtitle: Text(
maxLines: 1,
dateFormat.format(session.date as DateTime),
textAlign: TextAlign.center),
),
])))));
}
}

View File

@ -0,0 +1,294 @@
import 'dart:async';
import 'dart:convert';
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';
import 'package:sendtrain/daos/sessions_dao.dart';
import 'package:sendtrain/database/database.dart';
import 'package:sendtrain/widgets/builders/dialogs.dart';
import 'package:sendtrain/widgets/generic/elements/form_search_input.dart';
import 'package:sendtrain/widgets/generic/elements/form_text_input.dart';
import 'package:sendtrain/widgets/sessions/session_view.dart';
class SessionEditor extends StatefulWidget {
const SessionEditor({super.key, this.data, this.session, this.callback});
final Session? session;
final Map<String, dynamic>? data;
final Function? callback;
@override
State<SessionEditor> createState() => _SessionEditorState();
}
// used to pass the result of the found image back to current context...
class SessionPayload {
String? photoUri;
String? address;
List<PlatformFile>? files;
}
class _SessionEditorState extends State<SessionEditor> {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
late AppDatabase db;
String editorType = 'Create';
final Map<String, TextEditingController> sessionCreateController = {
'name': TextEditingController(),
'content': TextEditingController(),
'status': TextEditingController(),
'date': TextEditingController(),
'address': TextEditingController(),
'media': TextEditingController(),
};
final SessionPayload sessionPayload = SessionPayload();
@override
void initState() {
super.initState();
db = Provider.of<AppDatabase>(context, listen: false);
// if we're editing a session, we'll want to populate it with the appropriate values
if (widget.session != null) {
editorType = 'Edit';
final Session session = widget.session!;
sessionCreateController['name']?.text = session.title;
sessionCreateController['content']?.text = session.content;
sessionCreateController['status']?.text = session.status.name;
sessionCreateController['date']?.text =
DateFormat('yyyy-MM-dd').format(session.date!);
if (session.address != null) {
sessionCreateController['address']?.text = session.address!;
}
}
}
Future createSession(context) async {
Map<Symbol, Value> payload = {
Symbol('title'): Value<String>(sessionCreateController['name']!.text),
Symbol('content'):
Value<String>(sessionCreateController['content']!.text),
// we want to maintain existing status during update
Symbol('status'): widget.session != null
? Value<SessionStatus>(widget.session!.status)
: Value<SessionStatus>(SessionStatus.pending),
Symbol('date'): Value<DateTime>(
DateTime.parse(sessionCreateController['date']!.text)),
};
// if a session exists we'll want to update it
// so the payload needs the session id
if (widget.session != null) {
payload[Symbol('id')] = Value<int>(widget.session!.id);
}
// optional params
if (sessionCreateController['address']!.text.isNotEmpty) {
payload[Symbol('address')] =
Value<String>(sessionCreateController['address']!.text);
}
return await SessionsDao(db)
.createOrUpdate(Function.apply(SessionsCompanion.new, [], payload));
}
Future deleteSessionMedia(int sessionId, MediaType mediaType) async {
List<MediaItem> deletedMedia =
(await MediaItemsDao(db).fromSession(sessionId))
.where((mediaItem) => mediaItem.type == mediaType)
.toList();
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 =
DateFormat('yyyy-MM-dd').format(DateTime.now());
return Padding(
padding: EdgeInsets.fromLTRB(15, 0, 15, 15),
child: Form(
key: _formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Padding(
padding: EdgeInsets.only(top: 10, bottom: 10),
child: Text('$editorType Session',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.titleLarge)),
FormTextInput(
controller: sessionCreateController['name']!,
title: 'Title'),
FormTextInput(
controller: sessionCreateController['content']!,
title: 'Description',
icon: Icon(Icons.description_rounded),
maxLines: 10),
FormTextInput(
controller: sessionCreateController['date']!,
title: 'Date',
icon: Icon(Icons.date_range_rounded),
onTap: () {
showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime.now(),
lastDate: DateTime.now().add(Duration(days: 365)))
.then((date) {
if (date != null) {
sessionCreateController['date']?.text =
DateFormat('yyyy-MM-dd').format(date);
}
});
}),
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: '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: [
Padding(
padding: EdgeInsets.only(top: 10),
child: FilledButton(
onPressed: () async => {
if (_formKey.currentState!.validate())
{
await createSession(_formKey.currentContext)
.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 deleteSessionMedia(
currentSessionId,
MediaType.location);
await createSessionMedia(
'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];
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
// otherwise the dialog is already open, so no need
if (widget.session == null) {
SessionsDao(db)
.find(currentSessionId)
.then((session) =>
showGenericDialog(
SessionView(
session: session),
_formKey.currentContext!));
}
Navigator.pop(
_formKey.currentContext!, 'Submit');
if (widget.callback != null) {
await widget
.callback!();
}
})
}
},
child: Text('Submit')))
]),
],
)));
}
}

View File

@ -0,0 +1,188 @@
import 'package:flutter/material.dart';
import 'package:flutter_expandable_fab/flutter_expandable_fab.dart';
import 'package:intl/intl.dart';
import 'package:intl/date_symbol_data_local.dart';
import 'package:provider/provider.dart';
import 'package:sendtrain/daos/activities_dao.dart';
import 'package:sendtrain/daos/sessions_dao.dart';
import 'package:sendtrain/database/database.dart';
import 'package:sendtrain/extensions/string_extensions.dart';
import 'package:sendtrain/helpers/widget_helpers.dart';
import 'package:sendtrain/widgets/achievements/achievement_editor.dart';
import 'package:sendtrain/widgets/generic/elements/generic_progress_indicator.dart';
import 'package:sendtrain/widgets/sessions/session_editor.dart';
import 'package:sendtrain/widgets/sessions/session_view_achievements.dart';
import 'package:sendtrain/widgets/sessions/session_view_activities.dart';
import 'package:sendtrain/widgets/sessions/session_view_media.dart';
class SessionView extends StatefulWidget {
const SessionView({super.key, required this.session});
final Session session;
@override
State<SessionView> createState() => _SessionViewState();
}
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() async {
Session updatedSession =
await SessionsDao(Provider.of<AppDatabase>(context, listen: false))
.find(session.id);
final state = _fabKey.currentState;
if (state != null && state.isOpen) {
state.toggle();
}
setState(() {
session = updatedSession;
});
}
@override
initState() {
super.initState();
initializeDateFormatting('en');
dateFormat = DateFormat('yyyy-MM-dd');
session = widget.session;
}
@override
Widget build(BuildContext context) {
return StreamBuilder<Session>(
initialData: session,
stream: SessionsDao(Provider.of<AppDatabase>(context))
.watchSession(session.id),
builder: (context, snapshot) {
// return StreamBuilder<List<Activity>>(
// stream: ActivitiesDao(Provider.of<AppDatabase>(context))
// .watchSessionActivities(session.id),
// builder: (context, snapshot) {
if (snapshot.hasData) {
return Scaffold(
floatingActionButtonLocation: ExpandableFab.location,
floatingActionButton: ExpandableFab(
key: _fabKey,
distance: 70,
type: ExpandableFabType.up,
overlayStyle: ExpandableFabOverlayStyle(
color: Colors.black.withOpacity(0.5),
blur: 10,
),
children: [
FloatingActionButton.extended(
icon: const Icon(Icons.edit_outlined),
label: Text('Add Achievement'),
onPressed: () {
showEditorSheet(
context,
AchievementEditor(
session: session, callback: resetState));
},
),
FloatingActionButton.extended(
icon: const Icon(Icons.edit_outlined),
label: Text('Edit'),
onPressed: () {
showEditorSheet(
context,
SessionEditor(
session: session, callback: resetState));
},
),
FloatingActionButton.extended(
icon: const Icon(Icons.history_outlined),
label: Text('Restart'),
onPressed: () {
Session newSession =
session.copyWith(status: SessionStatus.pending);
SessionsDao(Provider.of<AppDatabase>(context,
listen: false))
.replace(newSession);
final state = _fabKey.currentState;
if (state != null) {
state.toggle();
}
},
),
FloatingActionButton.extended(
icon: const Icon(Icons.done_all_outlined),
label: Text('Done'),
onPressed: () {
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();
}
},
),
]),
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
AppBar(
centerTitle: true,
title: Text(
'Session @ ${dateFormat.format(session.date as DateTime)}',
style: const TextStyle(fontSize: 15)),
),
Padding(
padding: const EdgeInsets.only(
left: 15, right: 20, top: 15, bottom: 10),
child: Text(
maxLines: 1,
style: const TextStyle(
fontSize: 25, fontWeight: FontWeight.bold),
title())),
SessionViewAchievements(session: session, callback: resetState),
Padding(
padding: const EdgeInsets.only(left: 15, right: 15),
child: Text(
style: const TextStyle(fontSize: 15),
session.content)),
const Padding(
padding: EdgeInsets.fromLTRB(15, 30, 0, 10),
child: Text(
style: TextStyle(
fontSize: 20, fontWeight: FontWeight.bold),
'Media:')),
SessionViewMedia(session: session),
const Padding(
padding: EdgeInsets.fromLTRB(15, 30, 0, 10),
child: Text(
style: TextStyle(
fontSize: 20, fontWeight: FontWeight.bold),
'Activites:')),
SessionViewActivities(session: session),
],
));
} else {
return GenericProgressIndicator();
}
});
}
}

View File

@ -0,0 +1,181 @@
import 'dart:convert';
import 'package:drift/drift.dart' hide Column;
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:sendtrain/daos/sessions_dao.dart';
import 'package:sendtrain/database/database.dart';
import 'package:sendtrain/extensions/string_extensions.dart';
import 'package:sendtrain/helpers/widget_helpers.dart';
import 'package:sendtrain/widgets/achievements/achievement_editor.dart';
import 'package:sendtrain/widgets/builders/dialogs.dart';
class SessionViewAchievements extends StatelessWidget {
const SessionViewAchievements(
{super.key, required this.session, this.callback});
final Session session;
final Function? callback;
Session updateAchievements(int index, List achievements) {
achievements.removeAt(index);
return session.copyWith(
achievements: Value<String>(json.encode(achievements)));
}
@override
Widget build(BuildContext context) {
Widget content;
final AppDatabase db = Provider.of<AppDatabase>(context, listen: false);
List achievements = json.decode(session.achievements ?? "[]");
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: () {
showEditorSheet(context,
AchievementEditor(session: session, callback: callback));
},
));
} 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].toString().toTitleCase()),
onPressed: () async {
// remove the achievement at index
// then update session
Session newSession =
updateAchievements(index, achievements);
await showUpdateDialog(
'Achievement Removal',
'Would you like to remove this achievement?',
context, () {
SessionsDao(db).replace(newSession);
if (callback != null) {
callback!();
}
});
}));
},
);
}
return Column(
children: [
Padding(
padding: const EdgeInsets.only(bottom: 10),
child: SizedBox(height: 40, child: content)),
],
);
}
}
// class SessionViewAchievements extends StatefulWidget {
// const SessionViewAchievements({super.key, required this.session});
// final Session session;
// @override
// State<SessionViewAchievements> createState() =>
// _SessionViewAchievementsState();
// }
// class _SessionViewAchievementsState extends State<SessionViewAchievements> {
// late final AppDatabase db;
// late Session session;
// late List achievements;
// @override
// void initState() {
// super.initState();
// db = Provider.of<AppDatabase>(context, listen: false);
// session = widget.session;
// achievements = json.decode(session.achievements!);
// }
// void resetState(int sessionId) async {
// Session updatedSession =
// await SessionsDao(Provider.of<AppDatabase>(context, listen: false))
// .find(sessionId);
// setState(() {
// session = updatedSession;
// achievements = json.decode(session.achievements!);
// });
// }
// Session updateAchievements(int index) {
// achievements.removeAt(index);
// return session.copyWith(
// achievements: Value<String>(json.encode(achievements)));
// }
// @override
// Widget build(BuildContext context) {
// 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: () {
// showEditorSheet(context,
// AchievementEditor(session: session, callback: resetState));
// },
// ));
// } 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].toString().toTitleCase()),
// onPressed: () async {
// // remove the achievement at index
// // then update session
// Session newSession = updateAchievements(index);
// await showUpdateDialog(
// 'Achievement Removal',
// 'Would you like to remove this achievement?',
// context,
// SessionsDao(db),
// newSession,
// resetState);
// }));
// },
// );
// }
// return Column(
// children: [
// Padding(
// padding: const EdgeInsets.only(bottom: 10),
// child: SizedBox(height: 40, child: content)),
// ],
// );
// }
// }

View File

@ -0,0 +1,40 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:sendtrain/daos/activities_dao.dart';
import 'package:sendtrain/database/database.dart';
import 'package:sendtrain/widgets/activities/activity_card.dart';
import 'package:sendtrain/widgets/generic/elements/generic_progress_indicator.dart';
class SessionViewActivities extends StatefulWidget {
const SessionViewActivities({super.key, required this.session});
final Session session;
@override
State<SessionViewActivities> createState() => _SessionViewActivitiesState();
}
class _SessionViewActivitiesState extends State<SessionViewActivities> {
@override
Widget build(BuildContext context) {
return StreamBuilder<List<Activity>>(
stream: ActivitiesDao(Provider.of<AppDatabase>(context))
.watchSessionActivities(widget.session.id),
builder: (context, snapshot) {
if (snapshot.hasData) {
final activities = snapshot.data!;
return Expanded(
child: ListView.builder(
// shrinkWrap: true,
padding: const EdgeInsets.fromLTRB(10, 0, 10, 0),
itemCount: activities.length,
itemBuilder: (BuildContext context, int index) {
return ActivityCard(activity: activities[index]);
},
));
} else {
return GenericProgressIndicator();
}
});
}
}

View File

@ -2,24 +2,33 @@ 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/widgets/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});
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),
.fromSession(widget.session.id),
builder: (context, snapshot) {
if (snapshot.hasData) {
final mediaItems = snapshot.data!;
List<Widget> mediaCards = List.generate(
mediaItems.length, (i) => MediaCard(media: mediaItems[i]));
List<Widget> mediaCards = List.generate(mediaItems.length,
(i) => MediaCard(media: mediaItems[i], callback: resetState));
return Column(
children: [

View File

@ -44,6 +44,12 @@ dependencies:
drift: ^2.22.1
flutter_expandable_fab: ^2.3.0
drift_flutter: ^0.2.2
map_location_picker: ^1.2.7
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

@ -7,6 +7,15 @@ import 'schema_v1.dart' as v1;
import 'schema_v2.dart' as v2;
import 'schema_v3.dart' as v3;
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;
import 'schema_v10.dart' as v10;
import 'schema_v11.dart' as v11;
import 'schema_v12.dart' as v12;
import 'schema_v13.dart' as v13;
class GeneratedHelper implements SchemaInstantiationHelper {
@override
@ -20,10 +29,28 @@ class GeneratedHelper implements SchemaInstantiationHelper {
return v3.DatabaseAtV3(db);
case 4:
return v4.DatabaseAtV4(db);
case 5:
return v5.DatabaseAtV5(db);
case 6:
return v6.DatabaseAtV6(db);
case 7:
return v7.DatabaseAtV7(db);
case 8:
return v8.DatabaseAtV8(db);
case 9:
return v9.DatabaseAtV9(db);
case 10:
return v10.DatabaseAtV10(db);
case 11:
return v11.DatabaseAtV11(db);
case 12:
return v12.DatabaseAtV12(db);
case 13:
return v13.DatabaseAtV13(db);
default:
throw MissingSchemaException(version, versions);
}
}
static const versions = const [1, 2, 3, 4];
static const versions = const [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
}

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

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

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