mirror of
https://github.com/wger-project/flutter.git
synced 2026-02-18 00:17:48 +01:00
powersync prototype (WIP)
This commit is contained in:
60
lib/api_client.dart
Normal file
60
lib/api_client.dart
Normal file
@@ -0,0 +1,60 @@
|
||||
import 'dart:convert';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
final log = Logger('powersync-test');
|
||||
|
||||
class ApiClient {
|
||||
final String baseUrl;
|
||||
|
||||
ApiClient(this.baseUrl);
|
||||
|
||||
Future<Map<String, dynamic>> authenticate(String username, String password) async {
|
||||
final response = await http.post(
|
||||
Uri.parse('$baseUrl/api/auth/'),
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: json.encode({'username': username, 'password': password}),
|
||||
);
|
||||
if (response.statusCode == 200) {
|
||||
return json.decode(response.body);
|
||||
} else {
|
||||
throw Exception('Failed to authenticate');
|
||||
}
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> getToken(String userId) async {
|
||||
final response = await http.get(
|
||||
Uri.parse('$baseUrl/api/get_powersync_token/'),
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
);
|
||||
if (response.statusCode == 200) {
|
||||
return json.decode(response.body);
|
||||
} else {
|
||||
throw Exception('Failed to fetch token');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> upsert(Map<String, dynamic> record) async {
|
||||
await http.put(
|
||||
Uri.parse('$baseUrl/api/upload_data/'),
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: json.encode(record),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> update(Map<String, dynamic> record) async {
|
||||
await http.patch(
|
||||
Uri.parse('$baseUrl/api/upload_data/'),
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: json.encode(record),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> delete(Map<String, dynamic> record) async {
|
||||
await http.delete(
|
||||
Uri.parse('$baseUrl/api/upload_data/'),
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: json.encode(record),
|
||||
);
|
||||
}
|
||||
}
|
||||
4
lib/app_config.dart
Normal file
4
lib/app_config.dart
Normal file
@@ -0,0 +1,4 @@
|
||||
class AppConfig {
|
||||
static const String djangoUrl = 'http://192.168.2.223:6061';
|
||||
static const String powersyncUrl = 'http://192.168.2.223:8080';
|
||||
}
|
||||
@@ -16,10 +16,12 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:wger/core/locator.dart';
|
||||
import 'package:wger/powersync.dart';
|
||||
import 'package:wger/providers/add_exercise.dart';
|
||||
import 'package:wger/providers/base_provider.dart';
|
||||
import 'package:wger/providers/body_weight.dart';
|
||||
@@ -52,15 +54,34 @@ import 'package:wger/screens/workout_plans_screen.dart';
|
||||
import 'package:wger/theme/theme.dart';
|
||||
import 'package:wger/widgets/core/about.dart';
|
||||
import 'package:wger/widgets/core/settings.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
import 'providers/auth.dart';
|
||||
|
||||
void main() async {
|
||||
//zx.setLogEnabled(kDebugMode);
|
||||
Logger.root.level = Level.INFO;
|
||||
Logger.root.onRecord.listen((record) {
|
||||
if (kDebugMode) {
|
||||
print('[${record.loggerName}] ${record.level.name}: ${record.time}: ${record.message}');
|
||||
|
||||
if (record.error != null) {
|
||||
print(record.error);
|
||||
}
|
||||
if (record.stackTrace != null) {
|
||||
print(record.stackTrace);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Needs to be called before runApp
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
await openDatabase();
|
||||
|
||||
final loggedIn = await isLoggedIn();
|
||||
print('is logged in $loggedIn');
|
||||
|
||||
// Locator to initialize exerciseDB
|
||||
await ServiceLocator().configure();
|
||||
// Application
|
||||
|
||||
54
lib/models/schema.dart
Normal file
54
lib/models/schema.dart
Normal file
@@ -0,0 +1,54 @@
|
||||
import 'package:powersync/powersync.dart';
|
||||
|
||||
const todosTable = 'todos';
|
||||
|
||||
// these are the same ones as in postgres, except for 'id'
|
||||
Schema schema = const Schema(([
|
||||
Table(todosTable, [
|
||||
Column.text('list_id'),
|
||||
Column.text('created_at'),
|
||||
Column.text('completed_at'),
|
||||
Column.text('description'),
|
||||
Column.integer('completed'),
|
||||
Column.text('created_by'),
|
||||
Column.text('completed_by'),
|
||||
], indexes: [
|
||||
// Index to allow efficient lookup within a list
|
||||
Index('list', [IndexedColumn('list_id')])
|
||||
]),
|
||||
Table('lists',
|
||||
[Column.text('created_at'), Column.text('name'), Column.text('owner_id')])
|
||||
]));
|
||||
|
||||
// post gres columns:
|
||||
// todos:
|
||||
// id | created_at | completed_at | description | completed | created_by | completed_by | list_id
|
||||
// lists:
|
||||
// id | created_at | name | owner_id
|
||||
|
||||
// diagnostics app:
|
||||
/*
|
||||
new Schema([
|
||||
new Table({
|
||||
name: 'lists', // same as flutter
|
||||
columns: [
|
||||
new Column({ name: 'created_at', type: ColumnType.TEXT }),
|
||||
new Column({ name: 'name', type: ColumnType.TEXT }),
|
||||
new Column({ name: 'owner_id', type: ColumnType.TEXT })
|
||||
]
|
||||
}),
|
||||
new Table({
|
||||
name: 'todos', // misses completed_at and completed_by, until these actually get populated with something
|
||||
columns: [
|
||||
new Column({ name: 'created_at', type: ColumnType.TEXT }),
|
||||
new Column({ name: 'description', type: ColumnType.TEXT }),
|
||||
new Column({ name: 'completed', type: ColumnType.INTEGER }),
|
||||
new Column({ name: 'created_by', type: ColumnType.TEXT }),
|
||||
new Column({ name: 'list_id', type: ColumnType.TEXT })
|
||||
]
|
||||
})
|
||||
])
|
||||
|
||||
Column.text('completed_at'),
|
||||
Column.text('completed_by'),
|
||||
*/
|
||||
50
lib/models/todo_item.dart
Normal file
50
lib/models/todo_item.dart
Normal file
@@ -0,0 +1,50 @@
|
||||
import 'package:wger/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.
|
||||
/// confirm how the watch works. this seems like a weird pattern
|
||||
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<void> 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 = ?',
|
||||
[await getUserId(), id]);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> delete() async {
|
||||
await db.execute('DELETE FROM $todosTable WHERE id = ?', [id]);
|
||||
}
|
||||
|
||||
static Future<void> addPhoto(String photoId, String id) async {
|
||||
await db.execute('UPDATE $todosTable SET photo_id = ? WHERE id = ?', [photoId, id]);
|
||||
}
|
||||
}
|
||||
96
lib/models/todo_list.dart
Normal file
96
lib/models/todo_list.dart
Normal file
@@ -0,0 +1,96 @@
|
||||
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<List<TodoList>> 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<List<TodoList>> 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<TodoList> create(String name) async {
|
||||
final results = await db.execute('''
|
||||
INSERT INTO
|
||||
lists(id, created_at, name, owner_id)
|
||||
VALUES(uuid(), datetime(), ?, ?)
|
||||
RETURNING *
|
||||
''', [name, await getUserId()]);
|
||||
return TodoList.fromRow(results.first);
|
||||
}
|
||||
|
||||
/// Watch items within this list.
|
||||
Stream<List<TodoItem>> 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<void> delete() async {
|
||||
await db.execute('DELETE FROM lists WHERE id = ?', [id]);
|
||||
}
|
||||
|
||||
/// Find list item.
|
||||
static Future<TodoList> 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<TodoItem> 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, await getUserId()]);
|
||||
return TodoItem.fromRow(results.first);
|
||||
}
|
||||
}
|
||||
132
lib/powersync.dart
Normal file
132
lib/powersync.dart
Normal file
@@ -0,0 +1,132 @@
|
||||
// This file performs setup of the PowerSync database
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:powersync/powersync.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:wger/api_client.dart';
|
||||
|
||||
import './app_config.dart';
|
||||
import './models/schema.dart';
|
||||
|
||||
final log = Logger('powersync-django');
|
||||
|
||||
/// Postgres Response codes that we cannot recover from by retrying.
|
||||
final List<RegExp> fatalResponseCodes = [
|
||||
// Class 22 — Data Exception
|
||||
// Examples include data type mismatch.
|
||||
RegExp(r'^22...$'),
|
||||
// Class 23 — Integrity Constraint Violation.
|
||||
// Examples include NOT NULL, FOREIGN KEY and UNIQUE violations.
|
||||
RegExp(r'^23...$'),
|
||||
// INSUFFICIENT PRIVILEGE - typically a row-level security violation
|
||||
RegExp(r'^42501$'),
|
||||
];
|
||||
|
||||
class DjangoConnector extends PowerSyncBackendConnector {
|
||||
PowerSyncDatabase db;
|
||||
|
||||
DjangoConnector(this.db);
|
||||
|
||||
final ApiClient apiClient = ApiClient(AppConfig.djangoUrl);
|
||||
|
||||
/// Get a token to authenticate against the PowerSync instance.
|
||||
@override
|
||||
Future<PowerSyncCredentials?> fetchCredentials() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final userId = prefs.getString('id');
|
||||
if (userId == null) {
|
||||
throw Exception('User does not have session');
|
||||
}
|
||||
// Somewhat contrived to illustrate usage, see auth docs here:
|
||||
// https://docs.powersync.com/usage/installation/authentication-setup/custom
|
||||
final session = await apiClient.getToken(userId);
|
||||
return PowerSyncCredentials(endpoint: AppConfig.powersyncUrl, token: session['token']);
|
||||
}
|
||||
|
||||
// Upload pending changes to Postgres via Django backend
|
||||
// this is generic. on the django side we inspect the request and do model-specific operations
|
||||
// would it make sense to do api calls here specific to the relevant model? (e.g. put to a todo-specific endpoint)
|
||||
@override
|
||||
Future<void> uploadData(PowerSyncDatabase database) async {
|
||||
final transaction = await database.getNextCrudTransaction();
|
||||
|
||||
if (transaction == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
for (var op in transaction.crud) {
|
||||
final record = {
|
||||
'table': op.table,
|
||||
'data': {'id': op.id, ...?op.opData},
|
||||
};
|
||||
|
||||
switch (op.op) {
|
||||
case UpdateType.put:
|
||||
await apiClient.upsert(record);
|
||||
break;
|
||||
case UpdateType.patch:
|
||||
await apiClient.update(record);
|
||||
break;
|
||||
case UpdateType.delete:
|
||||
await apiClient.delete(record);
|
||||
break;
|
||||
}
|
||||
}
|
||||
await transaction.complete();
|
||||
} on Exception catch (e) {
|
||||
log.severe('Error uploading data', e);
|
||||
// Error may be retryable - e.g. network error or temporary server error.
|
||||
// Throwing an error here causes this call to be retried after a delay.
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Global reference to the database
|
||||
late final PowerSyncDatabase db;
|
||||
|
||||
// Hacky flag to ensure the database is only initialized once, better to do this with listeners
|
||||
bool _dbInitialized = false;
|
||||
|
||||
Future<bool> isLoggedIn() async {
|
||||
final prefs = await SharedPreferences.getInstance(); // Initialize SharedPreferences
|
||||
final userId = prefs.getString('id');
|
||||
return userId != null;
|
||||
}
|
||||
|
||||
Future<String> getDatabasePath() async {
|
||||
final dir = await getApplicationSupportDirectory();
|
||||
return join(dir.path, 'powersync-demo.db');
|
||||
}
|
||||
|
||||
// opens the database and connects if logged in
|
||||
Future<void> openDatabase() async {
|
||||
// Open the local database
|
||||
if (!_dbInitialized) {
|
||||
db = PowerSyncDatabase(schema: schema, path: await getDatabasePath(), logger: attachedLogger);
|
||||
await db.initialize();
|
||||
_dbInitialized = true;
|
||||
}
|
||||
|
||||
DjangoConnector? currentConnector;
|
||||
|
||||
if (await isLoggedIn()) {
|
||||
// If the user is already logged in, connect immediately.
|
||||
// Otherwise, connect once logged in.
|
||||
currentConnector = DjangoConnector(db);
|
||||
db.connect(connector: currentConnector);
|
||||
}
|
||||
}
|
||||
|
||||
/// Explicit sign out - clear database and log out.
|
||||
Future<void> logout() async {
|
||||
await db.disconnectAndClear();
|
||||
}
|
||||
|
||||
/// id of the user currently logged in
|
||||
Future<String?> getUserId() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
return prefs.getString('id');
|
||||
}
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <file_selector_linux/file_selector_plugin.h>
|
||||
#include <powersync_flutter_libs/powersync_flutter_libs_plugin.h>
|
||||
#include <sqlite3_flutter_libs/sqlite3_flutter_libs_plugin.h>
|
||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||
|
||||
@@ -14,6 +15,9 @@ void fl_register_plugins(FlPluginRegistry* registry) {
|
||||
g_autoptr(FlPluginRegistrar) file_selector_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin");
|
||||
file_selector_plugin_register_with_registrar(file_selector_linux_registrar);
|
||||
g_autoptr(FlPluginRegistrar) powersync_flutter_libs_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "PowersyncFlutterLibsPlugin");
|
||||
powersync_flutter_libs_plugin_register_with_registrar(powersync_flutter_libs_registrar);
|
||||
g_autoptr(FlPluginRegistrar) sqlite3_flutter_libs_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "Sqlite3FlutterLibsPlugin");
|
||||
sqlite3_flutter_libs_plugin_register_with_registrar(sqlite3_flutter_libs_registrar);
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
file_selector_linux
|
||||
powersync_flutter_libs
|
||||
sqlite3_flutter_libs
|
||||
url_launcher_linux
|
||||
)
|
||||
|
||||
@@ -8,6 +8,7 @@ import Foundation
|
||||
import file_selector_macos
|
||||
import package_info_plus
|
||||
import path_provider_foundation
|
||||
import powersync_flutter_libs
|
||||
import rive_common
|
||||
import shared_preferences_foundation
|
||||
import sqlite3_flutter_libs
|
||||
@@ -18,6 +19,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
|
||||
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
|
||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||
PowersyncFlutterLibsPlugin.register(with: registry.registrar(forPlugin: "PowersyncFlutterLibsPlugin"))
|
||||
RivePlugin.register(with: registry.registrar(forPlugin: "RivePlugin"))
|
||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||
Sqlite3FlutterLibsPlugin.register(with: registry.registrar(forPlugin: "Sqlite3FlutterLibsPlugin"))
|
||||
|
||||
82
pubspec.lock
82
pubspec.lock
@@ -329,6 +329,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.1"
|
||||
fetch_api:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fetch_api
|
||||
sha256: "97f46c25b480aad74f7cc2ad7ccba2c5c6f08d008e68f95c1077286ce243d0e6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
fetch_client:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fetch_client
|
||||
sha256: "9666ee14536778474072245ed5cba07db81ae8eb5de3b7bf4a2d1e2c49696092"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.2"
|
||||
ffi:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -821,7 +837,7 @@ packages:
|
||||
source: hosted
|
||||
version: "1.0.2"
|
||||
logging:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: logging
|
||||
sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340"
|
||||
@@ -892,6 +908,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.3"
|
||||
mutex:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: mutex
|
||||
sha256: "8827da25de792088eb33e572115a5eb0d61d61a3c01acbc8bcbe76ed78f1a1f2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
nested:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1060,6 +1084,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.1"
|
||||
powersync:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: powersync
|
||||
sha256: c6975007493617fdfc5945c3fab24ea2e6999ae300dd4d19d739713a4f2bcd96
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.6.3"
|
||||
powersync_flutter_libs:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: powersync_flutter_libs
|
||||
sha256: "449063aa4956c6be215ea7dfb9cc61255188e82cf7bc3f75621796fcc6615b70"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.0"
|
||||
process:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1233,6 +1273,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.10.0"
|
||||
sprintf:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sprintf
|
||||
sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.0"
|
||||
sqlite3:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1249,6 +1297,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.24"
|
||||
sqlite3_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqlite3_web
|
||||
sha256: "51fec34757577841cc72d79086067e3651c434669d5af557a5c106787198a76f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.2-wip"
|
||||
sqlite_async:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: sqlite_async
|
||||
sha256: "79e636c857ed43f6cd5e5be72b36967a29f785daa63ff5b078bd34f74f44cb54"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.8.1"
|
||||
sqlparser:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1337,6 +1401,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.2"
|
||||
universal_io:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: universal_io
|
||||
sha256: "1722b2dcc462b4b2f3ee7d188dad008b6eb4c40bbd03a3de451d82c78bba9aad"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.2"
|
||||
url_launcher:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -1401,6 +1473,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.1"
|
||||
uuid:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: uuid
|
||||
sha256: "83d37c7ad7aaf9aa8e275490669535c8080377cfa7a7004c24dfac53afffaa90"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.4.2"
|
||||
vector_graphics:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -33,6 +33,7 @@ dependencies:
|
||||
sdk: flutter
|
||||
|
||||
android_metadata: ^0.2.1
|
||||
powersync: ^1.5.5
|
||||
collection: ^1.17.0
|
||||
cupertino_icons: ^1.0.8
|
||||
equatable: ^2.0.5
|
||||
@@ -70,6 +71,8 @@ dependencies:
|
||||
freezed_annotation: ^2.4.1
|
||||
clock: ^1.1.1
|
||||
flutter_svg_icons: ^0.0.1
|
||||
sqlite_async: ^0.8.1
|
||||
logging: ^1.2.0
|
||||
|
||||
dependency_overrides:
|
||||
intl: ^0.19.0
|
||||
|
||||
Reference in New Issue
Block a user