feat: history of requests

This commit is contained in:
DenserMeerkat
2024-07-21 19:55:32 +05:30
parent d9d60961f7
commit 2fc02eadd1
16 changed files with 355 additions and 124 deletions

View File

@@ -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";

View File

@@ -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,

View File

@@ -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;

View File

@@ -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]!,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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)

View File

@@ -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: [

View File

@@ -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(

View 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",
),
]);
}
}

View File

@@ -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,
],
),
), ),
); );
} }

View File

@@ -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,
))
], ],
); );
} }

View 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,
),
);
});
}
}

View File

@@ -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,
),
],
), ),
), ),
), ),

View File

@@ -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),
], ],
), ),
) )

View File

@@ -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';