diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..5a73a231 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.g.dart linguist-generated diff --git a/demos/supabase-todolist/build.yaml b/demos/supabase-todolist/build.yaml new file mode 100644 index 00000000..4ea5a80c --- /dev/null +++ b/demos/supabase-todolist/build.yaml @@ -0,0 +1,6 @@ +targets: + $default: + builders: + drift_dev: + options: + store_date_time_values_as_text: true diff --git a/demos/supabase-todolist/lib/attachments/photo_capture_widget.dart b/demos/supabase-todolist/lib/attachments/photo_capture_widget.dart index a4bad5a9..bd866164 100644 --- a/demos/supabase-todolist/lib/attachments/photo_capture_widget.dart +++ b/demos/supabase-todolist/lib/attachments/photo_capture_widget.dart @@ -4,7 +4,6 @@ import 'package:camera/camera.dart'; import 'package:flutter/material.dart'; import 'package:powersync/powersync.dart' as powersync; import 'package:powersync_flutter_demo/attachments/queue.dart'; -import 'package:powersync_flutter_demo/models/todo_item.dart'; import 'package:powersync_flutter_demo/powersync.dart'; class TakePhotoWidget extends StatefulWidget { @@ -43,7 +42,7 @@ class _TakePhotoWidgetState extends State { super.dispose(); } - Future _takePhoto(context) async { + Future _takePhoto(BuildContext context) async { try { // Ensure the camera is initialized before taking a photo await _initializeControllerFuture; @@ -57,13 +56,14 @@ class _TakePhotoWidgetState extends State { int photoSize = await photo.length(); - TodoItem.addPhoto(photoId, widget.todoId); - attachmentQueue.savePhoto(photoId, photoSize); + await appDb.addTodoPhoto(widget.todoId, photoId); + await attachmentQueue.savePhoto(photoId, photoSize); } catch (e) { log.info('Error taking photo: $e'); } // After taking the photo, navigate back to the previous screen + if (!context.mounted) return; Navigator.pop(context); } diff --git a/demos/supabase-todolist/lib/attachments/photo_widget.dart b/demos/supabase-todolist/lib/attachments/photo_widget.dart index c6ad8e52..0a71aec4 100644 --- a/demos/supabase-todolist/lib/attachments/photo_widget.dart +++ b/demos/supabase-todolist/lib/attachments/photo_widget.dart @@ -4,8 +4,7 @@ import 'package:flutter/material.dart'; import 'package:powersync_flutter_demo/attachments/camera_helpers.dart'; import 'package:powersync_flutter_demo/attachments/photo_capture_widget.dart'; import 'package:powersync_flutter_demo/attachments/queue.dart'; - -import '../models/todo_item.dart'; +import 'package:powersync_flutter_demo/database.dart'; class PhotoWidget extends StatefulWidget { final TodoItem todo; diff --git a/demos/supabase-todolist/lib/database.dart b/demos/supabase-todolist/lib/database.dart new file mode 100644 index 00000000..1913e599 --- /dev/null +++ b/demos/supabase-todolist/lib/database.dart @@ -0,0 +1,116 @@ +import 'package:drift/drift.dart'; +import 'package:powersync/powersync.dart' show uuid, PowerSyncDatabase; +import 'package:drift_sqlite_async/drift_sqlite_async.dart'; +import 'package:powersync_flutter_demo/powersync.dart'; + +part 'database.g.dart'; + +class TodoItems extends Table { + @override + String get tableName => 'todos'; + + TextColumn get id => text().clientDefault(() => uuid.v4())(); + TextColumn get listId => text().named('list_id').references(ListItems, #id)(); + TextColumn get photoId => text().nullable().named('photo_id')(); + DateTimeColumn get createdAt => dateTime().nullable().named('created_at')(); + DateTimeColumn get completedAt => + dateTime().nullable().named('completed_at')(); + BoolColumn get completed => boolean().nullable()(); + TextColumn get description => text()(); + TextColumn get createdBy => text().nullable().named('created_by')(); + TextColumn get completedBy => text().nullable().named('completed_by')(); +} + +class ListItems extends Table { + @override + String get tableName => 'lists'; + + TextColumn get id => text().clientDefault(() => uuid.v4())(); + DateTimeColumn get createdAt => + dateTime().named('created_at').clientDefault(() => DateTime.now())(); + TextColumn get name => text()(); + TextColumn get ownerId => text().nullable().named('owner_id')(); +} + +class ListItemWithStats { + late ListItem self; + int completedCount; + int pendingCount; + + ListItemWithStats( + this.self, + this.completedCount, + this.pendingCount, + ); +} + +@DriftDatabase(tables: [TodoItems, ListItems], include: {'queries.drift'}) +class AppDatabase extends _$AppDatabase { + AppDatabase(PowerSyncDatabase db) : super(SqliteAsyncDriftConnection(db)); + + @override + int get schemaVersion => 1; + + Stream> watchLists() { + return (select(listItems) + ..orderBy([(l) => OrderingTerm(expression: l.createdAt)])) + .watch(); + } + + Stream> watchListsWithStats() { + return listsWithStats().watch(); + } + + Future createList(String name) async { + return into(listItems).insertReturning( + ListItemsCompanion.insert(name: name, ownerId: Value(getUserId()))); + } + + Future deleteList(ListItem list) async { + await (delete(listItems)..where((t) => t.id.equals(list.id))).go(); + } + + Stream> watchTodoItems(ListItem list) { + return (select(todoItems) + ..where((t) => t.listId.equals(list.id)) + ..orderBy([(t) => OrderingTerm(expression: t.createdAt)])) + .watch(); + } + + Future deleteTodo(TodoItem todo) async { + await (delete(todoItems)..where((t) => t.id.equals(todo.id))).go(); + } + + Future addTodo(ListItem list, String description) async { + return into(todoItems).insertReturning(TodoItemsCompanion.insert( + listId: list.id, + description: description, + completed: const Value(false), + createdBy: Value(getUserId()))); + } + + Future toggleTodo(TodoItem todo) async { + if (todo.completed != true) { + await (update(todoItems)..where((t) => t.id.equals(todo.id))).write( + TodoItemsCompanion( + completed: const Value(true), + completedAt: Value(DateTime.now()), + completedBy: Value(getUserId()))); + } else { + await (update(todoItems)..where((t) => t.id.equals(todo.id))).write( + const TodoItemsCompanion( + completed: Value(false), + completedAt: Value.absent(), + completedBy: Value.absent())); + } + } + + Future addTodoPhoto(String todoId, String photoId) async { + await (update(todoItems)..where((t) => t.id.equals(todoId))) + .write(TodoItemsCompanion(photoId: Value(photoId))); + } + + Future findList(String id) { + return (select(listItems)..where((t) => t.id.equals(id))).getSingle(); + } +} diff --git a/demos/supabase-todolist/lib/database.g.dart b/demos/supabase-todolist/lib/database.g.dart new file mode 100644 index 00000000..e042fb58 --- /dev/null +++ b/demos/supabase-todolist/lib/database.g.dart @@ -0,0 +1,761 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'database.dart'; + +// ignore_for_file: type=lint +class $ListItemsTable extends ListItems + with TableInfo<$ListItemsTable, ListItem> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $ListItemsTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + clientDefault: () => uuid.v4()); + static const VerificationMeta _createdAtMeta = + const VerificationMeta('createdAt'); + @override + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + clientDefault: () => DateTime.now()); + static const VerificationMeta _nameMeta = const VerificationMeta('name'); + @override + late final GeneratedColumn name = GeneratedColumn( + 'name', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _ownerIdMeta = + const VerificationMeta('ownerId'); + @override + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + @override + List get $columns => [id, createdAt, name, ownerId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'lists'; + @override + VerificationContext validateIntegrity(Insertable instance, + {bool isInserting = false}) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('created_at')) { + context.handle(_createdAtMeta, + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); + } + if (data.containsKey('name')) { + context.handle( + _nameMeta, name.isAcceptableOrUnknown(data['name']!, _nameMeta)); + } else if (isInserting) { + context.missing(_nameMeta); + } + if (data.containsKey('owner_id')) { + context.handle(_ownerIdMeta, + ownerId.isAcceptableOrUnknown(data['owner_id']!, _ownerIdMeta)); + } + return context; + } + + @override + Set get $primaryKey => const {}; + @override + ListItem map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return ListItem( + id: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}id'])!, + createdAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + name: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}name'])!, + ownerId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}owner_id']), + ); + } + + @override + $ListItemsTable createAlias(String alias) { + return $ListItemsTable(attachedDatabase, alias); + } +} + +class ListItem extends DataClass implements Insertable { + final String id; + final DateTime createdAt; + final String name; + final String? ownerId; + const ListItem( + {required this.id, + required this.createdAt, + required this.name, + this.ownerId}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['name'] = Variable(name); + if (!nullToAbsent || ownerId != null) { + map['owner_id'] = Variable(ownerId); + } + return map; + } + + ListItemsCompanion toCompanion(bool nullToAbsent) { + return ListItemsCompanion( + id: Value(id), + createdAt: Value(createdAt), + name: Value(name), + ownerId: ownerId == null && nullToAbsent + ? const Value.absent() + : Value(ownerId), + ); + } + + factory ListItem.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return ListItem( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + name: serializer.fromJson(json['name']), + ownerId: serializer.fromJson(json['ownerId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'name': serializer.toJson(name), + 'ownerId': serializer.toJson(ownerId), + }; + } + + ListItem copyWith( + {String? id, + DateTime? createdAt, + String? name, + Value ownerId = const Value.absent()}) => + ListItem( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + name: name ?? this.name, + ownerId: ownerId.present ? ownerId.value : this.ownerId, + ); + @override + String toString() { + return (StringBuffer('ListItem(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('name: $name, ') + ..write('ownerId: $ownerId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, createdAt, name, ownerId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is ListItem && + other.id == this.id && + other.createdAt == this.createdAt && + other.name == this.name && + other.ownerId == this.ownerId); +} + +class ListItemsCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value name; + final Value ownerId; + final Value rowid; + const ListItemsCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.name = const Value.absent(), + this.ownerId = const Value.absent(), + this.rowid = const Value.absent(), + }); + ListItemsCompanion.insert({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + required String name, + this.ownerId = const Value.absent(), + this.rowid = const Value.absent(), + }) : name = Value(name); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? name, + Expression? ownerId, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (name != null) 'name': name, + if (ownerId != null) 'owner_id': ownerId, + if (rowid != null) 'rowid': rowid, + }); + } + + ListItemsCompanion copyWith( + {Value? id, + Value? createdAt, + Value? name, + Value? ownerId, + Value? rowid}) { + return ListItemsCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + name: name ?? this.name, + ownerId: ownerId ?? this.ownerId, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('ListItemsCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('name: $name, ') + ..write('ownerId: $ownerId, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class $TodoItemsTable extends TodoItems + with TableInfo<$TodoItemsTable, TodoItem> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $TodoItemsTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + clientDefault: () => uuid.v4()); + static const VerificationMeta _listIdMeta = const VerificationMeta('listId'); + @override + late final GeneratedColumn listId = GeneratedColumn( + 'list_id', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: + GeneratedColumn.constraintIsAlways('REFERENCES lists (id)')); + static const VerificationMeta _photoIdMeta = + const VerificationMeta('photoId'); + @override + late final GeneratedColumn photoId = GeneratedColumn( + 'photo_id', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + static const VerificationMeta _createdAtMeta = + const VerificationMeta('createdAt'); + @override + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', aliasedName, true, + type: DriftSqlType.dateTime, requiredDuringInsert: false); + static const VerificationMeta _completedAtMeta = + const VerificationMeta('completedAt'); + @override + late final GeneratedColumn completedAt = GeneratedColumn( + 'completed_at', aliasedName, true, + type: DriftSqlType.dateTime, requiredDuringInsert: false); + static const VerificationMeta _completedMeta = + const VerificationMeta('completed'); + @override + late final GeneratedColumn completed = GeneratedColumn( + 'completed', aliasedName, true, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('CHECK ("completed" IN (0, 1))')); + static const VerificationMeta _descriptionMeta = + const VerificationMeta('description'); + @override + late final GeneratedColumn description = GeneratedColumn( + 'description', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _createdByMeta = + const VerificationMeta('createdBy'); + @override + late final GeneratedColumn createdBy = GeneratedColumn( + 'created_by', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + static const VerificationMeta _completedByMeta = + const VerificationMeta('completedBy'); + @override + late final GeneratedColumn completedBy = GeneratedColumn( + 'completed_by', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + @override + List get $columns => [ + id, + listId, + photoId, + createdAt, + completedAt, + completed, + description, + createdBy, + completedBy + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'todos'; + @override + VerificationContext validateIntegrity(Insertable instance, + {bool isInserting = false}) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('list_id')) { + context.handle(_listIdMeta, + listId.isAcceptableOrUnknown(data['list_id']!, _listIdMeta)); + } else if (isInserting) { + context.missing(_listIdMeta); + } + if (data.containsKey('photo_id')) { + context.handle(_photoIdMeta, + photoId.isAcceptableOrUnknown(data['photo_id']!, _photoIdMeta)); + } + if (data.containsKey('created_at')) { + context.handle(_createdAtMeta, + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); + } + if (data.containsKey('completed_at')) { + context.handle( + _completedAtMeta, + completedAt.isAcceptableOrUnknown( + data['completed_at']!, _completedAtMeta)); + } + if (data.containsKey('completed')) { + context.handle(_completedMeta, + completed.isAcceptableOrUnknown(data['completed']!, _completedMeta)); + } + if (data.containsKey('description')) { + context.handle( + _descriptionMeta, + description.isAcceptableOrUnknown( + data['description']!, _descriptionMeta)); + } else if (isInserting) { + context.missing(_descriptionMeta); + } + if (data.containsKey('created_by')) { + context.handle(_createdByMeta, + createdBy.isAcceptableOrUnknown(data['created_by']!, _createdByMeta)); + } + if (data.containsKey('completed_by')) { + context.handle( + _completedByMeta, + completedBy.isAcceptableOrUnknown( + data['completed_by']!, _completedByMeta)); + } + return context; + } + + @override + Set get $primaryKey => const {}; + @override + TodoItem map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return TodoItem( + id: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}id'])!, + listId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}list_id'])!, + photoId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}photo_id']), + createdAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at']), + completedAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}completed_at']), + completed: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}completed']), + description: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}description'])!, + createdBy: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}created_by']), + completedBy: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}completed_by']), + ); + } + + @override + $TodoItemsTable createAlias(String alias) { + return $TodoItemsTable(attachedDatabase, alias); + } +} + +class TodoItem extends DataClass implements Insertable { + final String id; + final String listId; + final String? photoId; + final DateTime? createdAt; + final DateTime? completedAt; + final bool? completed; + final String description; + final String? createdBy; + final String? completedBy; + const TodoItem( + {required this.id, + required this.listId, + this.photoId, + this.createdAt, + this.completedAt, + this.completed, + required this.description, + this.createdBy, + this.completedBy}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['list_id'] = Variable(listId); + if (!nullToAbsent || photoId != null) { + map['photo_id'] = Variable(photoId); + } + if (!nullToAbsent || createdAt != null) { + map['created_at'] = Variable(createdAt); + } + if (!nullToAbsent || completedAt != null) { + map['completed_at'] = Variable(completedAt); + } + if (!nullToAbsent || completed != null) { + map['completed'] = Variable(completed); + } + map['description'] = Variable(description); + if (!nullToAbsent || createdBy != null) { + map['created_by'] = Variable(createdBy); + } + if (!nullToAbsent || completedBy != null) { + map['completed_by'] = Variable(completedBy); + } + return map; + } + + TodoItemsCompanion toCompanion(bool nullToAbsent) { + return TodoItemsCompanion( + id: Value(id), + listId: Value(listId), + photoId: photoId == null && nullToAbsent + ? const Value.absent() + : Value(photoId), + createdAt: createdAt == null && nullToAbsent + ? const Value.absent() + : Value(createdAt), + completedAt: completedAt == null && nullToAbsent + ? const Value.absent() + : Value(completedAt), + completed: completed == null && nullToAbsent + ? const Value.absent() + : Value(completed), + description: Value(description), + createdBy: createdBy == null && nullToAbsent + ? const Value.absent() + : Value(createdBy), + completedBy: completedBy == null && nullToAbsent + ? const Value.absent() + : Value(completedBy), + ); + } + + factory TodoItem.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return TodoItem( + id: serializer.fromJson(json['id']), + listId: serializer.fromJson(json['listId']), + photoId: serializer.fromJson(json['photoId']), + createdAt: serializer.fromJson(json['createdAt']), + completedAt: serializer.fromJson(json['completedAt']), + completed: serializer.fromJson(json['completed']), + description: serializer.fromJson(json['description']), + createdBy: serializer.fromJson(json['createdBy']), + completedBy: serializer.fromJson(json['completedBy']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'listId': serializer.toJson(listId), + 'photoId': serializer.toJson(photoId), + 'createdAt': serializer.toJson(createdAt), + 'completedAt': serializer.toJson(completedAt), + 'completed': serializer.toJson(completed), + 'description': serializer.toJson(description), + 'createdBy': serializer.toJson(createdBy), + 'completedBy': serializer.toJson(completedBy), + }; + } + + TodoItem copyWith( + {String? id, + String? listId, + Value photoId = const Value.absent(), + Value createdAt = const Value.absent(), + Value completedAt = const Value.absent(), + Value completed = const Value.absent(), + String? description, + Value createdBy = const Value.absent(), + Value completedBy = const Value.absent()}) => + TodoItem( + id: id ?? this.id, + listId: listId ?? this.listId, + photoId: photoId.present ? photoId.value : this.photoId, + createdAt: createdAt.present ? createdAt.value : this.createdAt, + completedAt: completedAt.present ? completedAt.value : this.completedAt, + completed: completed.present ? completed.value : this.completed, + description: description ?? this.description, + createdBy: createdBy.present ? createdBy.value : this.createdBy, + completedBy: completedBy.present ? completedBy.value : this.completedBy, + ); + @override + String toString() { + return (StringBuffer('TodoItem(') + ..write('id: $id, ') + ..write('listId: $listId, ') + ..write('photoId: $photoId, ') + ..write('createdAt: $createdAt, ') + ..write('completedAt: $completedAt, ') + ..write('completed: $completed, ') + ..write('description: $description, ') + ..write('createdBy: $createdBy, ') + ..write('completedBy: $completedBy') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, listId, photoId, createdAt, completedAt, + completed, description, createdBy, completedBy); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is TodoItem && + other.id == this.id && + other.listId == this.listId && + other.photoId == this.photoId && + other.createdAt == this.createdAt && + other.completedAt == this.completedAt && + other.completed == this.completed && + other.description == this.description && + other.createdBy == this.createdBy && + other.completedBy == this.completedBy); +} + +class TodoItemsCompanion extends UpdateCompanion { + final Value id; + final Value listId; + final Value photoId; + final Value createdAt; + final Value completedAt; + final Value completed; + final Value description; + final Value createdBy; + final Value completedBy; + final Value rowid; + const TodoItemsCompanion({ + this.id = const Value.absent(), + this.listId = const Value.absent(), + this.photoId = const Value.absent(), + this.createdAt = const Value.absent(), + this.completedAt = const Value.absent(), + this.completed = const Value.absent(), + this.description = const Value.absent(), + this.createdBy = const Value.absent(), + this.completedBy = const Value.absent(), + this.rowid = const Value.absent(), + }); + TodoItemsCompanion.insert({ + this.id = const Value.absent(), + required String listId, + this.photoId = const Value.absent(), + this.createdAt = const Value.absent(), + this.completedAt = const Value.absent(), + this.completed = const Value.absent(), + required String description, + this.createdBy = const Value.absent(), + this.completedBy = const Value.absent(), + this.rowid = const Value.absent(), + }) : listId = Value(listId), + description = Value(description); + static Insertable custom({ + Expression? id, + Expression? listId, + Expression? photoId, + Expression? createdAt, + Expression? completedAt, + Expression? completed, + Expression? description, + Expression? createdBy, + Expression? completedBy, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (listId != null) 'list_id': listId, + if (photoId != null) 'photo_id': photoId, + if (createdAt != null) 'created_at': createdAt, + if (completedAt != null) 'completed_at': completedAt, + if (completed != null) 'completed': completed, + if (description != null) 'description': description, + if (createdBy != null) 'created_by': createdBy, + if (completedBy != null) 'completed_by': completedBy, + if (rowid != null) 'rowid': rowid, + }); + } + + TodoItemsCompanion copyWith( + {Value? id, + Value? listId, + Value? photoId, + Value? createdAt, + Value? completedAt, + Value? completed, + Value? description, + Value? createdBy, + Value? completedBy, + Value? rowid}) { + return TodoItemsCompanion( + id: id ?? this.id, + listId: listId ?? this.listId, + photoId: photoId ?? this.photoId, + createdAt: createdAt ?? this.createdAt, + completedAt: completedAt ?? this.completedAt, + completed: completed ?? this.completed, + description: description ?? this.description, + createdBy: createdBy ?? this.createdBy, + completedBy: completedBy ?? this.completedBy, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (listId.present) { + map['list_id'] = Variable(listId.value); + } + if (photoId.present) { + map['photo_id'] = Variable(photoId.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (completedAt.present) { + map['completed_at'] = Variable(completedAt.value); + } + if (completed.present) { + map['completed'] = Variable(completed.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (createdBy.present) { + map['created_by'] = Variable(createdBy.value); + } + if (completedBy.present) { + map['completed_by'] = Variable(completedBy.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('TodoItemsCompanion(') + ..write('id: $id, ') + ..write('listId: $listId, ') + ..write('photoId: $photoId, ') + ..write('createdAt: $createdAt, ') + ..write('completedAt: $completedAt, ') + ..write('completed: $completed, ') + ..write('description: $description, ') + ..write('createdBy: $createdBy, ') + ..write('completedBy: $completedBy, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +abstract class _$AppDatabase extends GeneratedDatabase { + _$AppDatabase(QueryExecutor e) : super(e); + late final $ListItemsTable listItems = $ListItemsTable(this); + late final $TodoItemsTable todoItems = $TodoItemsTable(this); + Selectable listsWithStats() { + return customSelect( + 'SELECT"self"."id" AS "nested_0.id", "self"."created_at" AS "nested_0.created_at", "self"."name" AS "nested_0.name", "self"."owner_id" AS "nested_0.owner_id", (SELECT count() FROM todos WHERE list_id = self.id AND completed = TRUE) AS completed_count, (SELECT count() FROM todos WHERE list_id = self.id AND completed = FALSE) AS pending_count FROM lists AS self ORDER BY created_at', + variables: [], + readsFrom: { + todoItems, + listItems, + }).asyncMap((QueryRow row) async => ListItemWithStats( + await listItems.mapFromRow(row, tablePrefix: 'nested_0'), + row.read('completed_count'), + row.read('pending_count'), + )); + } + + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [listItems, todoItems]; + @override + DriftDatabaseOptions get options => + const DriftDatabaseOptions(storeDateTimeAsText: true); +} diff --git a/demos/supabase-todolist/lib/models/todo_item.dart b/demos/supabase-todolist/lib/models/todo_item.dart deleted file mode 100644 index 5e15f3f8..00000000 --- a/demos/supabase-todolist/lib/models/todo_item.dart +++ /dev/null @@ -1,50 +0,0 @@ -import 'package:powersync_flutter_demo/models/schema.dart'; - -import '../powersync.dart'; -import 'package:powersync/sqlite3.dart' as sqlite; - -/// TodoItem represents a result row of a query on "todos". -/// -/// This class is immutable - methods on this class do not modify the instance -/// directly. Instead, watch or re-query the data to get the updated item. -class TodoItem { - final String id; - final String description; - final String? photoId; - final bool completed; - - TodoItem( - {required this.id, - required this.description, - required this.completed, - required this.photoId}); - - factory TodoItem.fromRow(sqlite.Row row) { - return TodoItem( - id: row['id'], - description: row['description'], - photoId: row['photo_id'], - completed: row['completed'] == 1); - } - - Future toggle() async { - if (completed) { - await db.execute( - 'UPDATE $todosTable SET completed = FALSE, completed_by = NULL, completed_at = NULL WHERE id = ?', - [id]); - } else { - await db.execute( - 'UPDATE $todosTable SET completed = TRUE, completed_by = ?, completed_at = datetime() WHERE id = ?', - [getUserId(), id]); - } - } - - Future delete() async { - await db.execute('DELETE FROM $todosTable WHERE id = ?', [id]); - } - - static Future addPhoto(String photoId, String id) async { - await db.execute( - 'UPDATE $todosTable SET photo_id = ? WHERE id = ?', [photoId, id]); - } -} diff --git a/demos/supabase-todolist/lib/models/todo_list.dart b/demos/supabase-todolist/lib/models/todo_list.dart deleted file mode 100644 index 489d1631..00000000 --- a/demos/supabase-todolist/lib/models/todo_list.dart +++ /dev/null @@ -1,103 +0,0 @@ -import 'package:powersync/sqlite3.dart' as sqlite; - -import './todo_item.dart'; -import '../powersync.dart'; - -/// TodoList represents a result row of a query on "lists". -/// -/// This class is immutable - methods on this class do not modify the instance -/// directly. Instead, watch or re-query the data to get the updated list. -class TodoList { - /// List id (UUID). - final String id; - - /// Descriptive name. - final String name; - - /// Number of completed todos in this list. - final int? completedCount; - - /// Number of pending todos in this list. - final int? pendingCount; - - TodoList( - {required this.id, - required this.name, - this.completedCount, - this.pendingCount}); - - factory TodoList.fromRow(sqlite.Row row) { - return TodoList( - id: row['id'], - name: row['name'], - completedCount: row['completed_count'], - pendingCount: row['pending_count']); - } - - /// Watch all lists. - static Stream> watchLists() { - // This query is automatically re-run when data in "lists" or "todos" is modified. - return db - .watch('SELECT * FROM lists ORDER BY created_at, id') - .map((results) { - return results.map(TodoList.fromRow).toList(growable: false); - }); - } - - /// Watch all lists, with [completedCount] and [pendingCount] populated. - static Stream> watchListsWithStats() { - // This query is automatically re-run when data in "lists" or "todos" is modified. - return db.watch(''' - SELECT - *, - (SELECT count() FROM todos WHERE list_id = lists.id AND completed = TRUE) as completed_count, - (SELECT count() FROM todos WHERE list_id = lists.id AND completed = FALSE) as pending_count - FROM lists - ORDER BY created_at - ''').map((results) { - return results.map(TodoList.fromRow).toList(growable: false); - }); - } - - /// Create a new list - static Future create(String name) async { - final results = await db.execute(''' - INSERT INTO - lists(id, created_at, name, owner_id) - VALUES(uuid(), datetime(), ?, ?) - RETURNING * - ''', [name, getUserId()]); - return TodoList.fromRow(results.first); - } - - /// Watch items within this list. - Stream> watchItems() { - return db.watch( - 'SELECT * FROM todos WHERE list_id = ? ORDER BY created_at DESC, id', - parameters: [id]).map((event) { - return event.map(TodoItem.fromRow).toList(growable: false); - }); - } - - /// Delete this list. - Future delete() async { - await db.execute('DELETE FROM lists WHERE id = ?', [id]); - } - - /// Find list item. - static Future find(id) async { - final results = await db.get('SELECT * FROM lists WHERE id = ?', [id]); - return TodoList.fromRow(results); - } - - /// Add a new todo item to this list. - Future add(String description) async { - final results = await db.execute(''' - INSERT INTO - todos(id, created_at, completed, list_id, description, created_by) - VALUES(uuid(), datetime(), FALSE, ?, ?, ?) - RETURNING * - ''', [id, description, getUserId()]); - return TodoItem.fromRow(results.first); - } -} diff --git a/demos/supabase-todolist/lib/powersync.dart b/demos/supabase-todolist/lib/powersync.dart index aaf952b4..85838e01 100644 --- a/demos/supabase-todolist/lib/powersync.dart +++ b/demos/supabase-todolist/lib/powersync.dart @@ -3,6 +3,7 @@ import 'package:logging/logging.dart'; import 'package:path/path.dart'; import 'package:path_provider/path_provider.dart'; import 'package:powersync/powersync.dart'; +import 'package:powersync_flutter_demo/database.dart'; import 'package:powersync_flutter_demo/migrations/fts_setup.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; @@ -134,6 +135,7 @@ class SupabaseConnector extends PowerSyncBackendConnector { /// Global reference to the database late final PowerSyncDatabase db; +late final AppDatabase appDb; bool isLoggedIn() { return Supabase.instance.client.auth.currentSession?.accessToken != null; @@ -154,6 +156,7 @@ Future openDatabase() async { db = PowerSyncDatabase( schema: schema, path: await getDatabasePath(), logger: attachedLogger); await db.initialize(); + appDb = AppDatabase(db); await loadSupabase(); diff --git a/demos/supabase-todolist/lib/queries.drift b/demos/supabase-todolist/lib/queries.drift new file mode 100644 index 00000000..cd0d24ce --- /dev/null +++ b/demos/supabase-todolist/lib/queries.drift @@ -0,0 +1,9 @@ +import 'database.dart'; + +listsWithStats WITH ListItemWithStats: + SELECT + self.**, + (SELECT count() FROM todos WHERE list_id = self.id AND completed = TRUE) as completed_count, + (SELECT count() FROM todos WHERE list_id = self.id AND completed = FALSE) as pending_count + FROM lists as self + ORDER BY created_at; diff --git a/demos/supabase-todolist/lib/widgets/fts_search_delegate.dart b/demos/supabase-todolist/lib/widgets/fts_search_delegate.dart index a4ef1dce..80aafa0d 100644 --- a/demos/supabase-todolist/lib/widgets/fts_search_delegate.dart +++ b/demos/supabase-todolist/lib/widgets/fts_search_delegate.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:logging/logging.dart'; +import 'package:powersync_flutter_demo/database.dart'; import 'package:powersync_flutter_demo/fts_helpers.dart' as fts_helpers; -import 'package:powersync_flutter_demo/models/todo_list.dart'; - -import './todo_list_page.dart'; +import 'package:powersync_flutter_demo/powersync.dart'; +import 'package:powersync_flutter_demo/widgets/todo_list_page.dart'; final log = Logger('powersync-supabase'); @@ -69,8 +69,8 @@ class FtsSearchDelegate extends SearchDelegate { return ListTile( title: Text(snapshot.data?[index]['name'] ?? ''), onTap: () async { - TodoList list = - await TodoList.find(snapshot.data![index]['id']); + ListItem list = + await appDb.findList(snapshot.data![index]['id']); navigator.push(MaterialPageRoute( builder: (context) => TodoListPage(list: list), )); diff --git a/demos/supabase-todolist/lib/widgets/list_item.dart b/demos/supabase-todolist/lib/widgets/list_item.dart index 981b382a..6c4650b4 100644 --- a/demos/supabase-todolist/lib/widgets/list_item.dart +++ b/demos/supabase-todolist/lib/widgets/list_item.dart @@ -1,18 +1,19 @@ import 'package:flutter/material.dart'; +import 'package:powersync_flutter_demo/database.dart'; +import 'package:powersync_flutter_demo/powersync.dart'; import './todo_list_page.dart'; -import '../models/todo_list.dart'; class ListItemWidget extends StatelessWidget { ListItemWidget({ required this.list, }) : super(key: ObjectKey(list)); - final TodoList list; + final ListItemWithStats list; Future delete() async { // Server will take care of deleting related todos - await list.delete(); + await appDb.deleteList(list.self); } @override @@ -20,8 +21,8 @@ class ListItemWidget extends StatelessWidget { viewList() { var navigator = Navigator.of(context); - navigator.push( - MaterialPageRoute(builder: (context) => TodoListPage(list: list))); + navigator.push(MaterialPageRoute( + builder: (context) => TodoListPage(list: list.self))); } final subtext = @@ -34,7 +35,7 @@ class ListItemWidget extends StatelessWidget { ListTile( onTap: viewList, leading: const Icon(Icons.list), - title: Text(list.name), + title: Text(list.self.name), subtitle: Text(subtext)), Row( mainAxisAlignment: MainAxisAlignment.end, diff --git a/demos/supabase-todolist/lib/widgets/list_item_dialog.dart b/demos/supabase-todolist/lib/widgets/list_item_dialog.dart index 3fb8c133..3c5e12f8 100644 --- a/demos/supabase-todolist/lib/widgets/list_item_dialog.dart +++ b/demos/supabase-todolist/lib/widgets/list_item_dialog.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; - -import '../models/todo_list.dart'; +import 'package:powersync_flutter_demo/powersync.dart'; class ListItemDialog extends StatefulWidget { const ListItemDialog({super.key}); @@ -23,7 +22,7 @@ class _ListItemDialogState extends State { } Future add() async { - await TodoList.create(_textFieldController.text); + await appDb.createList(_textFieldController.text); } @override diff --git a/demos/supabase-todolist/lib/widgets/lists_page.dart b/demos/supabase-todolist/lib/widgets/lists_page.dart index 933fabc1..ffbbd647 100644 --- a/demos/supabase-todolist/lib/widgets/lists_page.dart +++ b/demos/supabase-todolist/lib/widgets/lists_page.dart @@ -1,11 +1,12 @@ import 'dart:async'; import 'package:flutter/material.dart'; +import 'package:powersync_flutter_demo/database.dart'; +import 'package:powersync_flutter_demo/powersync.dart'; +import '../main.dart'; import './list_item.dart'; import './list_item_dialog.dart'; -import '../main.dart'; -import '../models/todo_list.dart'; void _showAddDialog(BuildContext context) async { return showDialog( @@ -51,7 +52,7 @@ class ListsWidget extends StatefulWidget { } class _ListsWidgetState extends State { - List _data = []; + List _data = []; StreamSubscription? _subscription; _ListsWidgetState(); @@ -59,7 +60,7 @@ class _ListsWidgetState extends State { @override void initState() { super.initState(); - final stream = TodoList.watchListsWithStats(); + final stream = appDb.watchListsWithStats(); _subscription = stream.listen((data) { if (!mounted) { return; diff --git a/demos/supabase-todolist/lib/widgets/todo_item_dialog.dart b/demos/supabase-todolist/lib/widgets/todo_item_dialog.dart index 641abd7f..b624c022 100644 --- a/demos/supabase-todolist/lib/widgets/todo_item_dialog.dart +++ b/demos/supabase-todolist/lib/widgets/todo_item_dialog.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; - -import '../models/todo_list.dart'; +import 'package:powersync_flutter_demo/database.dart'; +import 'package:powersync_flutter_demo/powersync.dart'; class TodoItemDialog extends StatefulWidget { - final TodoList list; + final ListItem list; const TodoItemDialog({super.key, required this.list}); @@ -32,7 +32,7 @@ class _TodoItemDialogState extends State { Future add() async { Navigator.of(context).pop(); - await widget.list.add(_textFieldController.text); + await appDb.addTodo(widget.list, _textFieldController.text); } @override diff --git a/demos/supabase-todolist/lib/widgets/todo_item_widget.dart b/demos/supabase-todolist/lib/widgets/todo_item_widget.dart index efe64fcc..aad890ae 100644 --- a/demos/supabase-todolist/lib/widgets/todo_item_widget.dart +++ b/demos/supabase-todolist/lib/widgets/todo_item_widget.dart @@ -2,8 +2,8 @@ import 'package:flutter/material.dart'; import 'package:powersync_flutter_demo/app_config.dart'; import 'package:powersync_flutter_demo/attachments/photo_widget.dart'; import 'package:powersync_flutter_demo/attachments/queue.dart'; - -import '../models/todo_item.dart'; +import 'package:powersync_flutter_demo/database.dart'; +import 'package:powersync_flutter_demo/powersync.dart'; class TodoItemWidget extends StatelessWidget { TodoItemWidget({ @@ -25,24 +25,24 @@ class TodoItemWidget extends StatelessWidget { if (todo.photoId != null) { attachmentQueue.deletePhoto(todo.photoId!); } - await todo.delete(); + await appDb.deleteTodo(todo); } @override Widget build(BuildContext context) { return ListTile( - onTap: todo.toggle, + onTap: () => appDb.toggleTodo(todo), leading: Checkbox( value: todo.completed, onChanged: (_) { - todo.toggle(); + appDb.toggleTodo(todo); }, ), title: Row( children: [ Expanded( child: Text(todo.description, - style: _getTextStyle(todo.completed))), + style: _getTextStyle(todo.completed == true))), IconButton( iconSize: 30, icon: const Icon( diff --git a/demos/supabase-todolist/lib/widgets/todo_list_page.dart b/demos/supabase-todolist/lib/widgets/todo_list_page.dart index e8cebba5..e88c3dbb 100644 --- a/demos/supabase-todolist/lib/widgets/todo_list_page.dart +++ b/demos/supabase-todolist/lib/widgets/todo_list_page.dart @@ -1,14 +1,14 @@ import 'dart:async'; import 'package:flutter/material.dart'; -import 'package:powersync_flutter_demo/models/todo_item.dart'; +import 'package:powersync_flutter_demo/database.dart'; +import 'package:powersync_flutter_demo/powersync.dart'; import './status_app_bar.dart'; import './todo_item_dialog.dart'; import './todo_item_widget.dart'; -import '../models/todo_list.dart'; -void _showAddDialog(BuildContext context, TodoList list) async { +void _showAddDialog(BuildContext context, ListItem list) async { return showDialog( context: context, barrierDismissible: false, // user must tap button! @@ -19,7 +19,7 @@ void _showAddDialog(BuildContext context, TodoList list) async { } class TodoListPage extends StatelessWidget { - final TodoList list; + final ListItem list; const TodoListPage({super.key, required this.list}); @@ -41,7 +41,7 @@ class TodoListPage extends StatelessWidget { } class TodoListWidget extends StatefulWidget { - final TodoList list; + final ListItem list; const TodoListWidget({super.key, required this.list}); @@ -60,7 +60,7 @@ class TodoListWidgetState extends State { @override void initState() { super.initState(); - final stream = widget.list.watchItems(); + final stream = appDb.watchTodoItems(widget.list); _subscription = stream.listen((data) { if (!mounted) { return; diff --git a/demos/supabase-todolist/pubspec.lock b/demos/supabase-todolist/pubspec.lock index e0615605..0fab7bcb 100644 --- a/demos/supabase-todolist/pubspec.lock +++ b/demos/supabase-todolist/pubspec.lock @@ -1,6 +1,30 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7" + url: "https://pub.dev/" + source: hosted + version: "67.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d" + url: "https://pub.dev/" + source: hosted + version: "6.4.1" + analyzer_plugin: + dependency: transitive + description: + name: analyzer_plugin + sha256: "9661b30b13a685efaee9f02e5d01ed9f2b423bd889d28a304d02d704aee69161" + url: "https://pub.dev/" + source: hosted + version: "0.11.3" app_links: dependency: transitive description: @@ -17,6 +41,14 @@ packages: url: "https://pub.dev/" source: hosted version: "3.4.10" + args: + dependency: transitive + description: + name: args + sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 + url: "https://pub.dev/" + source: hosted + version: "2.4.2" async: dependency: transitive description: @@ -33,6 +65,70 @@ packages: url: "https://pub.dev/" source: hosted version: "2.1.1" + build: + dependency: transitive + description: + name: build + sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" + url: "https://pub.dev/" + source: hosted + version: "2.4.1" + build_config: + dependency: transitive + description: + name: build_config + sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1 + url: "https://pub.dev/" + source: hosted + version: "1.1.1" + build_daemon: + dependency: transitive + description: + name: build_daemon + sha256: "0343061a33da9c5810b2d6cee51945127d8f4c060b7fbdd9d54917f0a3feaaa1" + url: "https://pub.dev/" + source: hosted + version: "4.0.1" + build_resolvers: + dependency: transitive + description: + name: build_resolvers + sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" + url: "https://pub.dev/" + source: hosted + version: "2.4.2" + build_runner: + dependency: "direct dev" + description: + name: build_runner + sha256: "581bacf68f89ec8792f5e5a0b2c4decd1c948e97ce659dc783688c8a88fbec21" + url: "https://pub.dev/" + source: hosted + version: "2.4.8" + build_runner_core: + dependency: transitive + description: + name: build_runner_core + sha256: "4ae8ffe5ac758da294ecf1802f2aff01558d8b1b00616aa7538ea9a8a5d50799" + url: "https://pub.dev/" + source: hosted + version: "7.3.0" + built_collection: + dependency: transitive + description: + name: built_collection + sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" + url: "https://pub.dev/" + source: hosted + version: "5.1.1" + built_value: + dependency: transitive + description: + name: built_value + sha256: fedde275e0a6b798c3296963c5cd224e3e1b55d0e478d5b7e65e6b540f363a0e + url: "https://pub.dev/" + source: hosted + version: "8.9.1" camera: dependency: "direct main" description: @@ -81,6 +177,30 @@ packages: url: "https://pub.dev/" source: hosted version: "1.3.0" + charcode: + dependency: transitive + description: + name: charcode + sha256: fb98c0f6d12c920a02ee2d998da788bca066ca5f148492b7085ee23372b12306 + url: "https://pub.dev/" + source: hosted + version: "1.3.1" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff + url: "https://pub.dev/" + source: hosted + version: "2.0.3" + cli_util: + dependency: transitive + description: + name: cli_util + sha256: c05b7406fdabc7a49a3929d4af76bcaccbbffcbcdcf185b082e1ae07da323d19 + url: "https://pub.dev/" + source: hosted + version: "0.4.1" clock: dependency: transitive description: @@ -89,6 +209,14 @@ packages: url: "https://pub.dev/" source: hosted version: "1.1.1" + code_builder: + dependency: transitive + description: + name: code_builder + sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37 + url: "https://pub.dev/" + source: hosted + version: "4.10.0" collection: dependency: transitive description: @@ -121,6 +249,38 @@ packages: url: "https://pub.dev/" source: hosted version: "3.0.3" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: "40ae61a5d43feea6d24bd22c0537a6629db858963b99b4bc1c3db80676f32368" + url: "https://pub.dev/" + source: hosted + version: "2.3.4" + drift: + dependency: "direct main" + description: + name: drift + sha256: b50a8342c6ddf05be53bda1d246404cbad101b64dc73e8d6d1ac1090d119b4e2 + url: "https://pub.dev/" + source: hosted + version: "2.15.0" + drift_dev: + dependency: "direct dev" + description: + name: drift_dev + sha256: c037d9431b6f8dc633652b1469e5f53aaec6e4eb405ed29dd232fa888ef10d88 + url: "https://pub.dev/" + source: hosted + version: "2.15.0" + drift_sqlite_async: + dependency: "direct main" + description: + name: drift_sqlite_async + sha256: fb0f7681e1f314326874193d2b0c998def00f97d44c29d66239623e7a32b79fd + url: "https://pub.dev/" + source: hosted + version: "0.1.0-alpha.1" fake_async: dependency: transitive description: @@ -184,6 +344,14 @@ packages: description: flutter source: sdk version: "0.0.0" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612" + url: "https://pub.dev/" + source: hosted + version: "3.2.0" functions_client: dependency: transitive description: @@ -192,6 +360,14 @@ packages: url: "https://pub.dev/" source: hosted version: "2.0.0" + glob: + dependency: transitive + description: + name: glob + sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" + url: "https://pub.dev/" + source: hosted + version: "2.1.2" gotrue: dependency: transitive description: @@ -200,6 +376,14 @@ packages: url: "https://pub.dev/" source: hosted version: "2.5.1" + graphs: + dependency: transitive + description: + name: graphs + sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19 + url: "https://pub.dev/" + source: hosted + version: "2.3.1" gtk: dependency: transitive description: @@ -216,6 +400,14 @@ packages: url: "https://pub.dev/" source: hosted version: "1.2.1" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" + url: "https://pub.dev/" + source: hosted + version: "3.2.1" http_parser: dependency: transitive description: @@ -232,6 +424,14 @@ packages: url: "https://pub.dev/" source: hosted version: "4.1.7" + io: + dependency: transitive + description: + name: io + sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" + url: "https://pub.dev/" + source: hosted + version: "1.0.4" js: dependency: transitive description: @@ -240,6 +440,14 @@ packages: url: "https://pub.dev/" source: hosted version: "0.6.7" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 + url: "https://pub.dev/" + source: hosted + version: "4.8.1" jwt_decode: dependency: transitive description: @@ -320,6 +528,14 @@ packages: url: "https://pub.dev/" source: hosted version: "1.0.5" + package_config: + dependency: transitive + description: + name: package_config + sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" + url: "https://pub.dev/" + source: hosted + version: "2.1.0" path: dependency: "direct main" description: @@ -408,6 +624,14 @@ packages: url: "https://pub.dev/" source: hosted version: "3.7.4" + pool: + dependency: transitive + description: + name: pool + sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" + url: "https://pub.dev/" + source: hosted + version: "1.5.1" postgrest: dependency: transitive description: @@ -430,6 +654,22 @@ packages: relative: true source: path version: "0.2.0" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" + url: "https://pub.dev/" + source: hosted + version: "2.1.4" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367 + url: "https://pub.dev/" + source: hosted + version: "1.2.3" realtime_client: dependency: transitive description: @@ -438,6 +678,14 @@ packages: url: "https://pub.dev/" source: hosted version: "2.0.1" + recase: + dependency: transitive + description: + name: recase + sha256: e4eb4ec2dcdee52dcf99cb4ceabaffc631d7424ee55e56f280bc039737f89213 + url: "https://pub.dev/" + source: hosted + version: "4.1.0" retry: dependency: transitive description: @@ -510,11 +758,35 @@ packages: url: "https://pub.dev/" source: hosted version: "2.3.2" + shelf: + dependency: transitive + description: + name: shelf + sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 + url: "https://pub.dev/" + source: hosted + version: "1.4.1" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1" + url: "https://pub.dev/" + source: hosted + version: "1.0.4" sky_engine: dependency: transitive description: flutter source: sdk version: "0.0.99" + source_gen: + dependency: transitive + description: + name: source_gen + sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" + url: "https://pub.dev/" + source: hosted + version: "1.5.0" source_span: dependency: transitive description: @@ -555,6 +827,14 @@ packages: url: "https://pub.dev/" source: hosted version: "0.6.0" + sqlparser: + dependency: transitive + description: + name: sqlparser + sha256: dc384bb1f56d1384ce078edb5ff8247976abdab79d0c83e437210c85f06ecb61 + url: "https://pub.dev/" + source: hosted + version: "0.34.0" stack_trace: dependency: transitive description: @@ -627,6 +907,14 @@ packages: url: "https://pub.dev/" source: hosted version: "0.6.1" + timing: + dependency: transitive + description: + name: timing + sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32" + url: "https://pub.dev/" + source: hosted + version: "1.0.1" typed_data: dependency: transitive description: @@ -723,6 +1011,14 @@ packages: url: "https://pub.dev/" source: hosted version: "13.0.0" + watcher: + dependency: transitive + description: + name: watcher + sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" + url: "https://pub.dev/" + source: hosted + version: "1.1.0" web: dependency: transitive description: @@ -763,6 +1059,14 @@ packages: url: "https://pub.dev/" source: hosted version: "6.5.0" + yaml: + dependency: transitive + description: + name: yaml + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" + url: "https://pub.dev/" + source: hosted + version: "3.1.2" yet_another_json_isolate: dependency: transitive description: diff --git a/demos/supabase-todolist/pubspec.yaml b/demos/supabase-todolist/pubspec.yaml index f263e09f..4ecb7785 100644 --- a/demos/supabase-todolist/pubspec.yaml +++ b/demos/supabase-todolist/pubspec.yaml @@ -20,8 +20,13 @@ dependencies: sqlite_async: ^0.6.0 camera: ^0.10.5+7 image: ^4.1.3 + drift: ^2.15.0 + drift_sqlite_async: ^0.1.0-alpha.1 dev_dependencies: + drift_dev: ^2.15.0 + build_runner: ^2.4.8 + flutter_test: sdk: flutter