Improved UI and App State Management

This commit is contained in:
Ankit Mahato
2023-03-05 04:22:22 +05:30
parent 76e5fc006c
commit 5c4ec360aa
17 changed files with 902 additions and 55 deletions

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../models/models.dart';
import '../providers/providers.dart';
import '../utils/utils.dart';
import '../consts.dart';
class CollectionPane extends ConsumerStatefulWidget {
@ -73,6 +74,7 @@ class _RequestListState extends ConsumerState<RequestList> {
@override
Widget build(BuildContext context) {
final requestItems = ref.watch(collectionStateNotifierProvider);
return ReorderableListView.builder(
buildDefaultDragHandles: false,
shrinkWrap: true,
@ -92,7 +94,9 @@ class _RequestListState extends ConsumerState<RequestList> {
key: Key(requestItems[index].id),
index: index,
child: RequestItem(
id: requestItems[index].id, requestModel: requestItems[index]),
id: requestItems[index].id,
requestModel: requestItems[index],
),
);
},
);
@ -156,7 +160,7 @@ class _RequestItemState extends ConsumerState<RequestItem> {
),
Expanded(
child: Text(
widget.requestModel.id,
getRequestTitleFromUrl(widget.requestModel.url),
softWrap: false,
overflow: TextOverflow.fade,
),
@ -215,12 +219,13 @@ class MethodBox extends StatelessWidget {
text = "DEL";
}
return SizedBox(
width: 25,
width: 28,
child: Text(
text,
style: TextStyle(
fontSize: 8,
fontWeight: FontWeight.bold,
color: getHTTPMethodColor(method),
),
),
);

View File

@ -1,6 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../providers/providers.dart';
import 'editor_pane/url_card.dart';
import 'editor_pane/details_card.dart';
class RequestEditorPane extends ConsumerStatefulWidget {
const RequestEditorPane({
@ -21,13 +23,19 @@ class _RequestEditorPaneState extends ConsumerState<RequestEditorPane> {
Widget build(BuildContext context) {
final activeId = ref.watch(activeItemIdStateProvider);
if (activeId == null) {
return Text("Select One");
return Text("Select");
} else {
return Padding(
padding: const EdgeInsets.all(8),
child: Column(
children: [
Container(),
EditorPaneRequestURLCard(),
SizedBox(
height: 10,
),
Expanded(
child: EditorPaneRequestDetailsCard(),
),
],
),
);

View File

@ -0,0 +1,126 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../providers/providers.dart';
import '../../consts.dart';
class EditRequestBody extends StatefulWidget {
const EditRequestBody({super.key});
@override
State<EditRequestBody> createState() => _EditRequestBodyState();
}
class _EditRequestBodyState extends State<EditRequestBody> {
@override
Widget build(BuildContext context) {
return Column(
children: [
Padding(
padding: const EdgeInsets.all(10),
child: Row(
children: [
SizedBox(
child: DropdownButtonBodyContentType(),
height: 30,
)
],
),
),
Expanded(
child: TextFieldEditor(),
)
],
);
}
}
class TextFieldEditor extends ConsumerStatefulWidget {
const TextFieldEditor({Key? key}) : super(key: key);
@override
ConsumerState<TextFieldEditor> createState() => _TextFieldEditorState();
}
class _TextFieldEditorState extends ConsumerState<TextFieldEditor> {
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
final activeId = ref.watch(activeItemIdStateProvider);
final reqestModel = ref
.read(collectionStateNotifierProvider.notifier)
.getRequestModel(activeId!);
return TextFormField(
key: Key("$activeId-body"),
initialValue: reqestModel.requestBody ?? "",
onChanged: (value) {
ref
.read(collectionStateNotifierProvider.notifier)
.update(activeId, requestBody: value);
},
decoration: InputDecoration(
hintText: "Enter body",
hintStyle: TextStyle(color: Colors.grey.shade500),
border: InputBorder.none,
),
keyboardType: TextInputType.multiline,
expands: true,
maxLines: null,
);
}
}
class DropdownButtonBodyContentType extends ConsumerStatefulWidget {
const DropdownButtonBodyContentType({
super.key,
});
@override
ConsumerState<DropdownButtonBodyContentType> createState() =>
_DropdownButtonBodyContentTypeState();
}
class _DropdownButtonBodyContentTypeState
extends ConsumerState<DropdownButtonBodyContentType> {
@override
Widget build(BuildContext context) {
final activeId = ref.watch(activeItemIdStateProvider);
final reqestModel = ref
.read(collectionStateNotifierProvider.notifier)
.getRequestModel(activeId!);
return DropdownButton<ContentType>(
focusColor: Colors.white,
value: reqestModel.requestBodyContentType,
icon: const Icon(
Icons.unfold_more_rounded,
size: 16,
),
elevation: 4,
underline: Container(
height: 0,
//color: Colors.deepPurpleAccent,
),
onChanged: (ContentType? value) {
ref
.read(collectionStateNotifierProvider.notifier)
.update(activeId!, requestBodyContentType: value);
},
borderRadius: BorderRadius.circular(10),
items: ContentType.values
.map<DropdownMenuItem<ContentType>>((ContentType value) {
return DropdownMenuItem<ContentType>(
value: value,
child: Padding(
padding: const EdgeInsets.only(left: 8),
child: Text(
value.name,
),
),
);
}).toList(),
);
}
}

View File

@ -0,0 +1,54 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:multi_split_view/multi_split_view.dart';
import 'request_pane.dart';
import 'response_pane.dart';
import '../styles.dart';
class EditorPaneRequestDetailsCard extends StatefulWidget {
const EditorPaneRequestDetailsCard({super.key});
@override
State<EditorPaneRequestDetailsCard> createState() =>
_EditorPaneRequestDetailsCardState();
}
class _EditorPaneRequestDetailsCardState
extends State<EditorPaneRequestDetailsCard> {
final MultiSplitViewController _controller = MultiSplitViewController(
areas: [
Area(minimalSize: 300),
Area(minimalSize: 300),
],
);
@override
Widget build(BuildContext context) {
return Card(
shape: cardShape,
child: MultiSplitViewTheme(
data: MultiSplitViewThemeData(
dividerThickness: 3,
dividerPainter: DividerPainters.background(
color: Colors.grey.shade200,
highlightedColor: Colors.grey.shade400,
animationEnabled: false,
),
),
child: MultiSplitView(
controller: _controller,
children: const [
EditRequestPane(),
ResponsePane(),
],
),
),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}

View File

@ -0,0 +1,61 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:tab_container/tab_container.dart';
import '../../providers/providers.dart';
import 'tables.dart';
import 'body_editor.dart';
class EditRequestPane extends ConsumerStatefulWidget {
const EditRequestPane({super.key});
@override
ConsumerState<EditRequestPane> createState() => _EditRequestPaneState();
}
class _EditRequestPaneState extends ConsumerState<EditRequestPane> {
late final TabContainerController _controller;
@override
void initState() {
super.initState();
_controller = TabContainerController(length: 3);
}
@override
Widget build(BuildContext context) {
final activeId = ref.watch(activeItemIdStateProvider);
_controller.jumpTo(ref
.read(collectionStateNotifierProvider.notifier)
.getRequestModel(activeId!)
.requestTabIndex);
return Padding(
padding: const EdgeInsets.all(5),
child: TabContainer(
key: Key(activeId),
controller: _controller,
color: Colors.grey.shade100,
onEnd: () {
ref
.read(collectionStateNotifierProvider.notifier)
.update(activeId, requestTabIndex: _controller.index);
},
tabs: const [
'URL Params',
'Headers',
'Body',
],
children: const [
EditRequestURLParams(),
EditRequestHeaders(),
EditRequestBody(),
],
),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}

View File

@ -0,0 +1,31 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class ResponsePane extends ConsumerStatefulWidget {
const ResponsePane({super.key});
@override
ConsumerState<ResponsePane> createState() => _ResponsePaneState();
}
class _ResponsePaneState extends ConsumerState<ResponsePane> {
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(5),
child: Column(
children: [],
),
);
}
@override
void dispose() {
super.dispose();
}
}

View File

@ -0,0 +1,230 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:davi/davi.dart';
import '../../models/models.dart';
import '../../providers/providers.dart';
class EditRequestURLParams extends ConsumerStatefulWidget {
const EditRequestURLParams({Key? key}) : super(key: key);
@override
ConsumerState<EditRequestURLParams> createState() =>
EditRequestURLParamsState();
}
class EditRequestURLParamsState extends ConsumerState<EditRequestURLParams> {
late List<KVRow> rows;
@override
void initState() {
super.initState();
}
Widget _buildParamField(BuildContext context, DaviRow<KVRow> row) {
String? activeId = ref.read(activeItemIdStateProvider);
int idx = row.index;
return TextFormField(
key: Key("$activeId-$idx-params-k"),
initialValue: rows[idx].k,
decoration: const InputDecoration(hintText: "Add URL Parameter"),
onChanged: (value) {
rows[idx] = rows[idx].copyWith(k: value);
_onFieldChange(activeId!);
});
}
Widget _buildValueField(BuildContext context, DaviRow<KVRow> row) {
String? activeId = ref.read(activeItemIdStateProvider);
int idx = row.index;
return TextFormField(
key: Key("$activeId-$idx-params-v"),
initialValue: rows[idx].v,
decoration: const InputDecoration(hintText: "Add Value"),
onChanged: (value) {
rows[idx] = rows[idx].copyWith(v: value);
_onFieldChange(activeId!);
});
}
void _onFieldChange(String activeId) {
ref
.read(collectionStateNotifierProvider.notifier)
.update(activeId, requestParams: rows);
}
@override
Widget build(BuildContext context) {
final activeId = ref.watch(activeItemIdStateProvider);
rows = ref
.read(collectionStateNotifierProvider.notifier)
.getRequestModel(activeId!)
.requestParams ??
[];
DaviModel<KVRow> model = DaviModel<KVRow>(
rows: rows,
columns: [
DaviColumn(
name: 'URL Parameter',
grow: 1,
cellBuilder: _buildParamField,
),
DaviColumn(
name: 'Value',
grow: 1,
cellBuilder: _buildValueField,
),
],
);
return Column(
children: [
Padding(
padding: const EdgeInsets.all(10),
child: Row(
children: [
ElevatedButton(
onPressed: () {
rows.add(const KVRow("", ""));
model.addRow(const KVRow("", ""));
},
child: const Text("+ Add Param"),
),
],
),
),
Expanded(
child: DaviTheme(
data: DaviThemeData(
columnDividerThickness: 1,
columnDividerColor: Colors.grey.shade100,
row: RowThemeData(dividerColor: Colors.grey.shade100),
decoration: const BoxDecoration(
border: Border(),
),
header: HeaderThemeData(
color: Colors.grey.shade100,
columnDividerColor: Colors.grey.shade100,
bottomBorderHeight: 1,
),
headerCell: const HeaderCellThemeData(
alignment: Alignment.center,
),
),
child: Davi<KVRow>(model),
),
),
],
);
}
}
class EditRequestHeaders extends ConsumerStatefulWidget {
const EditRequestHeaders({Key? key}) : super(key: key);
@override
ConsumerState<EditRequestHeaders> createState() => EditRequestHeadersState();
}
class EditRequestHeadersState extends ConsumerState<EditRequestHeaders> {
late List<KVRow> rows;
@override
void initState() {
super.initState();
}
Widget _buildHeaderField(BuildContext context, DaviRow<KVRow> row) {
String? activeId = ref.read(activeItemIdStateProvider);
int idx = row.index;
return TextFormField(
key: Key("$activeId-$idx-headers-k"),
initialValue: rows[idx].k,
decoration: const InputDecoration(hintText: "Add Header Name"),
onChanged: (value) {
rows[idx] = rows[idx].copyWith(k: value);
_onFieldChange(activeId!);
});
}
Widget _buildValueField(BuildContext context, DaviRow<KVRow> row) {
String? activeId = ref.read(activeItemIdStateProvider);
int idx = row.index;
return TextFormField(
key: Key("$activeId-$idx-headers-v"),
initialValue: rows[idx].v,
decoration: const InputDecoration(hintText: "Add Header Value"),
onChanged: (value) {
rows[idx] = rows[idx].copyWith(v: value);
_onFieldChange(activeId!);
});
}
void _onFieldChange(String activeId) {
ref
.read(collectionStateNotifierProvider.notifier)
.update(activeId, requestHeaders: rows);
}
@override
Widget build(BuildContext context) {
final activeId = ref.watch(activeItemIdStateProvider);
rows = ref
.read(collectionStateNotifierProvider.notifier)
.getRequestModel(activeId!)
.requestHeaders ??
[];
DaviModel<KVRow> model = DaviModel<KVRow>(
rows: rows,
columns: [
DaviColumn(
name: 'Header Name',
grow: 1,
cellBuilder: _buildHeaderField,
),
DaviColumn(
name: 'Header Value',
grow: 1,
cellBuilder: _buildValueField,
),
],
);
return Column(
children: [
Padding(
padding: const EdgeInsets.all(10),
child: Row(
children: [
ElevatedButton(
onPressed: () {
rows.add(const KVRow("", ""));
model.addRow(const KVRow("", ""));
},
child: const Text("+ Add Header"),
),
],
),
),
Expanded(
child: DaviTheme(
data: DaviThemeData(
columnDividerThickness: 1,
columnDividerColor: Colors.grey.shade100,
row: RowThemeData(dividerColor: Colors.grey.shade100),
decoration: const BoxDecoration(
border: Border(),
),
header: HeaderThemeData(
color: Colors.grey.shade100,
columnDividerColor: Colors.grey.shade100,
bottomBorderHeight: 1,
),
headerCell: const HeaderCellThemeData(
alignment: Alignment.center,
),
),
child: Davi<KVRow>(model),
),
),
],
);
}
}

View File

@ -0,0 +1,172 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../providers/providers.dart';
import '../../utils/utils.dart';
import '../styles.dart';
import '../../consts.dart';
class EditorPaneRequestURLCard extends ConsumerStatefulWidget {
const EditorPaneRequestURLCard({super.key});
@override
ConsumerState<EditorPaneRequestURLCard> createState() =>
_EditorPaneRequestURLCardState();
}
class _EditorPaneRequestURLCardState
extends ConsumerState<EditorPaneRequestURLCard> {
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
final activeId = ref.watch(activeItemIdStateProvider);
return Card(
shape: cardShape,
child: Padding(
padding: EdgeInsets.symmetric(
vertical: 5,
horizontal: 20,
),
child: Row(
children: [
DropdownButtonHTTPMethod(),
const SizedBox(
width: 20,
),
Expanded(
child: URLTextField(),
),
const SizedBox(
width: 20,
),
Container(
height: 36,
child: ElevatedButton(
onPressed: () async {
ref
.read(collectionStateNotifierProvider.notifier)
.sendRequest(activeId!);
},
child: Row(
children: [
Text("Send"),
SizedBox(
width: 10,
),
Icon(size: 16, Icons.send),
],
),
),
),
],
),
),
);
}
}
class DropdownButtonHTTPMethod extends ConsumerStatefulWidget {
const DropdownButtonHTTPMethod({
super.key,
});
@override
ConsumerState<DropdownButtonHTTPMethod> createState() =>
_DropdownButtonHTTPMethodState();
}
class _DropdownButtonHTTPMethodState
extends ConsumerState<DropdownButtonHTTPMethod> {
//late HTTPVerb dropdownValue;
@override
void initState() {
super.initState();
//dropdownValue = ref
// .read(collectionStateNotifierProvider.notifier)
// .idxOfId(String id);
}
@override
Widget build(BuildContext context) {
final activeId = ref.watch(activeItemIdStateProvider);
final collection = ref.read(collectionStateNotifierProvider);
final idIdx = collection.indexWhere((m) => m.id == activeId);
final method = ref.watch(
collectionStateNotifierProvider.select((value) => value[idIdx].method));
//final model = ref
// .read(collectionStateNotifierProvider.notifier)
// .getRequestModel(activeId!);
return DropdownButton<HTTPVerb>(
focusColor: Colors.white,
//value: collection[idIdx].method,
value: method, //model.method,
icon: const Icon(Icons.unfold_more_rounded),
elevation: 4,
underline: Container(
height: 0,
//color: Colors.deepPurpleAccent,
),
onChanged: (HTTPVerb? value) {
ref
.read(collectionStateNotifierProvider.notifier)
.update(activeId!, method: value);
},
borderRadius: BorderRadius.circular(10),
items: HTTPVerb.values.map<DropdownMenuItem<HTTPVerb>>((HTTPVerb value) {
return DropdownMenuItem<HTTPVerb>(
//alignment: AlignmentDirectional.center,
value: value,
child: Padding(
padding: const EdgeInsets.only(left: 16),
child: Text(
value.name.toUpperCase(),
style: TextStyle(color: getHTTPMethodColor(value)),
),
),
);
}).toList(),
);
}
}
class URLTextField extends ConsumerStatefulWidget {
const URLTextField({
super.key,
});
@override
ConsumerState<URLTextField> createState() => _URLTextFieldState();
}
class _URLTextFieldState extends ConsumerState<URLTextField> {
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
final activeId = ref.watch(activeItemIdStateProvider);
return TextFormField(
key: Key("url-${activeId!}"),
initialValue: ref
.read(collectionStateNotifierProvider.notifier)
.getRequestModel(activeId!)
.url,
decoration: InputDecoration(
hintText: "Enter API endpoint like api.foss42.com/country/codes",
hintStyle: TextStyle(color: Colors.grey.shade500),
border: InputBorder.none,
),
onChanged: (value) {
ref
.read(collectionStateNotifierProvider.notifier)
.update(activeId!, url: value);
},
);
}
}

View File

@ -0,0 +1,11 @@
import 'package:flutter/material.dart';
final cardShape = RoundedRectangleBorder(
side: const BorderSide(
color: Colors.white70,
width: 1,
),
borderRadius: BorderRadius.circular(10),
);
final colorErrorMsg = Colors.grey.shade500;

View File

@ -1,3 +1,6 @@
enum HTTPVerb { get, head, post, put, patch, delete }
enum ContentType { json, text }
const DEFAULT_METHOD = HTTPVerb.get;
const DEFAULT_BODY_CONTENT_TYPE = ContentType.json;

View File

@ -0,0 +1,38 @@
import 'package:flutter/material.dart';
@immutable
class KVRow {
const KVRow(this.k, this.v);
final String k;
final dynamic v;
KVRow copyWith({
String? k,
dynamic v,
}) {
return KVRow(k ?? this.k, v ?? this.v);
}
@override
String toString() {
return {k: v}.toString();
}
}
Map<String, String>? rowsToMap(List<KVRow>? kvRows, {bool isHeader = false}) {
if (kvRows == null) {
return null;
}
Map<String, String> finalMap = {};
for (var row in kvRows) {
if (row.k.trim() != "") {
String key = row.k;
if (isHeader) {
key = key.toLowerCase();
}
finalMap[key] = row.v;
}
}
return finalMap;
}

View File

@ -1,46 +1,2 @@
import 'package:flutter/material.dart';
import '../consts.dart';
@immutable
class RequestModel {
const RequestModel({
required this.id,
this.method = DEFAULT_METHOD,
this.url = "",
});
final String id;
final HTTPVerb method;
final String url;
RequestModel duplicate({
required String id,
}) {
return RequestModel(
id: id,
method: method,
url: url,
);
}
RequestModel copyWith({
String? id,
HTTPVerb? method,
String? url,
}) {
return RequestModel(
id: id ?? this.id,
method: method ?? this.method,
url: url ?? this.url,
);
}
@override
String toString() {
return [
id,
method.name,
"URL: $url",
].join("\n");
}
}
export 'kvrow_model.dart';
export 'request_model.dart';

View File

@ -0,0 +1,77 @@
import 'package:flutter/material.dart';
import 'kvrow_model.dart';
import '../consts.dart';
@immutable
class RequestModel {
const RequestModel({
required this.id,
this.method = DEFAULT_METHOD,
this.url = "",
this.requestTabIndex = 0,
this.requestHeaders,
this.requestParams,
this.requestBodyContentType = DEFAULT_BODY_CONTENT_TYPE,
this.requestBody,
});
final String id;
final HTTPVerb method;
final String url;
final int requestTabIndex;
final List<KVRow>? requestHeaders;
final List<KVRow>? requestParams;
final ContentType requestBodyContentType;
final dynamic requestBody;
RequestModel duplicate({
required String id,
}) {
return RequestModel(
id: id,
method: method,
url: url,
requestHeaders: requestHeaders,
requestParams: requestParams,
requestBodyContentType: requestBodyContentType,
requestBody: requestBody,
);
}
RequestModel copyWith({
String? id,
HTTPVerb? method,
String? url,
int? requestTabIndex,
List<KVRow>? requestHeaders,
List<KVRow>? requestParams,
ContentType? requestBodyContentType,
dynamic requestBody,
}) {
return RequestModel(
id: id ?? this.id,
method: method ?? this.method,
url: url ?? this.url,
requestTabIndex: requestTabIndex ?? this.requestTabIndex,
requestHeaders: requestHeaders ?? this.requestHeaders,
requestParams: requestParams ?? this.requestParams,
requestBodyContentType:
requestBodyContentType ?? this.requestBodyContentType,
requestBody: requestBody ?? this.requestBody,
);
}
@override
String toString() {
return [
id,
method.name,
"URL: $url",
"Request Tab Index: ${requestTabIndex.toString()}",
"Request Headers: ${requestHeaders.toString()}",
"Request Params: ${requestParams.toString()}",
"Request Body Content Type: ${requestBodyContentType.toString()}",
"Request Body: ${requestBody.toString()}",
].join("\n");
}
}

View File

@ -59,16 +59,24 @@ class CollectionStateNotifier extends StateNotifier<List<RequestModel>> {
HTTPVerb? method,
String? url,
int? requestTabIndex,
List<KVRow>? requestHeaders,
List<KVRow>? requestParams,
ContentType? requestBodyContentType,
dynamic requestBody,
int? responseStatus,
Map<String, String>? responseHeaders,
String? responseBody,
}) {
final idx = idxOfId(id);
final newModel = state[idx].copyWith(
method: method,
url: url,
requestTabIndex: requestTabIndex,
requestHeaders: requestHeaders,
requestParams: requestParams,
requestBodyContentType: requestBodyContentType,
requestBody: requestBody,
);
//print(newModel);
state = [...state.sublist(0, idx), newModel, ...state.sublist(idx + 1)];
}
Future<void> sendRequest(String id) async {}
}

41
lib/utils/utils.dart Normal file
View File

@ -0,0 +1,41 @@
import 'package:flutter/material.dart';
import '../consts.dart';
Color getHTTPMethodColor(HTTPVerb method) {
Color col;
switch (method) {
case HTTPVerb.get:
col = Colors.green.shade800;
break;
case HTTPVerb.head:
col = Colors.green.shade800;
break;
case HTTPVerb.post:
col = Colors.blue.shade800;
break;
case HTTPVerb.put:
col = Colors.amber.shade900;
break;
case HTTPVerb.patch:
col = Colors.amber.shade900;
break;
case HTTPVerb.delete:
col = Colors.red.shade800;
break;
}
return col;
}
String getRequestTitleFromUrl(String? url) {
if (url == null || url.trim() == "") {
return "untitled";
}
if (url.contains("://")) {
String rem = url.split("://")[1];
if (rem.trim() == "") {
return "untitled";
}
return rem;
}
return url;
}

View File

@ -9,6 +9,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.10.0"
axis_layout:
dependency: transitive
description:
name: axis_layout
sha256: "645f76e306a48e1075f8f142bd8ef12c6ceb25cb8d8fdc53aa9d5b4e08a7994a"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
boolean_selector:
dependency: transitive
description:
@ -49,6 +57,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.0.2"
davi:
dependency: "direct main"
description:
name: davi
sha256: c47ff1da5bb0930a8c725030af2b14528e470ff2e28a863c4f3d710133bdbe7e
url: "https://pub.dev"
source: hosted
version: "3.2.0"
desktop_window:
dependency: "direct main"
description:
@ -200,6 +216,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.2.0"
tab_container:
dependency: "direct main"
description:
name: tab_container
sha256: "05782d9364332a55a5615825f83fa2595d21c2876dee2374bd32ff8b1031ee1d"
url: "https://pub.dev"
source: hosted
version: "2.0.0"
term_glyph:
dependency: transitive
description:

View File

@ -13,6 +13,8 @@ dependencies:
desktop_window: ^0.4.0
flutter_riverpod: ^2.1.3
uuid: ^3.0.7
tab_container: ^2.0.0
davi: ^3.2.0
dev_dependencies:
flutter_test: