mirror of
https://github.com/Nioux/AideDeJeu.git
synced 2025-10-29 06:26:02 +00:00
Début ajout sembast
This commit is contained in:
parent
f93587c745
commit
2d826d5248
8 changed files with 305 additions and 159 deletions
|
|
@ -1,11 +1,14 @@
|
|||
import 'package:aidedejeu_flutter/blocs/player_character/player_character_event.dart';
|
||||
import 'package:aidedejeu_flutter/blocs/player_character/player_character_state.dart';
|
||||
import 'package:aidedejeu_flutter/database.dart';
|
||||
import 'package:aidedejeu_flutter/databases/database.dart';
|
||||
import 'package:aidedejeu_flutter/databases/database_sqflite.dart';
|
||||
import 'package:bloc/bloc.dart';
|
||||
|
||||
class PlayerCharacterBloc
|
||||
extends Bloc<PlayerCharacterEvent, PlayerCharacterState> {
|
||||
|
||||
BaseDB _db = SqfliteDB.instance;
|
||||
|
||||
@override
|
||||
PlayerCharacterState get initialState => PlayerCharacterState();
|
||||
|
||||
|
|
@ -27,7 +30,7 @@ class PlayerCharacterBloc
|
|||
}
|
||||
Stream<PlayerCharacterState> _mapRaceEventToState(
|
||||
RaceEvent event) async* {
|
||||
var subRaces = await loadSubRaces(event.item);
|
||||
var subRaces = await _db.loadSubRaces(event.item);
|
||||
yield state.copyWithClean(race: event.item, subRaces: subRaces);
|
||||
}
|
||||
|
||||
|
|
@ -37,7 +40,7 @@ class PlayerCharacterBloc
|
|||
}
|
||||
Stream<PlayerCharacterState> _mapBackgroundEventToState(
|
||||
BackgroundEvent event) async* {
|
||||
var subBackgrounds = await loadSubBackgrounds(event.item);
|
||||
var subBackgrounds = await _db.loadSubBackgrounds(event.item);
|
||||
yield state.copyWithClean(background: event.item,subBackgrounds: subBackgrounds);
|
||||
}
|
||||
Stream<PlayerCharacterState> _mapSubBackgroundEventToState(
|
||||
|
|
@ -46,8 +49,8 @@ class PlayerCharacterBloc
|
|||
}
|
||||
Stream<PlayerCharacterState> _mapLoadEventToState(
|
||||
LoadEvent event) async* {
|
||||
var races = await loadRaces();
|
||||
var backgrounds = await loadBackgrounds();
|
||||
var races = await _db.loadRaces();
|
||||
var backgrounds = await _db.loadBackgrounds();
|
||||
yield state.copyWith(races: races, backgrounds: backgrounds); // state;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,143 +0,0 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:aidedejeu_flutter/models/items.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
|
||||
import 'models/filters.dart';
|
||||
|
||||
Database _database;
|
||||
|
||||
Future<Database> get database async {
|
||||
if (_database != null) return _database;
|
||||
_database = await getDatabaseInstance();
|
||||
return _database;
|
||||
}
|
||||
|
||||
Future<Database> getDatabaseInstance() async {
|
||||
var databasesPath = await getDatabasesPath();
|
||||
var path = join(databasesPath, "library.db");
|
||||
|
||||
var exists = await databaseExists(path);
|
||||
exists = false;
|
||||
if (!exists) {
|
||||
print("Creating new copy from asset");
|
||||
|
||||
try {
|
||||
await Directory(dirname(path)).create(recursive: true);
|
||||
} catch (_) {}
|
||||
|
||||
ByteData data = await rootBundle.load(join("assets", "library.db"));
|
||||
List<int> bytes =
|
||||
data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes);
|
||||
|
||||
await File(path).writeAsBytes(bytes, flush: true);
|
||||
} else {
|
||||
print("Opening existing database");
|
||||
}
|
||||
|
||||
return await openDatabase(path, readOnly: true);
|
||||
}
|
||||
|
||||
Future<Item> getItemWithId(String id) async {
|
||||
print("getItemWithId " + id);
|
||||
final db = await database;
|
||||
var response = await db.query(
|
||||
"Items",
|
||||
where: "Id = ? OR RootId = ?",
|
||||
whereArgs: [id, id]
|
||||
);
|
||||
if (response.isEmpty) {
|
||||
print("Id not found");
|
||||
}
|
||||
return response.isNotEmpty ? itemFromMap(response.first) : null;
|
||||
}
|
||||
|
||||
Future<Item> loadChildrenItems(Item item, List<Filter> filters) async {
|
||||
print("getChildrenItems " + (item?.itemType ?? ""));
|
||||
if (item.itemType.endsWith("Items")) {
|
||||
String itemType =
|
||||
item.itemType.substring(0, item.itemType.length - 1);
|
||||
String family = "";
|
||||
if (item is FilteredItems) {
|
||||
family = item.family ?? "";
|
||||
}
|
||||
String whereFilter = "";
|
||||
if(filters != null) {
|
||||
filters.forEach((filter) {
|
||||
if(filter.selectedValues.isNotEmpty) {
|
||||
whereFilter = " AND (${filter.name} LIKE '%" + filter.selectedValues.join("%' OR ${filter.name} LIKE '%") + "%')";
|
||||
}
|
||||
if(filter.rangeValues != null && (filter.rangeValues.start > 0 || filter.rangeValues.end < filter.values.length - 1) ) {
|
||||
whereFilter = " AND ([${filter.name}] BETWEEN '${filter.values[filter.rangeValues.start.round()]}' AND '${filter.values[filter.rangeValues.end.round()]}')";
|
||||
}
|
||||
});
|
||||
}
|
||||
print(whereFilter);
|
||||
final db = await database;
|
||||
var response = await db.query(
|
||||
"Items",
|
||||
where: "ItemType = ? AND Family = ?" + whereFilter,
|
||||
whereArgs: [itemType, family],
|
||||
orderBy: "NormalizedName"
|
||||
);
|
||||
if (response.isEmpty) {
|
||||
print("Children not found");
|
||||
}
|
||||
item.children = response.isNotEmpty
|
||||
? itemsFromMapList(response)
|
||||
: null;
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
Future<List<RaceItem>> loadRaces() async {
|
||||
final db = await database;
|
||||
var response = await db.query(
|
||||
"Items",
|
||||
where: "ItemType = 'RaceItem'",
|
||||
orderBy: "NormalizedName"
|
||||
);
|
||||
if (response.isNotEmpty) {
|
||||
return itemsFromMapList(response);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<List<SubRaceItem>> loadSubRaces(RaceItem race) async {
|
||||
final db = await database;
|
||||
var response = await db.query(
|
||||
"Items",
|
||||
where: "ItemType = 'SubRaceItem' AND ParentLink = ?",
|
||||
whereArgs: [race.id],
|
||||
orderBy: "NormalizedName"
|
||||
);
|
||||
if (response.isNotEmpty) {
|
||||
return itemsFromMapList(response);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<List<T>> loadTypedItems<T extends Item>({String itemType, Item item}) async {
|
||||
final db = await database;
|
||||
var response = await db.query(
|
||||
"Items",
|
||||
where: "ItemType = ?" + (item != null ? " AND ParentLink = ?" : ""),
|
||||
whereArgs: item != null ? [itemType, item.id] : [itemType],
|
||||
orderBy: "NormalizedName"
|
||||
);
|
||||
if (response.isNotEmpty) {
|
||||
return itemsFromMapList(response);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<List<BackgroundItem>> loadBackgrounds() async {
|
||||
return loadTypedItems<BackgroundItem>(itemType: "BackgroundItem");
|
||||
}
|
||||
|
||||
Future<List<SubBackgroundItem>> loadSubBackgrounds(Item item) async {
|
||||
return loadTypedItems<SubBackgroundItem>(itemType: "SubBackgroundItem", item: item);
|
||||
}
|
||||
|
||||
18
aidedejeu_flutter/lib/databases/database.dart
Normal file
18
aidedejeu_flutter/lib/databases/database.dart
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import 'package:aidedejeu_flutter/models/filters.dart';
|
||||
import 'package:aidedejeu_flutter/models/items.dart';
|
||||
|
||||
abstract class BaseDB {
|
||||
Future<Item> getItemWithId(String id);
|
||||
|
||||
Future<Item> loadChildrenItems(Item item, List<Filter> filters);
|
||||
|
||||
Future<List<RaceItem>> loadRaces();
|
||||
|
||||
Future<List<SubRaceItem>> loadSubRaces(RaceItem race);
|
||||
|
||||
Future<List<T>> loadTypedItems<T extends Item>({String itemType, Item item});
|
||||
|
||||
Future<List<BackgroundItem>> loadBackgrounds();
|
||||
|
||||
Future<List<SubBackgroundItem>> loadSubBackgrounds(Item item);
|
||||
}
|
||||
75
aidedejeu_flutter/lib/databases/database_sembast.dart
Normal file
75
aidedejeu_flutter/lib/databases/database_sembast.dart
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
import 'package:aidedejeu_flutter/databases/database.dart';
|
||||
import 'package:aidedejeu_flutter/models/filters.dart' as Filters;
|
||||
import 'package:aidedejeu_flutter/models/items.dart';
|
||||
import 'package:sembast/sembast.dart';
|
||||
import 'package:sembast/sembast_io.dart';
|
||||
import 'package:sembast_web/sembast_web.dart';
|
||||
|
||||
class SembastDB extends BaseDB {
|
||||
static SembastDB _instance;
|
||||
static SembastDB get instance {
|
||||
if(_instance == null) {
|
||||
_instance = SembastDB();
|
||||
}
|
||||
return _instance;
|
||||
}
|
||||
|
||||
Database _database;
|
||||
|
||||
Future<Database> get database async {
|
||||
if (_database != null) return _database;
|
||||
_database = await getDatabaseInstance();
|
||||
return _database;
|
||||
}
|
||||
|
||||
Future<Database> getDatabaseInstance() async {
|
||||
// File path to a file in the current directory
|
||||
String dbPath = 'library_sembast.db';
|
||||
DatabaseFactory dbFactory = databaseFactoryIo;
|
||||
|
||||
// We use the database factory to open the database
|
||||
return await dbFactory.openDatabase(dbPath);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Item> getItemWithId(String id) {
|
||||
// TODO: implement getItemWithId
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<BackgroundItem>> loadBackgrounds() {
|
||||
// TODO: implement loadBackgrounds
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Item> loadChildrenItems(Item item, List<Filters.Filter> filters) {
|
||||
// TODO: implement loadChildrenItems
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<RaceItem>> loadRaces() {
|
||||
// TODO: implement loadRaces
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<SubBackgroundItem>> loadSubBackgrounds(Item item) {
|
||||
// TODO: implement loadSubBackgrounds
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<SubRaceItem>> loadSubRaces(RaceItem race) {
|
||||
// TODO: implement loadSubRaces
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<T>> loadTypedItems<T extends Item>({String itemType, Item item}) {
|
||||
// TODO: implement loadTypedItems
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
160
aidedejeu_flutter/lib/databases/database_sqflite.dart
Normal file
160
aidedejeu_flutter/lib/databases/database_sqflite.dart
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:aidedejeu_flutter/databases/database.dart';
|
||||
import 'package:aidedejeu_flutter/models/filters.dart' as Filters;
|
||||
import 'package:aidedejeu_flutter/models/items.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
|
||||
class SqfliteDB extends BaseDB {
|
||||
static SqfliteDB _instance;
|
||||
static SqfliteDB get instance {
|
||||
if(_instance == null) {
|
||||
_instance = SqfliteDB();
|
||||
}
|
||||
return _instance;
|
||||
}
|
||||
Database _database;
|
||||
|
||||
Future<Database> get database async {
|
||||
if (_database != null) return _database;
|
||||
_database = await getDatabaseInstance();
|
||||
return _database;
|
||||
}
|
||||
|
||||
Future<Database> getDatabaseInstance() async {
|
||||
var databasesPath = await getDatabasesPath();
|
||||
var path = join(databasesPath, "library.db");
|
||||
|
||||
var exists = await databaseExists(path);
|
||||
exists = false;
|
||||
if (!exists) {
|
||||
print("Creating new copy from asset");
|
||||
|
||||
try {
|
||||
await Directory(dirname(path)).create(recursive: true);
|
||||
} catch (_) {}
|
||||
|
||||
ByteData data = await rootBundle.load(join("assets", "library.db"));
|
||||
List<int> bytes =
|
||||
data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes);
|
||||
|
||||
await File(path).writeAsBytes(bytes, flush: true);
|
||||
} else {
|
||||
print("Opening existing database");
|
||||
}
|
||||
|
||||
return await openDatabase(path, readOnly: true);
|
||||
}
|
||||
|
||||
Future<Item> getItemWithId(String id) async {
|
||||
print("getItemWithId " + id);
|
||||
final db = await database;
|
||||
var response = await db.query(
|
||||
"Items",
|
||||
where: "Id = ? OR RootId = ?",
|
||||
whereArgs: [id, id]
|
||||
);
|
||||
if (response.isEmpty) {
|
||||
print("Id not found");
|
||||
}
|
||||
return response.isNotEmpty ? itemFromMap(response.first) : null;
|
||||
}
|
||||
|
||||
Future<Item> loadChildrenItems(Item item, List<Filters.Filter> filters) async {
|
||||
print("getChildrenItems " + (item?.itemType ?? ""));
|
||||
if (item.itemType.endsWith("Items")) {
|
||||
String itemType =
|
||||
item.itemType.substring(0, item.itemType.length - 1);
|
||||
String family = "";
|
||||
if (item is FilteredItems) {
|
||||
family = item.family ?? "";
|
||||
}
|
||||
String whereFilter = "";
|
||||
if (filters != null) {
|
||||
filters.forEach((filter) {
|
||||
if (filter.selectedValues.isNotEmpty) {
|
||||
whereFilter = " AND (${filter.name} LIKE '%" +
|
||||
filter.selectedValues.join("%' OR ${filter.name} LIKE '%") +
|
||||
"%')";
|
||||
}
|
||||
if (filter.rangeValues != null && (filter.rangeValues.start > 0 ||
|
||||
filter.rangeValues.end < filter.values.length - 1)) {
|
||||
whereFilter =
|
||||
" AND ([${filter.name}] BETWEEN '${filter.values[filter.rangeValues
|
||||
.start.round()]}' AND '${filter.values[filter.rangeValues.end
|
||||
.round()]}')";
|
||||
}
|
||||
});
|
||||
}
|
||||
print(whereFilter);
|
||||
final db = await database;
|
||||
var response = await db.query(
|
||||
"Items",
|
||||
where: "ItemType = ? AND Family = ?" + whereFilter,
|
||||
whereArgs: [itemType, family],
|
||||
orderBy: "NormalizedName"
|
||||
);
|
||||
if (response.isEmpty) {
|
||||
print("Children not found");
|
||||
}
|
||||
item.children = response.isNotEmpty
|
||||
? itemsFromMapList(response)
|
||||
: null;
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
Future<List<RaceItem>> loadRaces() async {
|
||||
final db = await database;
|
||||
var response = await db.query(
|
||||
"Items",
|
||||
where: "ItemType = 'RaceItem'",
|
||||
orderBy: "NormalizedName"
|
||||
);
|
||||
if (response.isNotEmpty) {
|
||||
return itemsFromMapList(response);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<List<SubRaceItem>> loadSubRaces(RaceItem race) async {
|
||||
final db = await database;
|
||||
var response = await db.query(
|
||||
"Items",
|
||||
where: "ItemType = 'SubRaceItem' AND ParentLink = ?",
|
||||
whereArgs: [race.id],
|
||||
orderBy: "NormalizedName"
|
||||
);
|
||||
if (response.isNotEmpty) {
|
||||
return itemsFromMapList(response);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<List<T>> loadTypedItems<T extends Item>(
|
||||
{String itemType, Item item}) async {
|
||||
final db = await database;
|
||||
var response = await db.query(
|
||||
"Items",
|
||||
where: "ItemType = ?" + (item != null ? " AND ParentLink = ?" : ""),
|
||||
whereArgs: item != null ? [itemType, item.id] : [itemType],
|
||||
orderBy: "NormalizedName"
|
||||
);
|
||||
if (response.isNotEmpty) {
|
||||
return itemsFromMapList(response);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<List<BackgroundItem>> loadBackgrounds() async {
|
||||
return loadTypedItems<BackgroundItem>(itemType: "BackgroundItem");
|
||||
}
|
||||
|
||||
Future<List<SubBackgroundItem>> loadSubBackgrounds(Item item) async {
|
||||
return loadTypedItems<SubBackgroundItem>(
|
||||
itemType: "SubBackgroundItem", item: item);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:aidedejeu_flutter/database.dart';
|
||||
import 'file:///C:/dev/AideDeJeu/aidedejeu_flutter/lib/databases/database_sqflite.dart';
|
||||
import 'package:aidedejeu_flutter/databases/database.dart';
|
||||
import 'package:aidedejeu_flutter/localization.dart';
|
||||
import 'package:aidedejeu_flutter/models/filters.dart';
|
||||
import 'package:aidedejeu_flutter/widgets/filters.dart';
|
||||
|
|
@ -13,12 +14,14 @@ class LibraryPage extends StatefulWidget {
|
|||
LibraryPage({Key key, @required this.id}) : super(key: key);
|
||||
|
||||
final String id;
|
||||
final BaseDB _db = SqfliteDB.instance;
|
||||
|
||||
@override
|
||||
_LibraryPageState createState() => _LibraryPageState();
|
||||
}
|
||||
|
||||
class _LibraryPageState extends State<LibraryPage> {
|
||||
final BaseDB _db = SqfliteDB.instance;
|
||||
void setItem(Item item) {
|
||||
setState(() {
|
||||
this.item = item;
|
||||
|
|
@ -54,8 +57,8 @@ class _LibraryPageState extends State<LibraryPage> {
|
|||
}
|
||||
|
||||
Future<Item> _loadItem() async {
|
||||
var item = await getItemWithId(this.widget.id);
|
||||
await loadChildrenItems(item, filters);
|
||||
var item = await SqfliteDB.instance.getItemWithId(this.widget.id);
|
||||
await _db.loadChildrenItems(item, filters);
|
||||
return item;
|
||||
}
|
||||
|
||||
|
|
@ -176,7 +179,7 @@ class _LibraryPageState extends State<LibraryPage> {
|
|||
setState(() {
|
||||
filter.selectedValues = choices;
|
||||
});
|
||||
loadChildrenItems(item, filters).then((value) => {
|
||||
_db.loadChildrenItems(item, filters).then((value) => {
|
||||
setState(() {
|
||||
this.item = item;
|
||||
this.filters = filters;
|
||||
|
|
@ -194,7 +197,7 @@ class _LibraryPageState extends State<LibraryPage> {
|
|||
setState(() {
|
||||
filter.rangeValues = values;
|
||||
});
|
||||
loadChildrenItems(item, filters).then((value) => {
|
||||
_db.loadChildrenItems(item, filters).then((value) => {
|
||||
setState(() {
|
||||
this.item = item;
|
||||
this.filters = filters;
|
||||
|
|
|
|||
|
|
@ -136,7 +136,7 @@ packages:
|
|||
name: flutter_svg
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.17.2"
|
||||
version: "0.17.3+1"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
|
|
@ -156,6 +156,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.14.0+3"
|
||||
idb_shim:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: idb_shim
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.10.3+1"
|
||||
image:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -232,7 +239,7 @@ packages:
|
|||
name: package_config
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.9.1"
|
||||
version: "1.9.2"
|
||||
path:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -281,7 +288,7 @@ packages:
|
|||
name: pub_semver
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.4.3"
|
||||
version: "1.4.4"
|
||||
quiver:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -296,6 +303,20 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.23.1"
|
||||
sembast:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: sembast
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.3.0"
|
||||
sembast_web:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: sembast_web
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
|
|
@ -314,7 +335,14 @@ packages:
|
|||
name: sqflite
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
version: "1.3.0"
|
||||
sqflite_common:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqflite_common
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.0+1"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -377,7 +405,7 @@ packages:
|
|||
name: watcher
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.9.7+13"
|
||||
version: "0.9.7+14"
|
||||
xml:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
|||
|
|
@ -24,8 +24,10 @@ dependencies:
|
|||
sdk: flutter
|
||||
intl:
|
||||
intl_translation:
|
||||
flutter_markdown: ^0.3.3
|
||||
flutter_markdown:
|
||||
sqflite:
|
||||
sembast:
|
||||
sembast_web:
|
||||
path:
|
||||
flutter_svg:
|
||||
bloc:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue