mirror of
https://github.com/foss42/apidash.git
synced 2025-12-10 07:08:08 +08:00
feat: history of requests
This commit is contained in:
@@ -168,6 +168,7 @@ const kPb15 = EdgeInsets.only(
|
|||||||
const kPb70 = EdgeInsets.only(
|
const kPb70 = EdgeInsets.only(
|
||||||
bottom: 70,
|
bottom: 70,
|
||||||
);
|
);
|
||||||
|
const kHSpacer2 = SizedBox(width: 2);
|
||||||
const kHSpacer4 = SizedBox(width: 4);
|
const kHSpacer4 = SizedBox(width: 4);
|
||||||
const kHSpacer5 = SizedBox(width: 5);
|
const kHSpacer5 = SizedBox(width: 5);
|
||||||
const kHSpacer10 = SizedBox(width: 10);
|
const kHSpacer10 = SizedBox(width: 10);
|
||||||
@@ -191,8 +192,8 @@ const kRandMax = 100000;
|
|||||||
const kSuggestionsMenuWidth = 300.0;
|
const kSuggestionsMenuWidth = 300.0;
|
||||||
const kSuggestionsMenuMaxHeight = 200.0;
|
const kSuggestionsMenuMaxHeight = 200.0;
|
||||||
|
|
||||||
const kReqResTabWidth = 280.0;
|
const kSegmentedTabWidth = 140.0;
|
||||||
const kReqResTabHeight = 32.0;
|
const kSegmentedTabHeight = 32.0;
|
||||||
|
|
||||||
const kDataTableScrollbarTheme = ScrollbarThemeData(
|
const kDataTableScrollbarTheme = ScrollbarThemeData(
|
||||||
crossAxisMargin: -4,
|
crossAxisMargin: -4,
|
||||||
@@ -316,6 +317,20 @@ final kColorHttpMethodPut = Colors.amber.shade900;
|
|||||||
final kColorHttpMethodPatch = kColorHttpMethodPut;
|
final kColorHttpMethodPatch = kColorHttpMethodPut;
|
||||||
final kColorHttpMethodDelete = Colors.red.shade800;
|
final kColorHttpMethodDelete = Colors.red.shade800;
|
||||||
|
|
||||||
|
class ButtonData {
|
||||||
|
ButtonData({
|
||||||
|
required this.label,
|
||||||
|
required this.icon,
|
||||||
|
this.onPressed,
|
||||||
|
this.tooltip = "",
|
||||||
|
});
|
||||||
|
|
||||||
|
final String label;
|
||||||
|
final IconData icon;
|
||||||
|
final VoidCallback? onPressed;
|
||||||
|
final String tooltip;
|
||||||
|
}
|
||||||
|
|
||||||
enum ItemMenuOption {
|
enum ItemMenuOption {
|
||||||
edit("Rename"),
|
edit("Rename"),
|
||||||
delete("Delete"),
|
delete("Delete"),
|
||||||
@@ -724,6 +739,8 @@ const kLabelSave = "Save";
|
|||||||
const kLabelDownload = "Download";
|
const kLabelDownload = "Download";
|
||||||
const kLabelSaving = "Saving";
|
const kLabelSaving = "Saving";
|
||||||
const kLabelSaved = "Saved";
|
const kLabelSaved = "Saved";
|
||||||
|
const kLabelCode = "Code";
|
||||||
|
const kLabelDuplicate = "Duplicate";
|
||||||
// Request Pane
|
// Request Pane
|
||||||
const kLabelRequest = "Request";
|
const kLabelRequest = "Request";
|
||||||
const kLabelHideCode = "Hide Code";
|
const kLabelHideCode = "Hide Code";
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ part 'history_meta_model.g.dart';
|
|||||||
class HistoryMetaModel with _$HistoryMetaModel {
|
class HistoryMetaModel with _$HistoryMetaModel {
|
||||||
const factory HistoryMetaModel({
|
const factory HistoryMetaModel({
|
||||||
required String historyId,
|
required String historyId,
|
||||||
|
required String requestId,
|
||||||
@Default("") String name,
|
@Default("") String name,
|
||||||
required String url,
|
required String url,
|
||||||
required HTTPVerb method,
|
required HTTPVerb method,
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ HistoryMetaModel _$HistoryMetaModelFromJson(Map<String, dynamic> json) {
|
|||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$HistoryMetaModel {
|
mixin _$HistoryMetaModel {
|
||||||
String get historyId => throw _privateConstructorUsedError;
|
String get historyId => throw _privateConstructorUsedError;
|
||||||
|
String get requestId => throw _privateConstructorUsedError;
|
||||||
String get name => throw _privateConstructorUsedError;
|
String get name => throw _privateConstructorUsedError;
|
||||||
String get url => throw _privateConstructorUsedError;
|
String get url => throw _privateConstructorUsedError;
|
||||||
HTTPVerb get method => throw _privateConstructorUsedError;
|
HTTPVerb get method => throw _privateConstructorUsedError;
|
||||||
@@ -41,6 +42,7 @@ abstract class $HistoryMetaModelCopyWith<$Res> {
|
|||||||
@useResult
|
@useResult
|
||||||
$Res call(
|
$Res call(
|
||||||
{String historyId,
|
{String historyId,
|
||||||
|
String requestId,
|
||||||
String name,
|
String name,
|
||||||
String url,
|
String url,
|
||||||
HTTPVerb method,
|
HTTPVerb method,
|
||||||
@@ -62,6 +64,7 @@ class _$HistoryMetaModelCopyWithImpl<$Res, $Val extends HistoryMetaModel>
|
|||||||
@override
|
@override
|
||||||
$Res call({
|
$Res call({
|
||||||
Object? historyId = null,
|
Object? historyId = null,
|
||||||
|
Object? requestId = null,
|
||||||
Object? name = null,
|
Object? name = null,
|
||||||
Object? url = null,
|
Object? url = null,
|
||||||
Object? method = null,
|
Object? method = null,
|
||||||
@@ -73,6 +76,10 @@ class _$HistoryMetaModelCopyWithImpl<$Res, $Val extends HistoryMetaModel>
|
|||||||
? _value.historyId
|
? _value.historyId
|
||||||
: historyId // ignore: cast_nullable_to_non_nullable
|
: historyId // ignore: cast_nullable_to_non_nullable
|
||||||
as String,
|
as String,
|
||||||
|
requestId: null == requestId
|
||||||
|
? _value.requestId
|
||||||
|
: requestId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
name: null == name
|
name: null == name
|
||||||
? _value.name
|
? _value.name
|
||||||
: name // ignore: cast_nullable_to_non_nullable
|
: name // ignore: cast_nullable_to_non_nullable
|
||||||
@@ -107,6 +114,7 @@ abstract class _$$HistoryMetaModelImplCopyWith<$Res>
|
|||||||
@useResult
|
@useResult
|
||||||
$Res call(
|
$Res call(
|
||||||
{String historyId,
|
{String historyId,
|
||||||
|
String requestId,
|
||||||
String name,
|
String name,
|
||||||
String url,
|
String url,
|
||||||
HTTPVerb method,
|
HTTPVerb method,
|
||||||
@@ -126,6 +134,7 @@ class __$$HistoryMetaModelImplCopyWithImpl<$Res>
|
|||||||
@override
|
@override
|
||||||
$Res call({
|
$Res call({
|
||||||
Object? historyId = null,
|
Object? historyId = null,
|
||||||
|
Object? requestId = null,
|
||||||
Object? name = null,
|
Object? name = null,
|
||||||
Object? url = null,
|
Object? url = null,
|
||||||
Object? method = null,
|
Object? method = null,
|
||||||
@@ -137,6 +146,10 @@ class __$$HistoryMetaModelImplCopyWithImpl<$Res>
|
|||||||
? _value.historyId
|
? _value.historyId
|
||||||
: historyId // ignore: cast_nullable_to_non_nullable
|
: historyId // ignore: cast_nullable_to_non_nullable
|
||||||
as String,
|
as String,
|
||||||
|
requestId: null == requestId
|
||||||
|
? _value.requestId
|
||||||
|
: requestId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
name: null == name
|
name: null == name
|
||||||
? _value.name
|
? _value.name
|
||||||
: name // ignore: cast_nullable_to_non_nullable
|
: name // ignore: cast_nullable_to_non_nullable
|
||||||
@@ -166,6 +179,7 @@ class __$$HistoryMetaModelImplCopyWithImpl<$Res>
|
|||||||
class _$HistoryMetaModelImpl implements _HistoryMetaModel {
|
class _$HistoryMetaModelImpl implements _HistoryMetaModel {
|
||||||
const _$HistoryMetaModelImpl(
|
const _$HistoryMetaModelImpl(
|
||||||
{required this.historyId,
|
{required this.historyId,
|
||||||
|
required this.requestId,
|
||||||
this.name = "",
|
this.name = "",
|
||||||
required this.url,
|
required this.url,
|
||||||
required this.method,
|
required this.method,
|
||||||
@@ -178,6 +192,8 @@ class _$HistoryMetaModelImpl implements _HistoryMetaModel {
|
|||||||
@override
|
@override
|
||||||
final String historyId;
|
final String historyId;
|
||||||
@override
|
@override
|
||||||
|
final String requestId;
|
||||||
|
@override
|
||||||
@JsonKey()
|
@JsonKey()
|
||||||
final String name;
|
final String name;
|
||||||
@override
|
@override
|
||||||
@@ -191,7 +207,7 @@ class _$HistoryMetaModelImpl implements _HistoryMetaModel {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'HistoryMetaModel(historyId: $historyId, name: $name, url: $url, method: $method, responseStatus: $responseStatus, timeStamp: $timeStamp)';
|
return 'HistoryMetaModel(historyId: $historyId, requestId: $requestId, name: $name, url: $url, method: $method, responseStatus: $responseStatus, timeStamp: $timeStamp)';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -201,6 +217,8 @@ class _$HistoryMetaModelImpl implements _HistoryMetaModel {
|
|||||||
other is _$HistoryMetaModelImpl &&
|
other is _$HistoryMetaModelImpl &&
|
||||||
(identical(other.historyId, historyId) ||
|
(identical(other.historyId, historyId) ||
|
||||||
other.historyId == historyId) &&
|
other.historyId == historyId) &&
|
||||||
|
(identical(other.requestId, requestId) ||
|
||||||
|
other.requestId == requestId) &&
|
||||||
(identical(other.name, name) || other.name == name) &&
|
(identical(other.name, name) || other.name == name) &&
|
||||||
(identical(other.url, url) || other.url == url) &&
|
(identical(other.url, url) || other.url == url) &&
|
||||||
(identical(other.method, method) || other.method == method) &&
|
(identical(other.method, method) || other.method == method) &&
|
||||||
@@ -212,8 +230,8 @@ class _$HistoryMetaModelImpl implements _HistoryMetaModel {
|
|||||||
|
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(
|
int get hashCode => Object.hash(runtimeType, historyId, requestId, name, url,
|
||||||
runtimeType, historyId, name, url, method, responseStatus, timeStamp);
|
method, responseStatus, timeStamp);
|
||||||
|
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
@override
|
@override
|
||||||
@@ -233,6 +251,7 @@ class _$HistoryMetaModelImpl implements _HistoryMetaModel {
|
|||||||
abstract class _HistoryMetaModel implements HistoryMetaModel {
|
abstract class _HistoryMetaModel implements HistoryMetaModel {
|
||||||
const factory _HistoryMetaModel(
|
const factory _HistoryMetaModel(
|
||||||
{required final String historyId,
|
{required final String historyId,
|
||||||
|
required final String requestId,
|
||||||
final String name,
|
final String name,
|
||||||
required final String url,
|
required final String url,
|
||||||
required final HTTPVerb method,
|
required final HTTPVerb method,
|
||||||
@@ -245,6 +264,8 @@ abstract class _HistoryMetaModel implements HistoryMetaModel {
|
|||||||
@override
|
@override
|
||||||
String get historyId;
|
String get historyId;
|
||||||
@override
|
@override
|
||||||
|
String get requestId;
|
||||||
|
@override
|
||||||
String get name;
|
String get name;
|
||||||
@override
|
@override
|
||||||
String get url;
|
String get url;
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ _$HistoryMetaModelImpl _$$HistoryMetaModelImplFromJson(
|
|||||||
Map<String, dynamic> json) =>
|
Map<String, dynamic> json) =>
|
||||||
_$HistoryMetaModelImpl(
|
_$HistoryMetaModelImpl(
|
||||||
historyId: json['historyId'] as String,
|
historyId: json['historyId'] as String,
|
||||||
|
requestId: json['requestId'] as String,
|
||||||
name: json['name'] as String? ?? "",
|
name: json['name'] as String? ?? "",
|
||||||
url: json['url'] as String,
|
url: json['url'] as String,
|
||||||
method: $enumDecode(_$HTTPVerbEnumMap, json['method']),
|
method: $enumDecode(_$HTTPVerbEnumMap, json['method']),
|
||||||
@@ -21,6 +22,7 @@ Map<String, dynamic> _$$HistoryMetaModelImplToJson(
|
|||||||
_$HistoryMetaModelImpl instance) =>
|
_$HistoryMetaModelImpl instance) =>
|
||||||
<String, dynamic>{
|
<String, dynamic>{
|
||||||
'historyId': instance.historyId,
|
'historyId': instance.historyId,
|
||||||
|
'requestId': instance.requestId,
|
||||||
'name': instance.name,
|
'name': instance.name,
|
||||||
'url': instance.url,
|
'url': instance.url,
|
||||||
'method': _$HTTPVerbEnumMap[instance.method]!,
|
'method': _$HTTPVerbEnumMap[instance.method]!,
|
||||||
|
|||||||
@@ -159,6 +159,32 @@ class CollectionStateNotifier
|
|||||||
ref.read(hasUnsavedChangesProvider.notifier).state = true;
|
ref.read(hasUnsavedChangesProvider.notifier).state = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void duplicateFromHistory(HistoryRequestModel historyRequestModel) {
|
||||||
|
final newId = getNewUuid();
|
||||||
|
|
||||||
|
var itemIds = ref.read(requestSequenceProvider);
|
||||||
|
var currentModel = historyRequestModel;
|
||||||
|
final newModel = RequestModel(
|
||||||
|
id: newId,
|
||||||
|
name: "${currentModel.metaData.name} (history)",
|
||||||
|
httpRequestModel: currentModel.httpRequestModel,
|
||||||
|
responseStatus: currentModel.metaData.responseStatus,
|
||||||
|
message: kResponseCodeReasons[currentModel.metaData.responseStatus],
|
||||||
|
httpResponseModel: currentModel.httpResponseModel,
|
||||||
|
isWorking: false,
|
||||||
|
sendingTime: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
itemIds.insert(0, newId);
|
||||||
|
var map = {...state!};
|
||||||
|
map[newId] = newModel;
|
||||||
|
state = map;
|
||||||
|
|
||||||
|
ref.read(requestSequenceProvider.notifier).state = [...itemIds];
|
||||||
|
ref.read(selectedIdStateProvider.notifier).state = newId;
|
||||||
|
ref.read(hasUnsavedChangesProvider.notifier).state = true;
|
||||||
|
}
|
||||||
|
|
||||||
void update(
|
void update(
|
||||||
String id, {
|
String id, {
|
||||||
HTTPVerb? method,
|
HTTPVerb? method,
|
||||||
@@ -261,6 +287,7 @@ class CollectionStateNotifier
|
|||||||
historyId: newHistoryId,
|
historyId: newHistoryId,
|
||||||
metaData: HistoryMetaModel(
|
metaData: HistoryMetaModel(
|
||||||
historyId: newHistoryId,
|
historyId: newHistoryId,
|
||||||
|
requestId: id,
|
||||||
name: requestModel.name,
|
name: requestModel.name,
|
||||||
url: substitutedHttpRequestModel.url,
|
url: substitutedHttpRequestModel.url,
|
||||||
method: substitutedHttpRequestModel.method,
|
method: substitutedHttpRequestModel.method,
|
||||||
|
|||||||
@@ -27,22 +27,27 @@ class NavbarButton extends ConsumerWidget {
|
|||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final bool isSelected = railIdx == buttonIdx;
|
final bool isSelected = railIdx == buttonIdx;
|
||||||
final Size size = isCompact ? const Size(56, 32) : const Size(65, 32);
|
final Size size = isCompact ? const Size(56, 32) : const Size(65, 32);
|
||||||
|
var onPress = isSelected
|
||||||
|
? null
|
||||||
|
: () {
|
||||||
|
if (buttonIdx != null) {
|
||||||
|
ref.read(navRailIndexStateProvider.notifier).state = buttonIdx!;
|
||||||
|
if ((railIdx > 2 && buttonIdx! <= 2) ||
|
||||||
|
!(ref
|
||||||
|
.read(mobileScaffoldKeyStateProvider)
|
||||||
|
.currentState
|
||||||
|
?.isDrawerOpen ??
|
||||||
|
true)) {
|
||||||
|
ref.read(leftDrawerStateProvider.notifier).state = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onTap?.call();
|
||||||
|
};
|
||||||
return MouseRegion(
|
return MouseRegion(
|
||||||
cursor: SystemMouseCursors.click,
|
cursor: SystemMouseCursors.click,
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
behavior: HitTestBehavior.translucent,
|
behavior: HitTestBehavior.translucent,
|
||||||
onTap: isSelected
|
onTap: onPress,
|
||||||
? null
|
|
||||||
: () {
|
|
||||||
if (buttonIdx != null) {
|
|
||||||
ref.read(navRailIndexStateProvider.notifier).state =
|
|
||||||
buttonIdx!;
|
|
||||||
if (railIdx > 2 && buttonIdx! <= 2) {
|
|
||||||
ref.read(leftDrawerStateProvider.notifier).state = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onTap?.call();
|
|
||||||
},
|
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
@@ -56,19 +61,7 @@ class NavbarButton extends ConsumerWidget {
|
|||||||
: TextButton.styleFrom(
|
: TextButton.styleFrom(
|
||||||
fixedSize: size,
|
fixedSize: size,
|
||||||
),
|
),
|
||||||
onPressed: isSelected
|
onPressed: onPress,
|
||||||
? null
|
|
||||||
: () {
|
|
||||||
if (buttonIdx != null) {
|
|
||||||
ref.read(navRailIndexStateProvider.notifier).state =
|
|
||||||
buttonIdx!;
|
|
||||||
if (railIdx > 2 && buttonIdx! <= 2) {
|
|
||||||
ref.read(leftDrawerStateProvider.notifier).state =
|
|
||||||
false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onTap?.call();
|
|
||||||
},
|
|
||||||
child: Icon(
|
child: Icon(
|
||||||
isSelected ? selectedIcon : icon,
|
isSelected ? selectedIcon : icon,
|
||||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ class _HistoryDetailsState extends ConsumerState<HistoryDetails>
|
|||||||
final codePaneVisible = ref.watch(historyCodePaneVisibleStateProvider);
|
final codePaneVisible = ref.watch(historyCodePaneVisibleStateProvider);
|
||||||
|
|
||||||
final TabController controller =
|
final TabController controller =
|
||||||
useTabController(initialLength: 2, vsync: this);
|
useTabController(initialLength: 3, vsync: this);
|
||||||
|
|
||||||
return selectedHistoryRequest != null
|
return selectedHistoryRequest != null
|
||||||
? LayoutBuilder(
|
? LayoutBuilder(
|
||||||
@@ -42,28 +42,45 @@ class _HistoryDetailsState extends ConsumerState<HistoryDetails>
|
|||||||
)),
|
)),
|
||||||
kVSpacer10,
|
kVSpacer10,
|
||||||
if (isCompact) ...[
|
if (isCompact) ...[
|
||||||
RequestResponseTabbar(
|
SegmentedTabbar(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
|
tabs: const [
|
||||||
|
Tab(text: kLabelRequest),
|
||||||
|
Tab(text: kLabelResponse),
|
||||||
|
Tab(text: kLabelCode),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
kVSpacer10,
|
kVSpacer10,
|
||||||
Expanded(
|
Expanded(
|
||||||
child: TabBarView(
|
child: TabBarView(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
children: [
|
children: [
|
||||||
HistoryRequestPane(
|
HistoryRequestPane(
|
||||||
isCompact: isCompact,
|
isCompact: isCompact,
|
||||||
),
|
),
|
||||||
const HistoryResponsePane(),
|
const HistoryResponsePane(),
|
||||||
],
|
const CodePane(
|
||||||
))
|
isHistoryRequest: true,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const HistoryPageBottombar()
|
||||||
] else ...[
|
] else ...[
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: kPh4,
|
padding: kPh4,
|
||||||
child: RequestDetailsCard(
|
child: RequestDetailsCard(
|
||||||
child: EqualSplitView(
|
child: EqualSplitView(
|
||||||
leftWidget: HistoryRequestPane(
|
leftWidget: Column(
|
||||||
isCompact: isCompact,
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: HistoryRequestPane(
|
||||||
|
isCompact: isCompact,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const HistoryPageBottombar(),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
rightWidget: codePaneVisible
|
rightWidget: codePaneVisible
|
||||||
? const CodePane(isHistoryRequest: true)
|
? const CodePane(isHistoryRequest: true)
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import 'package:apidash/providers/providers.dart';
|
|||||||
import 'package:apidash/utils/utils.dart';
|
import 'package:apidash/utils/utils.dart';
|
||||||
import 'history_pane.dart';
|
import 'history_pane.dart';
|
||||||
import 'history_viewer.dart';
|
import 'history_viewer.dart';
|
||||||
import 'history_widgets/history_widgets.dart';
|
|
||||||
|
|
||||||
class HistoryPage extends ConsumerWidget {
|
class HistoryPage extends ConsumerWidget {
|
||||||
const HistoryPage({
|
const HistoryPage({
|
||||||
@@ -23,14 +22,14 @@ class HistoryPage extends ConsumerWidget {
|
|||||||
: 'History';
|
: 'History';
|
||||||
if (context.isMediumWindow) {
|
if (context.isMediumWindow) {
|
||||||
return DrawerSplitView(
|
return DrawerSplitView(
|
||||||
scaffoldKey: scaffoldKey,
|
scaffoldKey: scaffoldKey,
|
||||||
mainContent: const HistoryViewer(),
|
mainContent: const HistoryViewer(),
|
||||||
title: Text(title),
|
title: Text(title),
|
||||||
leftDrawerContent: const HistoryPane(),
|
leftDrawerContent: const HistoryPane(),
|
||||||
actions: const [SizedBox(width: 16)],
|
actions: const [SizedBox(width: 16)],
|
||||||
onDrawerChanged: (value) =>
|
onDrawerChanged: (value) =>
|
||||||
ref.read(leftDrawerStateProvider.notifier).state = value,
|
ref.read(leftDrawerStateProvider.notifier).state = value,
|
||||||
bottomNavigationBar: const HistoryPageBottombar());
|
);
|
||||||
}
|
}
|
||||||
return const Column(
|
return const Column(
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ class HistoryList extends HookConsumerWidget {
|
|||||||
child: HistoryExpansionTile(
|
child: HistoryExpansionTile(
|
||||||
date: sortedHistoryKeys[index],
|
date: sortedHistoryKeys[index],
|
||||||
requestGroups: requestGroups,
|
requestGroups: requestGroups,
|
||||||
|
initiallyExpanded: index == 0,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -78,10 +79,12 @@ class HistoryExpansionTile extends StatefulHookConsumerWidget {
|
|||||||
super.key,
|
super.key,
|
||||||
required this.requestGroups,
|
required this.requestGroups,
|
||||||
required this.date,
|
required this.date,
|
||||||
|
this.initiallyExpanded = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
final Map<String, List<HistoryMetaModel>> requestGroups;
|
final Map<String, List<HistoryMetaModel>> requestGroups;
|
||||||
final DateTime date;
|
final DateTime date;
|
||||||
|
final bool initiallyExpanded;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ConsumerState<HistoryExpansionTile> createState() =>
|
ConsumerState<HistoryExpansionTile> createState() =>
|
||||||
@@ -95,8 +98,9 @@ class _HistoryExpansionTileState extends ConsumerState<HistoryExpansionTile>
|
|||||||
final animationController = useAnimationController(
|
final animationController = useAnimationController(
|
||||||
duration: const Duration(milliseconds: 200),
|
duration: const Duration(milliseconds: 200),
|
||||||
vsync: this,
|
vsync: this,
|
||||||
|
initialValue: widget.initiallyExpanded ? 1.0 : 0.0,
|
||||||
);
|
);
|
||||||
final animation = Tween(begin: 0.25, end: 0.0).animate(animationController);
|
final animation = Tween(begin: 0.0, end: 0.25).animate(animationController);
|
||||||
final colorScheme = Theme.of(context).colorScheme;
|
final colorScheme = Theme.of(context).colorScheme;
|
||||||
final selectedGroupId = ref.watch(selectedRequestGroupIdStateProvider);
|
final selectedGroupId = ref.watch(selectedRequestGroupIdStateProvider);
|
||||||
return ExpansionTile(
|
return ExpansionTile(
|
||||||
@@ -122,16 +126,16 @@ class _HistoryExpansionTileState extends ConsumerState<HistoryExpansionTile>
|
|||||||
),
|
),
|
||||||
onExpansionChanged: (value) {
|
onExpansionChanged: (value) {
|
||||||
if (value) {
|
if (value) {
|
||||||
animationController.reverse();
|
|
||||||
} else {
|
|
||||||
animationController.forward();
|
animationController.forward();
|
||||||
|
} else {
|
||||||
|
animationController.reverse();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
trailing: const SizedBox.shrink(),
|
trailing: const SizedBox.shrink(),
|
||||||
tilePadding: kPh8,
|
tilePadding: kPh8,
|
||||||
shape: const RoundedRectangleBorder(),
|
shape: const RoundedRectangleBorder(),
|
||||||
collapsedBackgroundColor: colorScheme.surfaceContainerLow,
|
collapsedBackgroundColor: colorScheme.surfaceContainerLow,
|
||||||
initiallyExpanded: true,
|
initiallyExpanded: widget.initiallyExpanded,
|
||||||
childrenPadding: kPv8 + kPe4,
|
childrenPadding: kPv8 + kPe4,
|
||||||
children: widget.requestGroups.values.map((item) {
|
children: widget.requestGroups.values.map((item) {
|
||||||
return Padding(
|
return Padding(
|
||||||
|
|||||||
47
lib/screens/history/history_widgets/his_action_buttons.dart
Normal file
47
lib/screens/history/history_widgets/his_action_buttons.dart
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:apidash/providers/providers.dart';
|
||||||
|
import 'package:apidash/widgets/widgets.dart';
|
||||||
|
import 'package:apidash/models/models.dart';
|
||||||
|
import 'package:apidash/consts.dart';
|
||||||
|
|
||||||
|
class HistoryActionButtons extends ConsumerWidget {
|
||||||
|
const HistoryActionButtons({super.key, this.historyRequestModel});
|
||||||
|
|
||||||
|
final HistoryRequestModel? historyRequestModel;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final collectionStateNotifier = ref.watch(collectionStateNotifierProvider);
|
||||||
|
final isAvailable = collectionStateNotifier?.values.any((element) =>
|
||||||
|
element.id == historyRequestModel?.metaData.requestId) ??
|
||||||
|
false;
|
||||||
|
final requestId = historyRequestModel?.metaData.requestId;
|
||||||
|
return FilledButtonGroup(buttons: [
|
||||||
|
ButtonData(
|
||||||
|
icon: Icons.copy_rounded,
|
||||||
|
label: kLabelDuplicate,
|
||||||
|
onPressed: requestId != null
|
||||||
|
? () {
|
||||||
|
ref
|
||||||
|
.read(collectionStateNotifierProvider.notifier)
|
||||||
|
.duplicateFromHistory(historyRequestModel!);
|
||||||
|
ref.read(navRailIndexStateProvider.notifier).state = 0;
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
tooltip: "Duplicate Request",
|
||||||
|
),
|
||||||
|
ButtonData(
|
||||||
|
icon: Icons.north_east_rounded,
|
||||||
|
label: kLabelRequest,
|
||||||
|
onPressed: isAvailable && requestId != null
|
||||||
|
? () {
|
||||||
|
ref.read(selectedIdStateProvider.notifier).state = requestId;
|
||||||
|
ref.read(navRailIndexStateProvider.notifier).state = 0;
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
tooltip: isAvailable ? "Go to Request" : "Couldn't find Request",
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,11 @@
|
|||||||
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 'package:apidash/extensions/extensions.dart';
|
||||||
import 'package:apidash/providers/providers.dart';
|
import 'package:apidash/providers/providers.dart';
|
||||||
import 'package:apidash/utils/utils.dart';
|
import 'package:apidash/utils/utils.dart';
|
||||||
|
import 'package:apidash/consts.dart';
|
||||||
import '../history_requests.dart';
|
import '../history_requests.dart';
|
||||||
|
import 'his_action_buttons.dart';
|
||||||
|
|
||||||
class HistoryPageBottombar extends ConsumerWidget {
|
class HistoryPageBottombar extends ConsumerWidget {
|
||||||
const HistoryPageBottombar({
|
const HistoryPageBottombar({
|
||||||
@@ -16,59 +19,87 @@ class HistoryPageBottombar extends ConsumerWidget {
|
|||||||
final requestGroup = getRequestGroup(
|
final requestGroup = getRequestGroup(
|
||||||
historyMetas?.values.toList(), selectedRequestModel?.metaData);
|
historyMetas?.values.toList(), selectedRequestModel?.metaData);
|
||||||
final requestCount = requestGroup.length;
|
final requestCount = requestGroup.length;
|
||||||
return Padding(
|
|
||||||
padding: MediaQuery.of(context).viewInsets,
|
return Container(
|
||||||
child: Container(
|
height: 60 + MediaQuery.paddingOf(context).bottom,
|
||||||
height: 60 + MediaQuery.paddingOf(context).bottom,
|
width: MediaQuery.sizeOf(context).width,
|
||||||
width: MediaQuery.sizeOf(context).width,
|
padding: EdgeInsets.only(
|
||||||
padding: EdgeInsets.only(
|
bottom: MediaQuery.paddingOf(context).bottom,
|
||||||
bottom: MediaQuery.paddingOf(context).bottom,
|
left: 16,
|
||||||
left: 16,
|
right: 16,
|
||||||
right: 16,
|
),
|
||||||
),
|
decoration: BoxDecoration(
|
||||||
decoration: BoxDecoration(
|
color: Theme.of(context).colorScheme.surface,
|
||||||
color: Theme.of(context).colorScheme.surface,
|
border: Border(
|
||||||
border: Border(
|
top: BorderSide(
|
||||||
top: BorderSide(
|
color: Theme.of(context).colorScheme.onInverseSurface,
|
||||||
color: Theme.of(context).colorScheme.onInverseSurface,
|
width: 1,
|
||||||
width: 1,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: Row(
|
),
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
child: context.isMediumWindow
|
||||||
children: [
|
? Row(
|
||||||
requestCount > 1
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
? Badge(
|
children: [
|
||||||
label: Text(
|
HistoryActionButtons(historyRequestModel: selectedRequestModel),
|
||||||
requestCount > 9 ? '9 +' : requestCount.toString(),
|
HistorySheetButton(requestCount: requestCount)
|
||||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
],
|
||||||
),
|
)
|
||||||
child: IconButton(
|
: Center(
|
||||||
style: IconButton.styleFrom(
|
child: HistoryActionButtons(
|
||||||
backgroundColor:
|
historyRequestModel: selectedRequestModel)),
|
||||||
Theme.of(context).colorScheme.secondaryContainer,
|
);
|
||||||
),
|
}
|
||||||
onPressed: () {
|
}
|
||||||
showModalBottomSheet(
|
|
||||||
context: context,
|
class HistorySheetButton extends StatelessWidget {
|
||||||
isScrollControlled: true,
|
const HistorySheetButton({
|
||||||
builder: (context) {
|
super.key,
|
||||||
return ConstrainedBox(
|
required this.requestCount,
|
||||||
constraints:
|
});
|
||||||
const BoxConstraints(maxWidth: 500),
|
|
||||||
child: const HistorRequestsScrollableSheet());
|
final int requestCount;
|
||||||
},
|
|
||||||
);
|
@override
|
||||||
},
|
Widget build(BuildContext context) {
|
||||||
icon: const Icon(
|
final isCompact = context.isCompactWindow;
|
||||||
Icons.keyboard_arrow_up_rounded,
|
const icon = Icon(Icons.keyboard_arrow_up_rounded);
|
||||||
),
|
return Badge(
|
||||||
),
|
isLabelVisible: requestCount > 1,
|
||||||
)
|
label: Text(
|
||||||
: const SizedBox.shrink(),
|
requestCount > 9 ? '9+' : requestCount.toString(),
|
||||||
],
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
child: FilledButton.tonal(
|
||||||
|
style: FilledButton.styleFrom(
|
||||||
|
minimumSize: const Size(44, 44),
|
||||||
|
padding: isCompact ? kP4 : const EdgeInsets.fromLTRB(16, 12, 8, 12),
|
||||||
),
|
),
|
||||||
|
onPressed: requestCount > 1
|
||||||
|
? () {
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
isScrollControlled: true,
|
||||||
|
builder: (context) {
|
||||||
|
return ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(maxWidth: 500),
|
||||||
|
child: const HistorRequestsScrollableSheet());
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
child: isCompact
|
||||||
|
? icon
|
||||||
|
: const Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Show All",
|
||||||
|
style: kTextStyleButton,
|
||||||
|
),
|
||||||
|
kHSpacer5,
|
||||||
|
icon,
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,13 +19,14 @@ class RequestResponseTabs extends StatelessWidget {
|
|||||||
child: EditorPaneRequestURLCard(),
|
child: EditorPaneRequestURLCard(),
|
||||||
),
|
),
|
||||||
kVSpacer10,
|
kVSpacer10,
|
||||||
RequestResponseTabbar(
|
SegmentedTabbar(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
|
tabs: const [
|
||||||
|
Tab(text: kLabelRequest),
|
||||||
|
Tab(text: kLabelResponse),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(child: RequestResponseTabviews(controller: controller))
|
||||||
child: RequestResponseTabviews(
|
|
||||||
controller: controller,
|
|
||||||
))
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
60
lib/widgets/button_group_filled.dart
Normal file
60
lib/widgets/button_group_filled.dart
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:apidash/consts.dart';
|
||||||
|
|
||||||
|
class FilledButtonGroup extends StatelessWidget {
|
||||||
|
const FilledButtonGroup({super.key, required this.buttons});
|
||||||
|
|
||||||
|
final List<ButtonData> buttons;
|
||||||
|
|
||||||
|
Widget buildButton(ButtonData buttonData, {bool showLabel = true}) {
|
||||||
|
final icon = Icon(buttonData.icon, size: 20);
|
||||||
|
final label = Text(
|
||||||
|
buttonData.label,
|
||||||
|
style: kTextStyleButton,
|
||||||
|
);
|
||||||
|
return Tooltip(
|
||||||
|
message: buttonData.tooltip,
|
||||||
|
child: FilledButton.icon(
|
||||||
|
style: FilledButton.styleFrom(
|
||||||
|
minimumSize: const Size(44, 44),
|
||||||
|
padding: kPh12,
|
||||||
|
shape: const ContinuousRectangleBorder()),
|
||||||
|
onPressed: buttonData.onPressed,
|
||||||
|
label: showLabel
|
||||||
|
? Row(
|
||||||
|
children: [
|
||||||
|
icon,
|
||||||
|
kHSpacer4,
|
||||||
|
label,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: icon,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return LayoutBuilder(builder: (context, constraints) {
|
||||||
|
final showLabel = constraints.maxWidth > buttons.length * 110;
|
||||||
|
List<Widget> buttonWidgets = buttons
|
||||||
|
.map((button) => buildButton(button, showLabel: showLabel))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
List<Widget> buttonsWithSpacers = [];
|
||||||
|
for (int i = 0; i < buttonWidgets.length; i++) {
|
||||||
|
buttonsWithSpacers.add(buttonWidgets[i]);
|
||||||
|
if (i < buttonWidgets.length - 1) {
|
||||||
|
buttonsWithSpacers.add(kHSpacer2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ClipRRect(
|
||||||
|
borderRadius: kBorderRadius20,
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: buttonsWithSpacers,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,20 +1,27 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:apidash/consts.dart';
|
import 'package:apidash/consts.dart';
|
||||||
|
|
||||||
class RequestResponseTabbar extends StatelessWidget {
|
class SegmentedTabbar extends StatelessWidget {
|
||||||
const RequestResponseTabbar({
|
const SegmentedTabbar({
|
||||||
super.key,
|
super.key,
|
||||||
required this.controller,
|
required this.controller,
|
||||||
|
required this.tabs,
|
||||||
|
this.tabWidth = kSegmentedTabWidth,
|
||||||
|
this.tabHeight = kSegmentedTabHeight,
|
||||||
});
|
});
|
||||||
|
|
||||||
final TabController controller;
|
final TabController controller;
|
||||||
|
final List<Widget> tabs;
|
||||||
|
final double tabWidth;
|
||||||
|
final double tabHeight;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Center(
|
return Center(
|
||||||
child: Container(
|
child: Container(
|
||||||
width: kReqResTabWidth,
|
margin: kPh4,
|
||||||
height: kReqResTabHeight,
|
width: tabWidth * tabs.length,
|
||||||
|
height: tabHeight,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: kBorderRadius20,
|
borderRadius: kBorderRadius20,
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
@@ -40,14 +47,7 @@ class RequestResponseTabbar extends StatelessWidget {
|
|||||||
color: Theme.of(context).colorScheme.primary,
|
color: Theme.of(context).colorScheme.primary,
|
||||||
),
|
),
|
||||||
controller: controller,
|
controller: controller,
|
||||||
tabs: const <Widget>[
|
tabs: tabs,
|
||||||
Tab(
|
|
||||||
text: kLabelRequest,
|
|
||||||
),
|
|
||||||
Tab(
|
|
||||||
text: kLabelResponse,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -20,6 +20,10 @@ class RequestDataTable extends StatelessWidget {
|
|||||||
final clrScheme = Theme.of(context).colorScheme;
|
final clrScheme = Theme.of(context).colorScheme;
|
||||||
|
|
||||||
final List<DataColumn> columns = [
|
final List<DataColumn> columns = [
|
||||||
|
const DataColumn2(
|
||||||
|
label: Text(''),
|
||||||
|
fixedWidth: 8,
|
||||||
|
),
|
||||||
DataColumn2(
|
DataColumn2(
|
||||||
label: Text(keyName ?? kNameField),
|
label: Text(keyName ?? kNameField),
|
||||||
),
|
),
|
||||||
@@ -30,6 +34,10 @@ class RequestDataTable extends StatelessWidget {
|
|||||||
DataColumn2(
|
DataColumn2(
|
||||||
label: Text(valueName ?? kNameValue),
|
label: Text(valueName ?? kNameValue),
|
||||||
),
|
),
|
||||||
|
const DataColumn2(
|
||||||
|
label: Text(''),
|
||||||
|
fixedWidth: 8,
|
||||||
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
final fieldDecoration = InputDecoration(
|
final fieldDecoration = InputDecoration(
|
||||||
@@ -52,6 +60,7 @@ class RequestDataTable extends StatelessWidget {
|
|||||||
.map<DataRow>(
|
.map<DataRow>(
|
||||||
(MapEntry<String, String> entry) => DataRow(
|
(MapEntry<String, String> entry) => DataRow(
|
||||||
cells: <DataCell>[
|
cells: <DataCell>[
|
||||||
|
const DataCell(kHSpacer5),
|
||||||
DataCell(
|
DataCell(
|
||||||
ReadOnlyTextField(
|
ReadOnlyTextField(
|
||||||
initialValue: entry.key,
|
initialValue: entry.key,
|
||||||
@@ -67,6 +76,7 @@ class RequestDataTable extends StatelessWidget {
|
|||||||
decoration: fieldDecoration,
|
decoration: fieldDecoration,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
const DataCell(kHSpacer5),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
export 'button_clear_response.dart';
|
export 'button_clear_response.dart';
|
||||||
export 'button_copy.dart';
|
export 'button_copy.dart';
|
||||||
export 'button_discord.dart';
|
export 'button_discord.dart';
|
||||||
|
export 'button_group_filled.dart';
|
||||||
export 'button_repo.dart';
|
export 'button_repo.dart';
|
||||||
export 'button_save_download.dart';
|
export 'button_save_download.dart';
|
||||||
export 'button_send.dart';
|
export 'button_send.dart';
|
||||||
@@ -48,7 +49,7 @@ export 'splitview_drawer.dart';
|
|||||||
export 'splitview_dashboard.dart';
|
export 'splitview_dashboard.dart';
|
||||||
export 'splitview_equal.dart';
|
export 'splitview_equal.dart';
|
||||||
export 'splitview_history.dart';
|
export 'splitview_history.dart';
|
||||||
export 'tabbar_request_response.dart';
|
export 'tabbar_segmented.dart';
|
||||||
export 'table_map.dart';
|
export 'table_map.dart';
|
||||||
export 'table_request.dart';
|
export 'table_request.dart';
|
||||||
export 'tabs.dart';
|
export 'tabs.dart';
|
||||||
|
|||||||
Reference in New Issue
Block a user