mirror of
https://github.com/foss42/apidash.git
synced 2025-06-01 23:45:19 +08:00
Improved UI and App State Management
This commit is contained in:
@ -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),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -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(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
126
lib/components/editor_pane/body_editor.dart
Normal file
126
lib/components/editor_pane/body_editor.dart
Normal 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(),
|
||||
);
|
||||
}
|
||||
}
|
54
lib/components/editor_pane/details_card.dart
Normal file
54
lib/components/editor_pane/details_card.dart
Normal 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();
|
||||
}
|
||||
}
|
61
lib/components/editor_pane/request_pane.dart
Normal file
61
lib/components/editor_pane/request_pane.dart
Normal 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();
|
||||
}
|
||||
}
|
31
lib/components/editor_pane/response_pane.dart
Normal file
31
lib/components/editor_pane/response_pane.dart
Normal 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();
|
||||
}
|
||||
}
|
230
lib/components/editor_pane/tables.dart
Normal file
230
lib/components/editor_pane/tables.dart
Normal 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),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
172
lib/components/editor_pane/url_card.dart
Normal file
172
lib/components/editor_pane/url_card.dart
Normal 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);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
11
lib/components/styles.dart
Normal file
11
lib/components/styles.dart
Normal 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;
|
@ -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;
|
||||
|
38
lib/models/kvrow_model.dart
Normal file
38
lib/models/kvrow_model.dart
Normal 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;
|
||||
}
|
@ -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';
|
||||
|
77
lib/models/request_model.dart
Normal file
77
lib/models/request_model.dart
Normal 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");
|
||||
}
|
||||
}
|
@ -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
41
lib/utils/utils.dart
Normal 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;
|
||||
}
|
24
pubspec.lock
24
pubspec.lock
@ -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:
|
||||
|
@ -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:
|
||||
|
Reference in New Issue
Block a user