From d9d60961f76d8272626a291852a90bedf930264c Mon Sep 17 00:00:00 2001 From: DenserMeerkat Date: Sun, 21 Jul 2024 04:34:12 +0530 Subject: [PATCH] wip: history requests sheet --- lib/consts.dart | 6 +- .../common_widgets/sidebar_header.dart | 2 +- lib/screens/history/history_details.dart | 4 +- lib/screens/history/history_page.dart | 17 +- lib/screens/history/history_pane.dart | 150 +++++++++++++----- lib/screens/history/history_requests.dart | 136 +++++++++++++++- .../history_widgets/his_bottombar.dart | 75 +++++++++ .../his_request_pane.dart | 0 .../his_response_pane.dart | 0 .../history_widgets/his_sidebar_header.dart | 50 ++++++ .../his_url_card.dart} | 0 .../history_widgets/history_widgets.dart | 5 + 12 files changed, 380 insertions(+), 65 deletions(-) create mode 100644 lib/screens/history/history_widgets/his_bottombar.dart rename lib/screens/history/{details_pane => history_widgets}/his_request_pane.dart (100%) rename lib/screens/history/{details_pane => history_widgets}/his_response_pane.dart (100%) create mode 100644 lib/screens/history/history_widgets/his_sidebar_header.dart rename lib/screens/history/{details_pane/url_card.dart => history_widgets/his_url_card.dart} (100%) create mode 100644 lib/screens/history/history_widgets/history_widgets.dart diff --git a/lib/consts.dart b/lib/consts.dart index 03172793..63be0705 100644 --- a/lib/consts.dart +++ b/lib/consts.dart @@ -100,13 +100,15 @@ const kP6 = EdgeInsets.all(6); const kP8 = EdgeInsets.all(8); const kPs8 = EdgeInsets.only(left: 8); const kPs2 = EdgeInsets.only(left: 2); +const kPe4 = EdgeInsets.only(right: 4); const kPe8 = EdgeInsets.only(right: 8.0); const kPh20v5 = EdgeInsets.symmetric(horizontal: 20, vertical: 5); const kPh20v10 = EdgeInsets.symmetric(horizontal: 20, vertical: 10); const kP10 = EdgeInsets.all(10); -const kPv8 = EdgeInsets.symmetric(vertical: 8); -const kPv6 = EdgeInsets.symmetric(vertical: 6); const kPv2 = EdgeInsets.symmetric(vertical: 2); +const kPv6 = EdgeInsets.symmetric(vertical: 6); +const kPv8 = EdgeInsets.symmetric(vertical: 8); +const kPv10 = EdgeInsets.symmetric(vertical: 10); const kPh2 = EdgeInsets.symmetric(horizontal: 2); const kPt28o8 = EdgeInsets.only(top: 28, left: 8.0, right: 8.0, bottom: 8.0); const kPt5o10 = diff --git a/lib/screens/common_widgets/sidebar_header.dart b/lib/screens/common_widgets/sidebar_header.dart index 4e6b16f7..1a26f0ea 100644 --- a/lib/screens/common_widgets/sidebar_header.dart +++ b/lib/screens/common_widgets/sidebar_header.dart @@ -47,7 +47,7 @@ class SidebarHeader extends ConsumerWidget { ? IconButton( style: IconButton.styleFrom( padding: const EdgeInsets.all(4), - minimumSize: const Size(30, 30), + minimumSize: const Size(36, 36), ), onPressed: () { mobileScaffoldKey.currentState?.closeDrawer(); diff --git a/lib/screens/history/history_details.dart b/lib/screens/history/history_details.dart index 7498fa5f..10f02251 100644 --- a/lib/screens/history/history_details.dart +++ b/lib/screens/history/history_details.dart @@ -5,9 +5,7 @@ import 'package:apidash/providers/providers.dart'; import 'package:apidash/widgets/widgets.dart'; import 'package:apidash/consts.dart'; import 'package:apidash/screens/common_widgets/common_widgets.dart'; -import 'details_pane/url_card.dart'; -import 'details_pane/his_request_pane.dart'; -import 'details_pane/his_response_pane.dart'; +import 'history_widgets/history_widgets.dart'; class HistoryDetails extends StatefulHookConsumerWidget { const HistoryDetails({super.key}); diff --git a/lib/screens/history/history_page.dart b/lib/screens/history/history_page.dart index 0f288fb7..3787ce4f 100644 --- a/lib/screens/history/history_page.dart +++ b/lib/screens/history/history_page.dart @@ -6,6 +6,7 @@ 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({ @@ -22,14 +23,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, - ); + 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()); } return const Column( children: [ diff --git a/lib/screens/history/history_pane.dart b/lib/screens/history/history_pane.dart index be7e36d7..d206dabb 100644 --- a/lib/screens/history/history_pane.dart +++ b/lib/screens/history/history_pane.dart @@ -4,8 +4,10 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:apidash/extensions/extensions.dart'; import 'package:apidash/providers/providers.dart'; import 'package:apidash/widgets/widgets.dart'; +import 'package:apidash/models/models.dart'; import 'package:apidash/utils/utils.dart'; import 'package:apidash/consts.dart'; +import 'history_widgets/history_widgets.dart'; class HistoryPane extends ConsumerWidget { const HistoryPane({ @@ -19,7 +21,11 @@ class HistoryPane extends ConsumerWidget { (context.isMediumWindow ? kPb70 : EdgeInsets.zero), child: const Column( crossAxisAlignment: CrossAxisAlignment.stretch, - children: [kVSpacer10, Expanded(child: HistoryList()), kVSpacer5], + children: [ + HistorySidebarHeader(), + Expanded(child: HistoryList()), + kVSpacer5, + ], ), ); } @@ -32,7 +38,6 @@ class HistoryList extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final selectedGroupId = ref.watch(selectedRequestGroupIdStateProvider); final historySequence = ref.watch(historySequenceProvider); final alwaysShowHistoryPaneScrollbar = ref.watch(settingsProvider .select((value) => value.alwaysShowCollectionPaneScrollbar)); @@ -43,51 +48,108 @@ class HistoryList extends HookConsumerWidget { controller: scrollController, thumbVisibility: alwaysShowHistoryPaneScrollbar, radius: const Radius.circular(12), - child: ListView( + child: ListView.separated( padding: EdgeInsets.only(bottom: MediaQuery.paddingOf(context).bottom), controller: scrollController, - children: sortedHistoryKeys != null - ? sortedHistoryKeys.map((date) { - var items = historySequence![date]!; - final requestGroups = getRequestGroups(items); - return Column( - children: [ - ExpansionTile( - title: Text( - humanizeDate(date), - ), - initiallyExpanded: true, - children: requestGroups.values.map((item) { - return Padding( - padding: kPv2 + kPh4, - child: SidebarHistoryCard( - id: item.first.historyId, - models: item, - method: item.first.method, - isSelected: selectedGroupId == - getHistoryRequestKey(item.first), - requestGroupSize: item.length, - onTap: () { - ref - .read(historyMetaStateNotifier.notifier) - .loadHistoryRequest(item.first.historyId); - }, - ), - ); - }).toList(), - ), - ], - ); - }).toList() - : [ - const Center( - child: Text( - 'No history', - style: TextStyle(color: Colors.grey), - ), - ) - ], + itemCount: sortedHistoryKeys?.length ?? 0, + separatorBuilder: (context, index) => Divider( + height: 0, + thickness: 2, + color: Theme.of(context).colorScheme.surfaceContainerHigh, + ), + itemBuilder: (context, index) { + var items = historySequence![sortedHistoryKeys![index]]!; + final requestGroups = getRequestGroups(items); + return Padding( + padding: kPv2, + child: HistoryExpansionTile( + date: sortedHistoryKeys[index], + requestGroups: requestGroups, + ), + ); + }, ), ); } } + +class HistoryExpansionTile extends StatefulHookConsumerWidget { + const HistoryExpansionTile({ + super.key, + required this.requestGroups, + required this.date, + }); + + final Map> requestGroups; + final DateTime date; + + @override + ConsumerState createState() => + _HistoryExpansionTileState(); +} + +class _HistoryExpansionTileState extends ConsumerState + with SingleTickerProviderStateMixin { + @override + Widget build(BuildContext context) { + final animationController = useAnimationController( + duration: const Duration(milliseconds: 200), + vsync: this, + ); + final animation = Tween(begin: 0.25, end: 0.0).animate(animationController); + final colorScheme = Theme.of(context).colorScheme; + final selectedGroupId = ref.watch(selectedRequestGroupIdStateProvider); + return ExpansionTile( + dense: true, + title: Row( + children: [ + RotationTransition( + turns: animation, + child: Icon( + Icons.chevron_right_rounded, + size: 20, + color: colorScheme.onSurface.withOpacity(0.6), + )), + kHSpacer5, + Text( + humanizeDate(widget.date), + style: Theme.of(context).textTheme.titleSmall?.copyWith( + fontWeight: FontWeight.bold, + color: colorScheme.onSurface.withOpacity(0.6), + ), + ), + ], + ), + onExpansionChanged: (value) { + if (value) { + animationController.reverse(); + } else { + animationController.forward(); + } + }, + trailing: const SizedBox.shrink(), + tilePadding: kPh8, + shape: const RoundedRectangleBorder(), + collapsedBackgroundColor: colorScheme.surfaceContainerLow, + initiallyExpanded: true, + childrenPadding: kPv8 + kPe4, + children: widget.requestGroups.values.map((item) { + return Padding( + padding: kPv2 + kPh4, + child: SidebarHistoryCard( + id: item.first.historyId, + models: item, + method: item.first.method, + isSelected: selectedGroupId == getHistoryRequestKey(item.first), + requestGroupSize: item.length, + onTap: () { + ref + .read(historyMetaStateNotifier.notifier) + .loadHistoryRequest(item.first.historyId); + }, + ), + ); + }).toList(), + ); + } +} diff --git a/lib/screens/history/history_requests.dart b/lib/screens/history/history_requests.dart index f0bc10cf..7927cd3c 100644 --- a/lib/screens/history/history_requests.dart +++ b/lib/screens/history/history_requests.dart @@ -1,12 +1,20 @@ -import 'package:apidash/consts.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:apidash/providers/history_providers.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:apidash/providers/providers.dart'; import 'package:apidash/utils/history_utils.dart'; import 'package:apidash/widgets/widgets.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:apidash/consts.dart'; class HistoryRequests extends ConsumerWidget { - const HistoryRequests({super.key}); + const HistoryRequests({ + super.key, + this.scrollController, + this.onSelect, + }); + + final ScrollController? scrollController; + final Function()? onSelect; @override Widget build(BuildContext context, WidgetRef ref) { @@ -15,9 +23,12 @@ class HistoryRequests extends ConsumerWidget { final historyMetas = ref.watch(historyMetaStateNotifier); final requestGroup = getRequestGroup( historyMetas?.values.toList(), selectedRequest?.metaData); - return Column( + return ListView( + shrinkWrap: true, + controller: scrollController, + padding: kPh4, children: [ - kVSpacer20, + kVSpacer10, ...requestGroup.map((request) => Padding( padding: kPv2 + kPh4, child: HistoryRequestCard( @@ -30,10 +41,121 @@ class HistoryRequests extends ConsumerWidget { ref .read(historyMetaStateNotifier.notifier) .loadHistoryRequest(request.historyId); + onSelect?.call(); }, ), - )) + )), + kVSpacer10, ], ); } } + +class HistorRequestsScrollableSheet extends StatefulWidget { + const HistorRequestsScrollableSheet({ + super.key, + }); + + @override + State createState() => + _HistorRequestsScrollableSheetState(); +} + +class _HistorRequestsScrollableSheetState + extends State { + double sheetPosition = 0.5; + final double dragSensitivity = 600; + @override + Widget build(BuildContext context) { + return DraggableScrollableSheet( + initialChildSize: sheetPosition, + expand: false, + builder: (context, scrollController) { + return Column( + children: [ + Grabber( + onVerticalDragUpdate: (DragUpdateDetails details) { + setState(() { + sheetPosition -= details.delta.dy / dragSensitivity; + if (sheetPosition < 0.25) { + sheetPosition = 0.25; + } + if (sheetPosition > 0.9) { + sheetPosition = 0.9; + } + }); + }, + isOnDesktopAndWeb: isOnDesktopAndWeb, + ), + Expanded( + child: HistoryRequests( + scrollController: scrollController, + onSelect: () { + Navigator.of(context).pop(); + }, + ), + ), + ], + ); + }); + } + + bool get isOnDesktopAndWeb { + if (kIsWeb) { + return true; + } + switch (defaultTargetPlatform) { + case TargetPlatform.macOS: + case TargetPlatform.linux: + case TargetPlatform.windows: + return true; + case TargetPlatform.android: + case TargetPlatform.iOS: + case TargetPlatform.fuchsia: + return false; + } + } +} + +class Grabber extends StatelessWidget { + const Grabber({ + super.key, + required this.onVerticalDragUpdate, + required this.isOnDesktopAndWeb, + }); + + final ValueChanged onVerticalDragUpdate; + final bool isOnDesktopAndWeb; + + @override + Widget build(BuildContext context) { + if (!isOnDesktopAndWeb) { + return const SizedBox.shrink(); + } + final ColorScheme colorScheme = Theme.of(context).colorScheme; + + return GestureDetector( + onVerticalDragUpdate: onVerticalDragUpdate, + child: Container( + width: double.infinity, + decoration: BoxDecoration( + color: colorScheme.surfaceContainerLow, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(16), topRight: Radius.circular(16)), + ), + child: Align( + alignment: Alignment.topCenter, + child: Container( + margin: kPv10, + width: 80.0, + height: 6.0, + decoration: BoxDecoration( + color: colorScheme.surfaceContainerHighest, + borderRadius: BorderRadius.circular(8.0), + ), + ), + ), + ), + ); + } +} diff --git a/lib/screens/history/history_widgets/his_bottombar.dart b/lib/screens/history/history_widgets/his_bottombar.dart new file mode 100644 index 00000000..5f146cc5 --- /dev/null +++ b/lib/screens/history/history_widgets/his_bottombar.dart @@ -0,0 +1,75 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:apidash/providers/providers.dart'; +import 'package:apidash/utils/utils.dart'; +import '../history_requests.dart'; + +class HistoryPageBottombar extends ConsumerWidget { + const HistoryPageBottombar({ + super.key, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final selectedRequestModel = ref.watch(selectedHistoryRequestModelProvider); + final historyMetas = ref.watch(historyMetaStateNotifier); + 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, + ), + ), + ), + 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(), + ], + ), + ), + ); + } +} diff --git a/lib/screens/history/details_pane/his_request_pane.dart b/lib/screens/history/history_widgets/his_request_pane.dart similarity index 100% rename from lib/screens/history/details_pane/his_request_pane.dart rename to lib/screens/history/history_widgets/his_request_pane.dart diff --git a/lib/screens/history/details_pane/his_response_pane.dart b/lib/screens/history/history_widgets/his_response_pane.dart similarity index 100% rename from lib/screens/history/details_pane/his_response_pane.dart rename to lib/screens/history/history_widgets/his_response_pane.dart diff --git a/lib/screens/history/history_widgets/his_sidebar_header.dart b/lib/screens/history/history_widgets/his_sidebar_header.dart new file mode 100644 index 00000000..e431c4b7 --- /dev/null +++ b/lib/screens/history/history_widgets/his_sidebar_header.dart @@ -0,0 +1,50 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:apidash/extensions/extensions.dart'; +import 'package:apidash/providers/providers.dart'; +import 'package:apidash/consts.dart'; + +class HistorySidebarHeader extends ConsumerWidget { + const HistorySidebarHeader({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final mobileScaffoldKey = ref.read(mobileScaffoldKeyStateProvider); + return Padding( + padding: kPe4, + child: Row( + children: [ + kHSpacer10, + Text( + "History", + style: Theme.of(context).textTheme.titleMedium, + ), + const Spacer(), + IconButton( + tooltip: "Auto Delete Settings", + style: IconButton.styleFrom( + foregroundColor: Theme.of(context).colorScheme.primary, + ), + onPressed: () {}, + icon: const Icon( + Icons.auto_delete_outlined, + size: 20, + ), + ), + context.width <= kMinWindowSize.width + ? IconButton( + style: IconButton.styleFrom( + padding: const EdgeInsets.all(4), + minimumSize: const Size(36, 36), + ), + onPressed: () { + mobileScaffoldKey.currentState?.closeDrawer(); + }, + icon: const Icon(Icons.chevron_left), + ) + : const SizedBox.shrink(), + ], + ), + ); + } +} diff --git a/lib/screens/history/details_pane/url_card.dart b/lib/screens/history/history_widgets/his_url_card.dart similarity index 100% rename from lib/screens/history/details_pane/url_card.dart rename to lib/screens/history/history_widgets/his_url_card.dart diff --git a/lib/screens/history/history_widgets/history_widgets.dart b/lib/screens/history/history_widgets/history_widgets.dart new file mode 100644 index 00000000..dc8712ff --- /dev/null +++ b/lib/screens/history/history_widgets/history_widgets.dart @@ -0,0 +1,5 @@ +export 'his_bottombar.dart'; +export 'his_request_pane.dart'; +export 'his_response_pane.dart'; +export 'his_sidebar_header.dart'; +export 'his_url_card.dart';