mirror of https://github.com/AMT-Cheif/drift.git
Integrate db viewer into devtools
This commit is contained in:
parent
90db860f03
commit
026fae935c
|
@ -18,4 +18,5 @@ flutter_export_environment.sh
|
||||||
docs/**/*.g.dart
|
docs/**/*.g.dart
|
||||||
|
|
||||||
*/build/
|
*/build/
|
||||||
|
drift/extension/devtools/build
|
||||||
**/pubspec_overrides.yaml
|
**/pubspec_overrides.yaml
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
!./build
|
|
@ -2,3 +2,7 @@ name: drift
|
||||||
issue_tracker: https://github.com/simolus3/drift/issues
|
issue_tracker: https://github.com/simolus3/drift/issues
|
||||||
version: 0.0.1
|
version: 0.0.1
|
||||||
material_icon_code_point: '0xf41e'
|
material_icon_code_point: '0xf41e'
|
||||||
|
|
||||||
|
# ??? https://github.com/flutter/devtools/issues/6539
|
||||||
|
issueTracker: https://github.com/simolus3/drift/issues
|
||||||
|
materialIconCodePoint: "0xf41e"
|
||||||
|
|
|
@ -143,6 +143,7 @@ abstract class GeneratedDatabase extends DatabaseConnectionUser
|
||||||
@override
|
@override
|
||||||
Future<void> close() async {
|
Future<void> close() async {
|
||||||
await super.close();
|
await super.close();
|
||||||
|
devtools.handleClosed(this);
|
||||||
|
|
||||||
assert(() {
|
assert(() {
|
||||||
if (_openedDbCount[runtimeType] != null) {
|
if (_openedDbCount[runtimeType] != null) {
|
||||||
|
|
|
@ -50,6 +50,14 @@ void handleCreated(GeneratedDatabase database) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void handleClosed(GeneratedDatabase database) {
|
||||||
|
if (_enable) {
|
||||||
|
final tracked = TrackedDatabase.byDatabase[database];
|
||||||
|
TrackedDatabase.all.remove(tracked);
|
||||||
|
_postChangedEvent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
String describe(GeneratedDatabase database) {
|
String describe(GeneratedDatabase database) {
|
||||||
return json.encode(DatabaseDescription.fromDrift(database));
|
return json.encode(DatabaseDescription.fromDrift(database));
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,7 @@ class DriftServiceExtension {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return id.toString();
|
return id;
|
||||||
case 'unsubscribe-from-tables':
|
case 'unsubscribe-from-tables':
|
||||||
_activeSubscriptions.remove(int.parse(parameters['id']!))?.cancel();
|
_activeSubscriptions.remove(int.parse(parameters['id']!))?.cancel();
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -64,6 +64,11 @@ class EntityDescription {
|
||||||
final String type;
|
final String type;
|
||||||
final List<ColumnDescription>? columns;
|
final List<ColumnDescription>? columns;
|
||||||
|
|
||||||
|
late Map<String, ColumnDescription> columnsByName = {
|
||||||
|
for (final column in columns ?? const <ColumnDescription>[])
|
||||||
|
column.name: column,
|
||||||
|
};
|
||||||
|
|
||||||
EntityDescription(
|
EntityDescription(
|
||||||
{required this.name, required this.type, required this.columns});
|
{required this.name, required this.type, required this.columns});
|
||||||
|
|
||||||
|
@ -99,6 +104,10 @@ class DatabaseDescription {
|
||||||
final bool dateTimeAsText;
|
final bool dateTimeAsText;
|
||||||
final List<EntityDescription> entities;
|
final List<EntityDescription> entities;
|
||||||
|
|
||||||
|
late Map<String, EntityDescription> entitiesByName = {
|
||||||
|
for (final entity in entities) entity.name: entity,
|
||||||
|
};
|
||||||
|
|
||||||
DatabaseDescription({required this.dateTimeAsText, required this.entities});
|
DatabaseDescription({required this.dateTimeAsText, required this.entities});
|
||||||
|
|
||||||
factory DatabaseDescription.fromDrift(GeneratedDatabase database) {
|
factory DatabaseDescription.fromDrift(GeneratedDatabase database) {
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
extensions:
|
||||||
|
- drift: true
|
|
@ -0,0 +1,3 @@
|
||||||
|
dart run devtools_extensions build_and_copy \
|
||||||
|
--source=. \
|
||||||
|
--dest=../../drift/extension/devtools
|
|
@ -59,7 +59,7 @@ class DriftDevtoolsBody extends ConsumerWidget {
|
||||||
roundedTopBorder: false,
|
roundedTopBorder: false,
|
||||||
includeTopBorder: false,
|
includeTopBorder: false,
|
||||||
title: selected != null
|
title: selected != null
|
||||||
? Text(selected.typeName)
|
? Text('Inspecting ${selected.typeName}')
|
||||||
: const Text('No database selected'),
|
: const Text('No database selected'),
|
||||||
),
|
),
|
||||||
if (selected != null) const Expanded(child: DatabaseDetails())
|
if (selected != null) const Expanded(child: DatabaseDetails())
|
||||||
|
|
|
@ -0,0 +1,154 @@
|
||||||
|
import 'package:db_viewer/db_viewer.dart';
|
||||||
|
import 'package:drift/drift.dart';
|
||||||
|
// ignore: invalid_use_of_internal_member, implementation_imports
|
||||||
|
import 'package:drift/src/runtime/devtools/shared.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:rxdart/streams.dart';
|
||||||
|
|
||||||
|
import '../remote_database.dart';
|
||||||
|
|
||||||
|
class ViewerDatabase implements DbViewerDatabase {
|
||||||
|
final RemoteDatabase database;
|
||||||
|
|
||||||
|
final Map<String, FilterData> _cachedFilters = {};
|
||||||
|
|
||||||
|
ViewerDatabase({required this.database});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget buildWhereWidget(
|
||||||
|
{required VoidCallback onAddClicked,
|
||||||
|
required List<WhereClause> whereClauses}) {
|
||||||
|
return Container();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<int> count(String entityName) {
|
||||||
|
return customSelectStream('SELECT COUNT(*) AS r FROM "$entityName"')
|
||||||
|
.map((rows) => rows.first['r'] as int);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<Map<String, dynamic>>> customSelect(String query,
|
||||||
|
{Set<String>? fromEntityNames}) {
|
||||||
|
return database.select(query, const []);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<List<Map<String, dynamic>>> customSelectStream(String query,
|
||||||
|
{Set<String>? fromEntityNames}) {
|
||||||
|
fromEntityNames ??= const {};
|
||||||
|
|
||||||
|
final updates = database.tableUpdates
|
||||||
|
.where(
|
||||||
|
(e) => e.any((updated) => fromEntityNames!.contains(updated.table)))
|
||||||
|
.asyncMap((event) => customSelect(query));
|
||||||
|
|
||||||
|
return ConcatStream([
|
||||||
|
Stream.fromFuture(customSelect(query)),
|
||||||
|
updates,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<String> get entityNames => [
|
||||||
|
for (final entity in database.description.entities)
|
||||||
|
if (entity.type == 'table') entity.name,
|
||||||
|
];
|
||||||
|
|
||||||
|
@override
|
||||||
|
FilterData getCachedFilterData(String entityName) {
|
||||||
|
return _cachedFilters.putIfAbsent(
|
||||||
|
entityName, () => getFilterData(entityName));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<String> getColumnNamesByEntityName(String entityName) {
|
||||||
|
return database.description.entitiesByName[entityName]!.columnsByName.keys
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
FilterData getFilterData(String entityName) {
|
||||||
|
return DriftFilterData(
|
||||||
|
entity: database.description.entitiesByName[entityName]!);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String getType(String entityName, String columnName) {
|
||||||
|
final type = database.description.entitiesByName[entityName]!
|
||||||
|
.columnsByName[columnName]!.type!;
|
||||||
|
final genContext = GenerationContext(
|
||||||
|
DriftDatabaseOptions(
|
||||||
|
storeDateTimeAsText: database.description.dateTimeAsText),
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
|
||||||
|
return type.type?.sqlTypeName(genContext) ?? type.customTypeName!;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Map<String, dynamic>> remapData(
|
||||||
|
String entityName, List<Map<String, dynamic>> data) {
|
||||||
|
// ignore: invalid_use_of_internal_member
|
||||||
|
final types = SqlTypes(database.description.dateTimeAsText);
|
||||||
|
final mapped = <Map<String, dynamic>>[];
|
||||||
|
final entity = database.description.entitiesByName[entityName]!;
|
||||||
|
|
||||||
|
for (final row in data) {
|
||||||
|
final mappedRow = <String, dynamic>{};
|
||||||
|
|
||||||
|
for (var MapEntry(key: column, :value) in row.entries) {
|
||||||
|
final resolvedColumn = entity.columnsByName[column];
|
||||||
|
|
||||||
|
if (resolvedColumn != null) {
|
||||||
|
final type = resolvedColumn.type?.type ?? DriftSqlType.any;
|
||||||
|
|
||||||
|
mappedRow[column] = types.read(type, value);
|
||||||
|
} else {
|
||||||
|
mappedRow[column] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mapped.add(mappedRow);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mapped;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> runCustomStatement(String query) {
|
||||||
|
return database.execute(query, const []);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void updateFilterData(String entityName, FilterData filterData) {
|
||||||
|
_cachedFilters[entityName] = filterData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DriftFilterData extends FilterData {
|
||||||
|
final EntityDescription entity;
|
||||||
|
|
||||||
|
DriftFilterData({required this.entity});
|
||||||
|
|
||||||
|
@override
|
||||||
|
DriftFilterData copy() {
|
||||||
|
return DriftFilterData(entity: entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, bool> getSelectedColumns() {
|
||||||
|
return {
|
||||||
|
for (final column in entity.columns ?? const <ColumnDescription>[])
|
||||||
|
column.name: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
WhereClause? getWhereClause(String columnName) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get tableName => entity.name;
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
import 'package:db_viewer/db_viewer.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import '../remote_database.dart';
|
||||||
|
import 'database.dart';
|
||||||
|
|
||||||
|
class DatabaseViewer extends StatefulWidget {
|
||||||
|
final RemoteDatabase database;
|
||||||
|
|
||||||
|
const DatabaseViewer({super.key, required this.database});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<DatabaseViewer> createState() => _DatabaseViewerState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DatabaseViewerState extends State<DatabaseViewer> {
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
DbViewerDatabase.initDb(ViewerDatabase(database: widget.database));
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(maxHeight: 800),
|
||||||
|
child: const DbViewerNavigator(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ import 'package:devtools_app_shared/service.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
|
import 'db_viewer/viewer.dart';
|
||||||
import 'list.dart';
|
import 'list.dart';
|
||||||
import 'remote_database.dart';
|
import 'remote_database.dart';
|
||||||
import 'service.dart';
|
import 'service.dart';
|
||||||
|
@ -20,16 +21,6 @@ final loadedDatabase = AutoDisposeFutureProvider((ref) async {
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
|
||||||
final _testQuery = AutoDisposeFutureProvider((ref) async {
|
|
||||||
final database = await ref.watch(loadedDatabase.future);
|
|
||||||
|
|
||||||
if (database != null) {
|
|
||||||
return await database.select('SELECT 1, 2, 3', []);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
class DatabaseDetails extends ConsumerStatefulWidget {
|
class DatabaseDetails extends ConsumerStatefulWidget {
|
||||||
const DatabaseDetails({super.key});
|
const DatabaseDetails({super.key});
|
||||||
|
|
||||||
|
@ -51,21 +42,28 @@ class _DatabaseDetailsState extends ConsumerState<DatabaseDetails> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final database = ref.watch(loadedDatabase);
|
final database = ref.watch(loadedDatabase);
|
||||||
final query = ref.watch(_testQuery);
|
|
||||||
|
|
||||||
return database.when(
|
return database.when(
|
||||||
loading: () => const Center(child: CircularProgressIndicator()),
|
loading: () => const Center(child: CircularProgressIndicator()),
|
||||||
error: (err, stack) => Text('unknown error: $err\n$stack'),
|
error: (err, stack) => Text('unknown error: $err\n$stack'),
|
||||||
data: (database) {
|
data: (database) {
|
||||||
if (database != null) {
|
if (database != null) {
|
||||||
|
final textTheme = Theme.of(context).textTheme;
|
||||||
|
|
||||||
return Scrollbar(
|
return Scrollbar(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
child: ListView(
|
child: ListView(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
children: [
|
children: [
|
||||||
for (final entity in database.description.entities)
|
Padding(
|
||||||
Text('${entity.name}: ${entity.type}'),
|
padding: const EdgeInsets.all(8.0),
|
||||||
Text(query.toString()),
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Text('Database viewer', style: textTheme.headlineMedium),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
DatabaseViewer(database: database),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -155,7 +155,9 @@ class _DatabaseEntry extends ConsumerWidget {
|
||||||
|
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
behavior: HitTestBehavior.opaque,
|
behavior: HitTestBehavior.opaque,
|
||||||
onTap: () {},
|
onTap: () {
|
||||||
|
ref.read(selectedDatabase.notifier).state = database;
|
||||||
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
color: isSelected ? colorScheme.selectedRowBackgroundColor : null,
|
color: isSelected ? colorScheme.selectedRowBackgroundColor : null,
|
||||||
padding: _DatabaseListState._tilePadding,
|
padding: _DatabaseListState._tilePadding,
|
||||||
|
|
|
@ -62,12 +62,13 @@ class RemoteDatabase {
|
||||||
|
|
||||||
Future<List<Map<String, Object?>>> select(
|
Future<List<Map<String, Object?>>> select(
|
||||||
String sql, List<Object?> args) async {
|
String sql, List<Object?> args) async {
|
||||||
final result = await _driftRequest('execute-query', payload: {
|
final result = await _executeQuery<SelectResult>(
|
||||||
'query': json.encode(_protocol
|
ExecuteQuery(StatementMethod.select, sql, args));
|
||||||
.encodePayload(ExecuteQuery(StatementMethod.select, sql, args)))
|
return result.rows;
|
||||||
});
|
}
|
||||||
|
|
||||||
return (_protocol.decodePayload(result) as SelectResult).rows;
|
Future<void> execute(String sql, List<Object?> args) async {
|
||||||
|
await _executeQuery<void>(ExecuteQuery(StatementMethod.custom, sql, args));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<int> _newTableSubscription() async {
|
Future<int> _newTableSubscription() async {
|
||||||
|
@ -82,6 +83,14 @@ class RemoteDatabase {
|
||||||
_tableNotifications = null;
|
_tableNotifications = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<T> _executeQuery<T>(ExecuteQuery e) async {
|
||||||
|
final result = await _driftRequest('execute-query', payload: {
|
||||||
|
'query': json.encode(_protocol.encodePayload(e)),
|
||||||
|
});
|
||||||
|
|
||||||
|
return _protocol.decodePayload(result) as T;
|
||||||
|
}
|
||||||
|
|
||||||
Future<Object?> _driftRequest(String method,
|
Future<Object?> _driftRequest(String method,
|
||||||
{Map<String, String> payload = const {}}) async {
|
{Map<String, String> payload = const {}}) async {
|
||||||
final response = await serviceManager.callServiceExtensionOnMainIsolate(
|
final response = await serviceManager.callServiceExtensionOnMainIsolate(
|
||||||
|
|
|
@ -1,40 +1,16 @@
|
||||||
name: drift_devtools_extension
|
name: drift_devtools_extension
|
||||||
description: A new Flutter project.
|
description: A Flutter web app contribution drift tools to DevTools
|
||||||
# The following line prevents the package from being accidentally published to
|
publish_to: 'none'
|
||||||
# pub.dev using `flutter pub publish`. This is preferred for private packages.
|
|
||||||
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
|
||||||
|
|
||||||
# The following defines the version and build number for your application.
|
|
||||||
# A version number is three numbers separated by dots, like 1.2.43
|
|
||||||
# followed by an optional build number separated by a +.
|
|
||||||
# Both the version and the builder number may be overridden in flutter
|
|
||||||
# build by specifying --build-name and --build-number, respectively.
|
|
||||||
# In Android, build-name is used as versionName while build-number used as versionCode.
|
|
||||||
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
|
|
||||||
# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
|
|
||||||
# Read more about iOS versioning at
|
|
||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
|
||||||
# In Windows, build-name is used as the major, minor, and patch parts
|
|
||||||
# of the product and file versions while build-number is used as the build suffix.
|
|
||||||
version: 1.0.0+1
|
version: 1.0.0+1
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.1.0 <4.0.0'
|
sdk: '>=3.1.0 <4.0.0'
|
||||||
|
|
||||||
# Dependencies specify other packages that your package needs in order to work.
|
|
||||||
# To automatically upgrade your package dependencies to the latest versions
|
|
||||||
# consider running `flutter pub upgrade --major-versions`. Alternatively,
|
|
||||||
# dependencies can be manually updated by changing the version numbers below to
|
|
||||||
# the latest version available on pub.dev. To see which dependencies have newer
|
|
||||||
# versions available, run `flutter pub outdated`.
|
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|
||||||
|
|
||||||
# The following adds the Cupertino Icons font to your application.
|
|
||||||
# Use with the CupertinoIcons class for iOS style icons.
|
|
||||||
cupertino_icons: ^1.0.2
|
|
||||||
devtools_extensions: ^0.0.8
|
devtools_extensions: ^0.0.8
|
||||||
devtools_app_shared: ^0.0.5
|
devtools_app_shared: ^0.0.5
|
||||||
db_viewer: ^1.0.3
|
db_viewer: ^1.0.3
|
||||||
|
@ -48,52 +24,7 @@ dependencies:
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|
||||||
# The "flutter_lints" package below contains a set of recommended lints to
|
|
||||||
# encourage good coding practices. The lint set provided by the package is
|
|
||||||
# activated in the `analysis_options.yaml` file located at the root of your
|
|
||||||
# package. See that file for information about deactivating specific lint
|
|
||||||
# rules and activating additional ones.
|
|
||||||
flutter_lints: ^2.0.0
|
flutter_lints: ^2.0.0
|
||||||
|
|
||||||
# For information on the generic Dart part of this file, see the
|
|
||||||
# following page: https://dart.dev/tools/pub/pubspec
|
|
||||||
|
|
||||||
# The following section is specific to Flutter packages.
|
|
||||||
flutter:
|
flutter:
|
||||||
|
|
||||||
# The following line ensures that the Material Icons font is
|
|
||||||
# included with your application, so that you can use the icons in
|
|
||||||
# the material Icons class.
|
|
||||||
uses-material-design: true
|
uses-material-design: true
|
||||||
|
|
||||||
# To add assets to your application, add an assets section, like this:
|
|
||||||
# assets:
|
|
||||||
# - images/a_dot_burr.jpeg
|
|
||||||
# - images/a_dot_ham.jpeg
|
|
||||||
|
|
||||||
# An image asset can refer to one or more resolution-specific "variants", see
|
|
||||||
# https://flutter.dev/assets-and-images/#resolution-aware
|
|
||||||
|
|
||||||
# For details regarding adding assets from package dependencies, see
|
|
||||||
# https://flutter.dev/assets-and-images/#from-packages
|
|
||||||
|
|
||||||
# To add custom fonts to your application, add a fonts section here,
|
|
||||||
# in this "flutter" section. Each entry in this list should have a
|
|
||||||
# "family" key with the font family name, and a "fonts" key with a
|
|
||||||
# list giving the asset and other descriptors for the font. For
|
|
||||||
# example:
|
|
||||||
# fonts:
|
|
||||||
# - family: Schyler
|
|
||||||
# fonts:
|
|
||||||
# - asset: fonts/Schyler-Regular.ttf
|
|
||||||
# - asset: fonts/Schyler-Italic.ttf
|
|
||||||
# style: italic
|
|
||||||
# - family: Trajan Pro
|
|
||||||
# fonts:
|
|
||||||
# - asset: fonts/TrajanPro.ttf
|
|
||||||
# - asset: fonts/TrajanPro_Bold.ttf
|
|
||||||
# weight: 700
|
|
||||||
#
|
|
||||||
# For details regarding fonts from package dependencies,
|
|
||||||
# see https://flutter.dev/custom-fonts/#from-packages
|
|
||||||
|
|
Loading…
Reference in New Issue