wip: history requests sheet

This commit is contained in:
DenserMeerkat
2024-07-21 04:34:12 +05:30
parent f8ede1edc8
commit d9d60961f7
12 changed files with 380 additions and 65 deletions

View File

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

View File

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

View File

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

View File

@ -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({
@ -29,7 +30,7 @@ class HistoryPage extends ConsumerWidget {
actions: const [SizedBox(width: 16)],
onDrawerChanged: (value) =>
ref.read(leftDrawerStateProvider.notifier).state = value,
);
bottomNavigationBar: const HistoryPageBottombar());
}
return const Column(
children: [

View File

@ -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,29 +48,99 @@ 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),
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<String, List<HistoryMetaModel>> requestGroups;
final DateTime date;
@override
ConsumerState<HistoryExpansionTile> createState() =>
_HistoryExpansionTileState();
}
class _HistoryExpansionTileState extends ConsumerState<HistoryExpansionTile>
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,
children: requestGroups.values.map((item) {
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),
isSelected: selectedGroupId == getHistoryRequestKey(item.first),
requestGroupSize: item.length,
onTap: () {
ref
@ -75,19 +150,6 @@ class HistoryList extends HookConsumerWidget {
),
);
}).toList(),
),
],
);
}).toList()
: [
const Center(
child: Text(
'No history',
style: TextStyle(color: Colors.grey),
),
)
],
),
);
}
}

View File

@ -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<HistorRequestsScrollableSheet> createState() =>
_HistorRequestsScrollableSheetState();
}
class _HistorRequestsScrollableSheetState
extends State<HistorRequestsScrollableSheet> {
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<DragUpdateDetails> 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),
),
),
),
),
);
}
}

View File

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

View File

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

View File

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