Fully functional prototype with final UI design

This commit is contained in:
Ankit Mahato
2023-03-05 14:51:15 +05:30
parent aa1567851c
commit a9810ef7c2
16 changed files with 800 additions and 213 deletions

View File

@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../models/models.dart';
import '../providers/providers.dart';
import '../utils/utils.dart';
import 'styles.dart';
import '../consts.dart';
class CollectionPane extends ConsumerStatefulWidget {
@ -38,7 +39,10 @@ class _CollectionPaneState extends ConsumerState<CollectionPane> {
.read(activeItemIdStateProvider.notifier)
.update((state) => newId);
},
child: const Text('+ New'),
child: const Text(
'+ New',
style: textStyleButton,
),
),
],
),
@ -120,12 +124,9 @@ class RequestItem extends ConsumerStatefulWidget {
}
class _RequestItemState extends ConsumerState<RequestItem> {
late Color _color;
@override
void initState() {
super.initState();
_color = Colors.grey.shade50;
}
@override
@ -133,11 +134,11 @@ class _RequestItemState extends ConsumerState<RequestItem> {
final activeRequest = ref.watch(activeItemIdStateProvider);
bool isActiveId = activeRequest == widget.id;
return Material(
borderRadius: BorderRadius.circular(10.0),
borderRadius: borderRadius10,
elevation: isActiveId ? 2 : 0,
color: isActiveId ? Colors.grey.shade300 : _color,
color: isActiveId ? colorGrey300 : colorGrey50,
child: InkWell(
borderRadius: BorderRadius.circular(10.0),
borderRadius: borderRadius10,
onTap: () {
ref
.read(activeItemIdStateProvider.notifier)

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../providers/providers.dart';
import '../styles.dart';
import '../../consts.dart';
class EditRequestBody extends StatefulWidget {
@ -13,23 +14,92 @@ class EditRequestBody extends StatefulWidget {
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,
)
],
return Container(
decoration: tableContainerDecoration,
margin: p5,
child: Column(
children: [
Padding(
padding: p10,
child: Row(
children: const [
Text(
"Select Content Type:",
//style: Theme.of(context).textTheme.titleMedium,
),
SizedBox(
height: 30,
child: DropdownButtonBodyContentType(),
),
],
),
),
),
Expanded(
child: TextFieldEditor(),
)
],
const Divider(),
const Expanded(
child: Padding(
padding: p10,
child: TextFieldEditor(),
),
)
],
),
);
}
}
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 collection = ref.read(collectionStateNotifierProvider);
final idIdx = collection.indexWhere((m) => m.id == activeId);
final requestBodyContentType = ref.watch(collectionStateNotifierProvider
.select((value) => value[idIdx].requestBodyContentType));
return DropdownButton<ContentType>(
focusColor: colorGrey50,
value: requestBodyContentType,
icon: const Icon(
Icons.unfold_more_rounded,
size: 16,
),
elevation: 4,
style: codeStyle.copyWith(
color: Theme.of(context)
.colorScheme
.primary), //Theme.of(context).textTheme.bodyMedium,
underline: Container(
height: 0,
),
onChanged: (ContentType? value) {
ref
.read(collectionStateNotifierProvider.notifier)
.update(activeId!, requestBodyContentType: value);
},
borderRadius: borderRadius10,
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,
style: textStyleButton,
),
),
);
}).toList(),
);
}
}
@ -61,9 +131,10 @@ class _TextFieldEditorState extends ConsumerState<TextFieldEditor> {
.read(collectionStateNotifierProvider.notifier)
.update(activeId, requestBody: value);
},
style: codeStyle,
decoration: InputDecoration(
hintText: "Enter body",
hintStyle: TextStyle(color: Colors.grey.shade500),
hintText: "Enter content (body)",
hintStyle: codeStyle.copyWith(color: colorGrey500),
border: InputBorder.none,
),
keyboardType: TextInputType.multiline,
@ -72,55 +143,3 @@ class _TextFieldEditorState extends ConsumerState<TextFieldEditor> {
);
}
}
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

@ -1,5 +1,4 @@
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';
@ -30,8 +29,8 @@ class _EditorPaneRequestDetailsCardState
data: MultiSplitViewThemeData(
dividerThickness: 3,
dividerPainter: DividerPainters.background(
color: Colors.grey.shade200,
highlightedColor: Colors.grey.shade400,
color: colorGrey200,
highlightedColor: colorGrey400,
animationEnabled: false,
),
),

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:tab_container/tab_container.dart';
import '../../providers/providers.dart';
import '../styles.dart';
import 'tables.dart';
import 'body_editor.dart';
@ -28,28 +29,41 @@ class _EditRequestPaneState extends ConsumerState<EditRequestPane> {
.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 [
return TabContainer(
key: Key(activeId),
controller: _controller,
color: colorGrey100,
onEnd: () {
ref
.read(collectionStateNotifierProvider.notifier)
.update(activeId, requestTabIndex: _controller.index);
},
isStringTabs: false,
tabs: const [
Text(
'URL Params',
textAlign: TextAlign.center,
overflow: TextOverflow.fade,
style: textStyleButton,
),
Text(
'Headers',
textAlign: TextAlign.center,
overflow: TextOverflow.fade,
style: textStyleButton,
),
Text(
'Body',
],
children: const [
EditRequestURLParams(),
EditRequestHeaders(),
EditRequestBody(),
],
),
textAlign: TextAlign.center,
overflow: TextOverflow.fade,
style: textStyleButton,
)
],
children: const [
EditRequestURLParams(),
EditRequestHeaders(),
EditRequestBody(),
],
);
}

View File

@ -1,5 +1,13 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_json_view/flutter_json_view.dart';
import '../../providers/providers.dart';
import '../../models/request_model.dart';
import '../styles.dart';
import "../../utils/utils.dart";
import "../../consts.dart";
class ResponsePane extends ConsumerStatefulWidget {
const ResponsePane({super.key});
@ -16,12 +24,28 @@ class _ResponsePaneState extends ConsumerState<ResponsePane> {
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(5),
child: Column(
children: [],
),
);
final activeId = ref.watch(activeItemIdStateProvider);
final collection = ref.read(collectionStateNotifierProvider);
final idIdx = collection.indexWhere((m) => m.id == activeId);
final status = ref.watch(collectionStateNotifierProvider
.select((value) => (value[idIdx].responseStatus,
value[idIdx].message,
value[idIdx].responseModel)));
if (status.$0 == null) {
return const NotSentWidget();
}
if (status.$0 == -1) {
return ErrorMessage(message: status.$1);
}
else{
//var responseModel = ref
// .read(collectionStateNotifierProvider.notifier)
// .getRequestModel(activeId!)
// .responseModel;
return ResponseViewer(statusCode: status.$0!,
message: status.$1,
responseModel: status.$2!);
}
}
@override
@ -29,3 +53,237 @@ class _ResponsePaneState extends ConsumerState<ResponsePane> {
super.dispose();
}
}
class NotSentWidget extends StatelessWidget {
const NotSentWidget({super.key});
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.north_east_rounded,
size: 40,
color: colorErrorMsg,
),
Text(
'Not Sent',
style: Theme.of(context)
.textTheme
.titleMedium
?.copyWith(color: colorErrorMsg),
),
],
),
);
}
}
class ErrorMessage extends StatelessWidget {
const ErrorMessage({super.key, required this.message});
final String? message;
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.warning_rounded,
size: 40,
color: colorErrorMsg,
),
Text(
message ?? 'And error occurred.',
style: Theme.of(context)
.textTheme
.titleMedium
?.copyWith(color: colorErrorMsg),
),
],
),
);
}
}
final jsonViewTheme = JsonViewTheme(defaultTextStyle : codeStyle,
viewType: JsonViewType.collapsible,
backgroundColor: colorBg,
stringStyle: const TextStyle(color: Colors.brown),
closeIcon: const Icon(
Icons.arrow_drop_up,
size: 18,
),
openIcon : const Icon(
Icons.arrow_drop_down,
size: 18,
),
);
class ResponseViewer extends StatelessWidget {
final int statusCode;
final String? message;
final ResponseModel responseModel;
const ResponseViewer({
super.key,
required this.statusCode,
required this.message,
required this.responseModel,
});
@override
Widget build(BuildContext context) {
var requestHeaders = responseModel.requestHeaders ?? {};
var responseHeaders = responseModel.headers ?? {};
var body = responseModel.body ?? '';
return Padding(
padding: p10,
child: SingleChildScrollView(
child: Column(
children: [
Row(
children: const [
Text(
"Response",
style: textStyleButton,
),
],
),
const SizedBox(height: 5),
Row(
children: [
SizedBox(
width:50,
child: Text(
statusCode.toString(),
style: codeStyle.copyWith(
color: getResponseStatusCodeColor(statusCode),
fontWeight: FontWeight.bold,
),
),
),
Expanded(
child: Text(
message ?? "",
style: codeStyle.copyWith(
color: getResponseStatusCodeColor(statusCode),
fontWeight: FontWeight.bold,
),
),
),
SizedBox(
width:100,
child: Text(
humanizeDuration(responseModel.time),
style: codeStyle.copyWith(
color: getResponseStatusCodeColor(statusCode),
fontWeight: FontWeight.bold,
),
),
),
],
),
const SizedBox(height: 5),
Row(
children: [
Expanded(
child: Text(
"Request Headers (${requestHeaders.length} items)",
style: codeStyle,
),
),
if(requestHeaders.isNotEmpty) TextButton(
onPressed: () async {
await Clipboard.setData(ClipboardData(text: json.encode(requestHeaders)));
},
child: Row(
children: const [
Icon(
Icons.content_copy,
size: 20,
),
Text("Copy")
],
),
),
],
),
if(requestHeaders.isNotEmpty) const SizedBox(height: 5),
if(requestHeaders.isNotEmpty) JsonView.map(
requestHeaders,
theme: jsonViewTheme,
),
const SizedBox(height: 5),
Row(
children: [
Expanded(
child: Text(
"Response Headers (${responseHeaders.length} items)",
style: codeStyle,
),
),
if(responseHeaders.isNotEmpty) TextButton(
onPressed: () async {
await Clipboard.setData(ClipboardData(text: json.encode(responseHeaders)));
},
child: Row(
children: const [
Icon(
Icons.content_copy,
size: 20,
),
Text("Copy")
],
),
),
],
),
if(responseHeaders.isNotEmpty) const SizedBox(height: 5),
if(responseHeaders.isNotEmpty) JsonView.map(
responseHeaders,
theme: jsonViewTheme,
),
const SizedBox(height: 5),
Row(
children: [
Expanded(
child: Text(
"Body ${body.isEmpty ? '(empty)' : ''}",
style: codeStyle,
),
),
if(body.isNotEmpty) TextButton(
onPressed: () async {
await Clipboard.setData(ClipboardData(text: body));
},
child: Row(
children: const [
Icon(
Icons.content_copy,
size: 20,
),
Text("Copy")
],
),
),
],
),
const SizedBox(height: 5),
if(body.isNotEmpty && responseModel.contentType!.startsWith(JSON_MIMETYPE))
JsonView.string(
body,
theme: jsonViewTheme,
),
if(body.isNotEmpty && responseModel.contentType!.startsWith("text/"))
Text(body),
],
),
),
);
}
}

View File

@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:davi/davi.dart';
import '../../models/models.dart';
import '../../providers/providers.dart';
import '../styles.dart';
class EditRequestURLParams extends ConsumerStatefulWidget {
const EditRequestURLParams({Key? key}) : super(key: key);
@ -26,7 +27,11 @@ class EditRequestURLParamsState extends ConsumerState<EditRequestURLParams> {
return TextFormField(
key: Key("$activeId-$idx-params-k"),
initialValue: rows[idx].k,
decoration: const InputDecoration(hintText: "Add URL Parameter"),
style: codeStyle,
decoration: InputDecoration(
hintStyle: codeStyle,
hintText: "Add URL Parameter",
),
onChanged: (value) {
rows[idx] = rows[idx].copyWith(k: value);
_onFieldChange(activeId!);
@ -39,7 +44,11 @@ class EditRequestURLParamsState extends ConsumerState<EditRequestURLParams> {
return TextFormField(
key: Key("$activeId-$idx-params-v"),
initialValue: rows[idx].v,
decoration: const InputDecoration(hintText: "Add Value"),
style: codeStyle,
decoration: InputDecoration(
hintStyle: codeStyle,
hintText: "Add Value",
),
onChanged: (value) {
rows[idx] = rows[idx].copyWith(v: value);
_onFieldChange(activeId!);
@ -75,44 +84,36 @@ class EditRequestURLParamsState extends ConsumerState<EditRequestURLParams> {
),
],
);
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,
),
return Container(
decoration: tableContainerDecoration,
margin: p5,
child: Column(
children: [
Padding(
padding: p10,
child: Row(
children: [
ElevatedButton(
onPressed: () {
rows.add(const KVRow("", ""));
model.addRow(const KVRow("", ""));
},
child: const Text(
"+ Add Param",
style: textStyleButton,
),
),
],
),
child: Davi<KVRow>(model),
),
),
],
Expanded(
child: DaviTheme(
data: tableThemeData,
child: Davi<KVRow>(model),
),
),
],
),
);
}
}
@ -138,7 +139,11 @@ class EditRequestHeadersState extends ConsumerState<EditRequestHeaders> {
return TextFormField(
key: Key("$activeId-$idx-headers-k"),
initialValue: rows[idx].k,
decoration: const InputDecoration(hintText: "Add Header Name"),
style: codeStyle,
decoration: InputDecoration(
hintStyle: codeStyle,
hintText: "Add Header Name",
),
onChanged: (value) {
rows[idx] = rows[idx].copyWith(k: value);
_onFieldChange(activeId!);
@ -151,7 +156,11 @@ class EditRequestHeadersState extends ConsumerState<EditRequestHeaders> {
return TextFormField(
key: Key("$activeId-$idx-headers-v"),
initialValue: rows[idx].v,
decoration: const InputDecoration(hintText: "Add Header Value"),
style: codeStyle,
decoration: InputDecoration(
hintStyle: codeStyle,
hintText: "Add Header Value",
),
onChanged: (value) {
rows[idx] = rows[idx].copyWith(v: value);
_onFieldChange(activeId!);
@ -187,44 +196,36 @@ class EditRequestHeadersState extends ConsumerState<EditRequestHeaders> {
),
],
);
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,
),
return Container(
decoration: tableContainerDecoration,
margin: p5,
child: Column(
children: [
Padding(
padding: p10,
child: Row(
children: [
ElevatedButton(
onPressed: () {
rows.add(const KVRow("", ""));
model.addRow(const KVRow("", ""));
},
child: const Text(
"+ Add Header",
style: textStyleButton,
),
),
],
),
child: Davi<KVRow>(model),
),
),
],
Expanded(
child: DaviTheme(
data: tableThemeData,
child: Davi<KVRow>(model),
),
),
],
),
);
}
}

View File

@ -26,23 +26,23 @@ class _EditorPaneRequestURLCardState
return Card(
shape: cardShape,
child: Padding(
padding: EdgeInsets.symmetric(
padding: const EdgeInsets.symmetric(
vertical: 5,
horizontal: 20,
),
child: Row(
children: [
DropdownButtonHTTPMethod(),
const DropdownButtonHTTPMethod(),
const SizedBox(
width: 20,
),
Expanded(
const Expanded(
child: URLTextField(),
),
const SizedBox(
width: 20,
),
Container(
SizedBox(
height: 36,
child: ElevatedButton(
onPressed: () async {
@ -51,8 +51,11 @@ class _EditorPaneRequestURLCardState
.sendRequest(activeId!);
},
child: Row(
children: [
Text("Send"),
children: const [
Text(
"Send",
style: textStyleButton,
),
SizedBox(
width: 10,
),
@ -80,14 +83,9 @@ class DropdownButtonHTTPMethod extends ConsumerStatefulWidget {
class _DropdownButtonHTTPMethodState
extends ConsumerState<DropdownButtonHTTPMethod> {
//late HTTPVerb dropdownValue;
@override
void initState() {
super.initState();
//dropdownValue = ref
// .read(collectionStateNotifierProvider.notifier)
// .idxOfId(String id);
}
@override
@ -97,34 +95,31 @@ class _DropdownButtonHTTPMethodState
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,
focusColor: colorBg,
value: 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),
borderRadius: borderRadius10,
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)),
style: codeStyle.copyWith(
fontWeight: FontWeight.bold,
color: getHTTPMethodColor(value),
),
),
),
);
@ -157,9 +152,10 @@ class _URLTextFieldState extends ConsumerState<URLTextField> {
.read(collectionStateNotifierProvider.notifier)
.getRequestModel(activeId!)
.url,
style: codeStyle,
decoration: InputDecoration(
hintText: "Enter API endpoint like api.foss42.com/country/codes",
hintStyle: TextStyle(color: Colors.grey.shade500),
hintStyle: codeStyle.copyWith(color: colorGrey500),
border: InputBorder.none,
),
onChanged: (value) {

View File

@ -1,11 +1,51 @@
import 'package:flutter/material.dart';
import 'package:davi/davi.dart';
import 'package:google_fonts/google_fonts.dart';
final codeStyle = GoogleFonts.sourceCodePro();
const textStyleButton = TextStyle(fontWeight: FontWeight.bold);
const colorBg = Colors.white;
final colorGrey50 = Colors.grey.shade50;
final colorGrey100 = Colors.grey.shade100;
final colorGrey200 = Colors.grey.shade200;
final colorGrey300 = Colors.grey.shade300;
final colorGrey400 = Colors.grey.shade400;
final colorGrey500 = Colors.grey.shade500;
final colorErrorMsg = colorGrey500;
final borderRadius10 = BorderRadius.circular(10);
final cardShape = RoundedRectangleBorder(
side: const BorderSide(
color: Colors.white70,
width: 1,
),
borderRadius: BorderRadius.circular(10),
borderRadius: borderRadius10,
);
final colorErrorMsg = Colors.grey.shade500;
final tableContainerDecoration = BoxDecoration(
color: colorBg,
borderRadius: borderRadius10,
);
final tableThemeData = DaviThemeData(
columnDividerThickness: 1,
columnDividerColor: colorGrey100,
row: RowThemeData(dividerColor: colorGrey100),
decoration: const BoxDecoration(
border: Border(),
),
header: HeaderThemeData(
color: colorGrey50,
columnDividerColor: colorGrey100,
bottomBorderHeight: 1,
bottomBorderColor: colorGrey100,
),
headerCell: const HeaderCellThemeData(
alignment: Alignment.center,
textStyle: null,
),
);
const p5 = EdgeInsets.all(5);
const p10 = EdgeInsets.all(10);