From 2fc02eadd150901f886794c1ca3722f7d8d8656c Mon Sep 17 00:00:00 2001 From: DenserMeerkat Date: Sun, 21 Jul 2024 19:55:32 +0530 Subject: [PATCH] feat: history of requests --- lib/consts.dart | 21 ++- lib/models/history_meta_model.dart | 1 + lib/models/history_meta_model.freezed.dart | 27 +++- lib/models/history_meta_model.g.dart | 2 + lib/providers/collection_providers.dart | 27 ++++ lib/screens/common_widgets/button_navbar.dart | 43 +++--- lib/screens/history/history_details.dart | 43 ++++-- lib/screens/history/history_page.dart | 17 ++- lib/screens/history/history_pane.dart | 12 +- .../history_widgets/his_action_buttons.dart | 47 +++++++ .../history_widgets/his_bottombar.dart | 131 +++++++++++------- .../requests_page/request_response_tabs.dart | 11 +- lib/widgets/button_group_filled.dart | 60 ++++++++ ...st_response.dart => tabbar_segmented.dart} | 24 ++-- lib/widgets/table_request.dart | 10 ++ lib/widgets/widgets.dart | 3 +- 16 files changed, 355 insertions(+), 124 deletions(-) create mode 100644 lib/screens/history/history_widgets/his_action_buttons.dart create mode 100644 lib/widgets/button_group_filled.dart rename lib/widgets/{tabbar_request_response.dart => tabbar_segmented.dart} (77%) diff --git a/lib/consts.dart b/lib/consts.dart index 63be0705..ed2bc4e1 100644 --- a/lib/consts.dart +++ b/lib/consts.dart @@ -168,6 +168,7 @@ const kPb15 = EdgeInsets.only( const kPb70 = EdgeInsets.only( bottom: 70, ); +const kHSpacer2 = SizedBox(width: 2); const kHSpacer4 = SizedBox(width: 4); const kHSpacer5 = SizedBox(width: 5); const kHSpacer10 = SizedBox(width: 10); @@ -191,8 +192,8 @@ const kRandMax = 100000; const kSuggestionsMenuWidth = 300.0; const kSuggestionsMenuMaxHeight = 200.0; -const kReqResTabWidth = 280.0; -const kReqResTabHeight = 32.0; +const kSegmentedTabWidth = 140.0; +const kSegmentedTabHeight = 32.0; const kDataTableScrollbarTheme = ScrollbarThemeData( crossAxisMargin: -4, @@ -316,6 +317,20 @@ final kColorHttpMethodPut = Colors.amber.shade900; final kColorHttpMethodPatch = kColorHttpMethodPut; 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 { edit("Rename"), delete("Delete"), @@ -724,6 +739,8 @@ const kLabelSave = "Save"; const kLabelDownload = "Download"; const kLabelSaving = "Saving"; const kLabelSaved = "Saved"; +const kLabelCode = "Code"; +const kLabelDuplicate = "Duplicate"; // Request Pane const kLabelRequest = "Request"; const kLabelHideCode = "Hide Code"; diff --git a/lib/models/history_meta_model.dart b/lib/models/history_meta_model.dart index 58e68aee..9188b6ad 100644 --- a/lib/models/history_meta_model.dart +++ b/lib/models/history_meta_model.dart @@ -9,6 +9,7 @@ part 'history_meta_model.g.dart'; class HistoryMetaModel with _$HistoryMetaModel { const factory HistoryMetaModel({ required String historyId, + required String requestId, @Default("") String name, required String url, required HTTPVerb method, diff --git a/lib/models/history_meta_model.freezed.dart b/lib/models/history_meta_model.freezed.dart index fb7ba2eb..41901461 100644 --- a/lib/models/history_meta_model.freezed.dart +++ b/lib/models/history_meta_model.freezed.dart @@ -21,6 +21,7 @@ HistoryMetaModel _$HistoryMetaModelFromJson(Map json) { /// @nodoc mixin _$HistoryMetaModel { String get historyId => throw _privateConstructorUsedError; + String get requestId => throw _privateConstructorUsedError; String get name => throw _privateConstructorUsedError; String get url => throw _privateConstructorUsedError; HTTPVerb get method => throw _privateConstructorUsedError; @@ -41,6 +42,7 @@ abstract class $HistoryMetaModelCopyWith<$Res> { @useResult $Res call( {String historyId, + String requestId, String name, String url, HTTPVerb method, @@ -62,6 +64,7 @@ class _$HistoryMetaModelCopyWithImpl<$Res, $Val extends HistoryMetaModel> @override $Res call({ Object? historyId = null, + Object? requestId = null, Object? name = null, Object? url = null, Object? method = null, @@ -73,6 +76,10 @@ class _$HistoryMetaModelCopyWithImpl<$Res, $Val extends HistoryMetaModel> ? _value.historyId : historyId // ignore: cast_nullable_to_non_nullable as String, + requestId: null == requestId + ? _value.requestId + : requestId // ignore: cast_nullable_to_non_nullable + as String, name: null == name ? _value.name : name // ignore: cast_nullable_to_non_nullable @@ -107,6 +114,7 @@ abstract class _$$HistoryMetaModelImplCopyWith<$Res> @useResult $Res call( {String historyId, + String requestId, String name, String url, HTTPVerb method, @@ -126,6 +134,7 @@ class __$$HistoryMetaModelImplCopyWithImpl<$Res> @override $Res call({ Object? historyId = null, + Object? requestId = null, Object? name = null, Object? url = null, Object? method = null, @@ -137,6 +146,10 @@ class __$$HistoryMetaModelImplCopyWithImpl<$Res> ? _value.historyId : historyId // ignore: cast_nullable_to_non_nullable as String, + requestId: null == requestId + ? _value.requestId + : requestId // ignore: cast_nullable_to_non_nullable + as String, name: null == name ? _value.name : name // ignore: cast_nullable_to_non_nullable @@ -166,6 +179,7 @@ class __$$HistoryMetaModelImplCopyWithImpl<$Res> class _$HistoryMetaModelImpl implements _HistoryMetaModel { const _$HistoryMetaModelImpl( {required this.historyId, + required this.requestId, this.name = "", required this.url, required this.method, @@ -178,6 +192,8 @@ class _$HistoryMetaModelImpl implements _HistoryMetaModel { @override final String historyId; @override + final String requestId; + @override @JsonKey() final String name; @override @@ -191,7 +207,7 @@ class _$HistoryMetaModelImpl implements _HistoryMetaModel { @override 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 @@ -201,6 +217,8 @@ class _$HistoryMetaModelImpl implements _HistoryMetaModel { other is _$HistoryMetaModelImpl && (identical(other.historyId, historyId) || other.historyId == historyId) && + (identical(other.requestId, requestId) || + other.requestId == requestId) && (identical(other.name, name) || other.name == name) && (identical(other.url, url) || other.url == url) && (identical(other.method, method) || other.method == method) && @@ -212,8 +230,8 @@ class _$HistoryMetaModelImpl implements _HistoryMetaModel { @JsonKey(ignore: true) @override - int get hashCode => Object.hash( - runtimeType, historyId, name, url, method, responseStatus, timeStamp); + int get hashCode => Object.hash(runtimeType, historyId, requestId, name, url, + method, responseStatus, timeStamp); @JsonKey(ignore: true) @override @@ -233,6 +251,7 @@ class _$HistoryMetaModelImpl implements _HistoryMetaModel { abstract class _HistoryMetaModel implements HistoryMetaModel { const factory _HistoryMetaModel( {required final String historyId, + required final String requestId, final String name, required final String url, required final HTTPVerb method, @@ -245,6 +264,8 @@ abstract class _HistoryMetaModel implements HistoryMetaModel { @override String get historyId; @override + String get requestId; + @override String get name; @override String get url; diff --git a/lib/models/history_meta_model.g.dart b/lib/models/history_meta_model.g.dart index 67975edc..3d8af833 100644 --- a/lib/models/history_meta_model.g.dart +++ b/lib/models/history_meta_model.g.dart @@ -10,6 +10,7 @@ _$HistoryMetaModelImpl _$$HistoryMetaModelImplFromJson( Map json) => _$HistoryMetaModelImpl( historyId: json['historyId'] as String, + requestId: json['requestId'] as String, name: json['name'] as String? ?? "", url: json['url'] as String, method: $enumDecode(_$HTTPVerbEnumMap, json['method']), @@ -21,6 +22,7 @@ Map _$$HistoryMetaModelImplToJson( _$HistoryMetaModelImpl instance) => { 'historyId': instance.historyId, + 'requestId': instance.requestId, 'name': instance.name, 'url': instance.url, 'method': _$HTTPVerbEnumMap[instance.method]!, diff --git a/lib/providers/collection_providers.dart b/lib/providers/collection_providers.dart index 3bb2258d..02eead40 100644 --- a/lib/providers/collection_providers.dart +++ b/lib/providers/collection_providers.dart @@ -159,6 +159,32 @@ class CollectionStateNotifier 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( String id, { HTTPVerb? method, @@ -261,6 +287,7 @@ class CollectionStateNotifier historyId: newHistoryId, metaData: HistoryMetaModel( historyId: newHistoryId, + requestId: id, name: requestModel.name, url: substitutedHttpRequestModel.url, method: substitutedHttpRequestModel.method, diff --git a/lib/screens/common_widgets/button_navbar.dart b/lib/screens/common_widgets/button_navbar.dart index 2cefc201..f18c695c 100644 --- a/lib/screens/common_widgets/button_navbar.dart +++ b/lib/screens/common_widgets/button_navbar.dart @@ -27,22 +27,27 @@ class NavbarButton extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final bool isSelected = railIdx == buttonIdx; 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( cursor: SystemMouseCursors.click, child: GestureDetector( behavior: HitTestBehavior.translucent, - onTap: isSelected - ? null - : () { - if (buttonIdx != null) { - ref.read(navRailIndexStateProvider.notifier).state = - buttonIdx!; - if (railIdx > 2 && buttonIdx! <= 2) { - ref.read(leftDrawerStateProvider.notifier).state = false; - } - } - onTap?.call(); - }, + onTap: onPress, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ @@ -56,19 +61,7 @@ class NavbarButton extends ConsumerWidget { : TextButton.styleFrom( fixedSize: size, ), - onPressed: isSelected - ? null - : () { - if (buttonIdx != null) { - ref.read(navRailIndexStateProvider.notifier).state = - buttonIdx!; - if (railIdx > 2 && buttonIdx! <= 2) { - ref.read(leftDrawerStateProvider.notifier).state = - false; - } - } - onTap?.call(); - }, + onPressed: onPress, child: Icon( isSelected ? selectedIcon : icon, color: Theme.of(context).colorScheme.onSurfaceVariant, diff --git a/lib/screens/history/history_details.dart b/lib/screens/history/history_details.dart index 10f02251..ebdf81c1 100644 --- a/lib/screens/history/history_details.dart +++ b/lib/screens/history/history_details.dart @@ -25,7 +25,7 @@ class _HistoryDetailsState extends ConsumerState final codePaneVisible = ref.watch(historyCodePaneVisibleStateProvider); final TabController controller = - useTabController(initialLength: 2, vsync: this); + useTabController(initialLength: 3, vsync: this); return selectedHistoryRequest != null ? LayoutBuilder( @@ -42,28 +42,45 @@ class _HistoryDetailsState extends ConsumerState )), kVSpacer10, if (isCompact) ...[ - RequestResponseTabbar( + SegmentedTabbar( controller: controller, + tabs: const [ + Tab(text: kLabelRequest), + Tab(text: kLabelResponse), + Tab(text: kLabelCode), + ], ), kVSpacer10, Expanded( - child: TabBarView( - controller: controller, - children: [ - HistoryRequestPane( - isCompact: isCompact, - ), - const HistoryResponsePane(), - ], - )) + child: TabBarView( + controller: controller, + children: [ + HistoryRequestPane( + isCompact: isCompact, + ), + const HistoryResponsePane(), + const CodePane( + isHistoryRequest: true, + ), + ], + ), + ), + const HistoryPageBottombar() ] else ...[ Expanded( child: Padding( padding: kPh4, child: RequestDetailsCard( child: EqualSplitView( - leftWidget: HistoryRequestPane( - isCompact: isCompact, + leftWidget: Column( + children: [ + Expanded( + child: HistoryRequestPane( + isCompact: isCompact, + ), + ), + const HistoryPageBottombar(), + ], ), rightWidget: codePaneVisible ? const CodePane(isHistoryRequest: true) diff --git a/lib/screens/history/history_page.dart b/lib/screens/history/history_page.dart index 3787ce4f..0f288fb7 100644 --- a/lib/screens/history/history_page.dart +++ b/lib/screens/history/history_page.dart @@ -6,7 +6,6 @@ import 'package:apidash/providers/providers.dart'; import 'package:apidash/utils/utils.dart'; import 'history_pane.dart'; import 'history_viewer.dart'; -import 'history_widgets/history_widgets.dart'; class HistoryPage extends ConsumerWidget { const HistoryPage({ @@ -23,14 +22,14 @@ class HistoryPage extends ConsumerWidget { : 'History'; if (context.isMediumWindow) { return DrawerSplitView( - scaffoldKey: scaffoldKey, - mainContent: const HistoryViewer(), - title: Text(title), - leftDrawerContent: const HistoryPane(), - actions: const [SizedBox(width: 16)], - onDrawerChanged: (value) => - ref.read(leftDrawerStateProvider.notifier).state = value, - bottomNavigationBar: const HistoryPageBottombar()); + scaffoldKey: scaffoldKey, + mainContent: const HistoryViewer(), + title: Text(title), + leftDrawerContent: const HistoryPane(), + actions: const [SizedBox(width: 16)], + onDrawerChanged: (value) => + ref.read(leftDrawerStateProvider.notifier).state = value, + ); } return const Column( children: [ diff --git a/lib/screens/history/history_pane.dart b/lib/screens/history/history_pane.dart index d206dabb..8b090b95 100644 --- a/lib/screens/history/history_pane.dart +++ b/lib/screens/history/history_pane.dart @@ -65,6 +65,7 @@ class HistoryList extends HookConsumerWidget { child: HistoryExpansionTile( date: sortedHistoryKeys[index], requestGroups: requestGroups, + initiallyExpanded: index == 0, ), ); }, @@ -78,10 +79,12 @@ class HistoryExpansionTile extends StatefulHookConsumerWidget { super.key, required this.requestGroups, required this.date, + this.initiallyExpanded = false, }); final Map> requestGroups; final DateTime date; + final bool initiallyExpanded; @override ConsumerState createState() => @@ -95,8 +98,9 @@ class _HistoryExpansionTileState extends ConsumerState final animationController = useAnimationController( duration: const Duration(milliseconds: 200), 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 selectedGroupId = ref.watch(selectedRequestGroupIdStateProvider); return ExpansionTile( @@ -122,16 +126,16 @@ class _HistoryExpansionTileState extends ConsumerState ), onExpansionChanged: (value) { if (value) { - animationController.reverse(); - } else { animationController.forward(); + } else { + animationController.reverse(); } }, trailing: const SizedBox.shrink(), tilePadding: kPh8, shape: const RoundedRectangleBorder(), collapsedBackgroundColor: colorScheme.surfaceContainerLow, - initiallyExpanded: true, + initiallyExpanded: widget.initiallyExpanded, childrenPadding: kPv8 + kPe4, children: widget.requestGroups.values.map((item) { return Padding( diff --git a/lib/screens/history/history_widgets/his_action_buttons.dart b/lib/screens/history/history_widgets/his_action_buttons.dart new file mode 100644 index 00000000..436c9557 --- /dev/null +++ b/lib/screens/history/history_widgets/his_action_buttons.dart @@ -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", + ), + ]); + } +} diff --git a/lib/screens/history/history_widgets/his_bottombar.dart b/lib/screens/history/history_widgets/his_bottombar.dart index 5f146cc5..9945b7bc 100644 --- a/lib/screens/history/history_widgets/his_bottombar.dart +++ b/lib/screens/history/history_widgets/his_bottombar.dart @@ -1,8 +1,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:apidash/extensions/extensions.dart'; import 'package:apidash/providers/providers.dart'; import 'package:apidash/utils/utils.dart'; +import 'package:apidash/consts.dart'; import '../history_requests.dart'; +import 'his_action_buttons.dart'; class HistoryPageBottombar extends ConsumerWidget { const HistoryPageBottombar({ @@ -16,59 +19,87 @@ class HistoryPageBottombar extends ConsumerWidget { final requestGroup = getRequestGroup( historyMetas?.values.toList(), selectedRequestModel?.metaData); final requestCount = requestGroup.length; - return Padding( - padding: MediaQuery.of(context).viewInsets, - child: Container( - height: 60 + MediaQuery.paddingOf(context).bottom, - width: MediaQuery.sizeOf(context).width, - padding: EdgeInsets.only( - bottom: MediaQuery.paddingOf(context).bottom, - left: 16, - right: 16, - ), - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surface, - border: Border( - top: BorderSide( - color: Theme.of(context).colorScheme.onInverseSurface, - width: 1, - ), + + return Container( + height: 60 + MediaQuery.paddingOf(context).bottom, + width: MediaQuery.sizeOf(context).width, + padding: EdgeInsets.only( + bottom: MediaQuery.paddingOf(context).bottom, + left: 16, + right: 16, + ), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surface, + border: Border( + top: BorderSide( + color: Theme.of(context).colorScheme.onInverseSurface, + width: 1, ), ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - requestCount > 1 - ? Badge( - label: Text( - requestCount > 9 ? '9 +' : requestCount.toString(), - style: const TextStyle(fontWeight: FontWeight.bold), - ), - child: IconButton( - style: IconButton.styleFrom( - backgroundColor: - Theme.of(context).colorScheme.secondaryContainer, - ), - onPressed: () { - showModalBottomSheet( - context: context, - isScrollControlled: true, - builder: (context) { - return ConstrainedBox( - constraints: - const BoxConstraints(maxWidth: 500), - child: const HistorRequestsScrollableSheet()); - }, - ); - }, - icon: const Icon( - Icons.keyboard_arrow_up_rounded, - ), - ), - ) - : const SizedBox.shrink(), - ], + ), + child: context.isMediumWindow + ? Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + HistoryActionButtons(historyRequestModel: selectedRequestModel), + HistorySheetButton(requestCount: requestCount) + ], + ) + : Center( + child: HistoryActionButtons( + historyRequestModel: selectedRequestModel)), + ); + } +} + +class HistorySheetButton extends StatelessWidget { + const HistorySheetButton({ + super.key, + required this.requestCount, + }); + + final int requestCount; + + @override + Widget build(BuildContext context) { + final isCompact = context.isCompactWindow; + const icon = Icon(Icons.keyboard_arrow_up_rounded); + return Badge( + isLabelVisible: requestCount > 1, + label: Text( + 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, + ], + ), ), ); } diff --git a/lib/screens/mobile/requests_page/request_response_tabs.dart b/lib/screens/mobile/requests_page/request_response_tabs.dart index 36ad6c11..34a0ed90 100644 --- a/lib/screens/mobile/requests_page/request_response_tabs.dart +++ b/lib/screens/mobile/requests_page/request_response_tabs.dart @@ -19,13 +19,14 @@ class RequestResponseTabs extends StatelessWidget { child: EditorPaneRequestURLCard(), ), kVSpacer10, - RequestResponseTabbar( + SegmentedTabbar( controller: controller, + tabs: const [ + Tab(text: kLabelRequest), + Tab(text: kLabelResponse), + ], ), - Expanded( - child: RequestResponseTabviews( - controller: controller, - )) + Expanded(child: RequestResponseTabviews(controller: controller)) ], ); } diff --git a/lib/widgets/button_group_filled.dart b/lib/widgets/button_group_filled.dart new file mode 100644 index 00000000..24ae622d --- /dev/null +++ b/lib/widgets/button_group_filled.dart @@ -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 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 buttonWidgets = buttons + .map((button) => buildButton(button, showLabel: showLabel)) + .toList(); + + List 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, + ), + ); + }); + } +} diff --git a/lib/widgets/tabbar_request_response.dart b/lib/widgets/tabbar_segmented.dart similarity index 77% rename from lib/widgets/tabbar_request_response.dart rename to lib/widgets/tabbar_segmented.dart index 5a117120..bb58dc13 100644 --- a/lib/widgets/tabbar_request_response.dart +++ b/lib/widgets/tabbar_segmented.dart @@ -1,20 +1,27 @@ import 'package:flutter/material.dart'; import 'package:apidash/consts.dart'; -class RequestResponseTabbar extends StatelessWidget { - const RequestResponseTabbar({ +class SegmentedTabbar extends StatelessWidget { + const SegmentedTabbar({ super.key, required this.controller, + required this.tabs, + this.tabWidth = kSegmentedTabWidth, + this.tabHeight = kSegmentedTabHeight, }); final TabController controller; + final List tabs; + final double tabWidth; + final double tabHeight; @override Widget build(BuildContext context) { return Center( child: Container( - width: kReqResTabWidth, - height: kReqResTabHeight, + margin: kPh4, + width: tabWidth * tabs.length, + height: tabHeight, decoration: BoxDecoration( borderRadius: kBorderRadius20, border: Border.all( @@ -40,14 +47,7 @@ class RequestResponseTabbar extends StatelessWidget { color: Theme.of(context).colorScheme.primary, ), controller: controller, - tabs: const [ - Tab( - text: kLabelRequest, - ), - Tab( - text: kLabelResponse, - ), - ], + tabs: tabs, ), ), ), diff --git a/lib/widgets/table_request.dart b/lib/widgets/table_request.dart index bdde7c47..014f0c86 100644 --- a/lib/widgets/table_request.dart +++ b/lib/widgets/table_request.dart @@ -20,6 +20,10 @@ class RequestDataTable extends StatelessWidget { final clrScheme = Theme.of(context).colorScheme; final List columns = [ + const DataColumn2( + label: Text(''), + fixedWidth: 8, + ), DataColumn2( label: Text(keyName ?? kNameField), ), @@ -30,6 +34,10 @@ class RequestDataTable extends StatelessWidget { DataColumn2( label: Text(valueName ?? kNameValue), ), + const DataColumn2( + label: Text(''), + fixedWidth: 8, + ), ]; final fieldDecoration = InputDecoration( @@ -52,6 +60,7 @@ class RequestDataTable extends StatelessWidget { .map( (MapEntry entry) => DataRow( cells: [ + const DataCell(kHSpacer5), DataCell( ReadOnlyTextField( initialValue: entry.key, @@ -67,6 +76,7 @@ class RequestDataTable extends StatelessWidget { decoration: fieldDecoration, ), ), + const DataCell(kHSpacer5), ], ), ) diff --git a/lib/widgets/widgets.dart b/lib/widgets/widgets.dart index 9f0323aa..db9b860c 100644 --- a/lib/widgets/widgets.dart +++ b/lib/widgets/widgets.dart @@ -1,6 +1,7 @@ export 'button_clear_response.dart'; export 'button_copy.dart'; export 'button_discord.dart'; +export 'button_group_filled.dart'; export 'button_repo.dart'; export 'button_save_download.dart'; export 'button_send.dart'; @@ -48,7 +49,7 @@ export 'splitview_drawer.dart'; export 'splitview_dashboard.dart'; export 'splitview_equal.dart'; export 'splitview_history.dart'; -export 'tabbar_request_response.dart'; +export 'tabbar_segmented.dart'; export 'table_map.dart'; export 'table_request.dart'; export 'tabs.dart';