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 kP8 = EdgeInsets.all(8);
const kPs8 = EdgeInsets.only(left: 8); const kPs8 = EdgeInsets.only(left: 8);
const kPs2 = EdgeInsets.only(left: 2); const kPs2 = EdgeInsets.only(left: 2);
const kPe4 = EdgeInsets.only(right: 4);
const kPe8 = EdgeInsets.only(right: 8.0); const kPe8 = EdgeInsets.only(right: 8.0);
const kPh20v5 = EdgeInsets.symmetric(horizontal: 20, vertical: 5); const kPh20v5 = EdgeInsets.symmetric(horizontal: 20, vertical: 5);
const kPh20v10 = EdgeInsets.symmetric(horizontal: 20, vertical: 10); const kPh20v10 = EdgeInsets.symmetric(horizontal: 20, vertical: 10);
const kP10 = EdgeInsets.all(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 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 kPh2 = EdgeInsets.symmetric(horizontal: 2);
const kPt28o8 = EdgeInsets.only(top: 28, left: 8.0, right: 8.0, bottom: 8.0); const kPt28o8 = EdgeInsets.only(top: 28, left: 8.0, right: 8.0, bottom: 8.0);
const kPt5o10 = const kPt5o10 =

View File

@ -47,7 +47,7 @@ class SidebarHeader extends ConsumerWidget {
? IconButton( ? IconButton(
style: IconButton.styleFrom( style: IconButton.styleFrom(
padding: const EdgeInsets.all(4), padding: const EdgeInsets.all(4),
minimumSize: const Size(30, 30), minimumSize: const Size(36, 36),
), ),
onPressed: () { onPressed: () {
mobileScaffoldKey.currentState?.closeDrawer(); mobileScaffoldKey.currentState?.closeDrawer();

View File

@ -5,9 +5,7 @@ import 'package:apidash/providers/providers.dart';
import 'package:apidash/widgets/widgets.dart'; import 'package:apidash/widgets/widgets.dart';
import 'package:apidash/consts.dart'; import 'package:apidash/consts.dart';
import 'package:apidash/screens/common_widgets/common_widgets.dart'; import 'package:apidash/screens/common_widgets/common_widgets.dart';
import 'details_pane/url_card.dart'; import 'history_widgets/history_widgets.dart';
import 'details_pane/his_request_pane.dart';
import 'details_pane/his_response_pane.dart';
class HistoryDetails extends StatefulHookConsumerWidget { class HistoryDetails extends StatefulHookConsumerWidget {
const HistoryDetails({super.key}); const HistoryDetails({super.key});

View File

@ -6,6 +6,7 @@ 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({
@ -29,7 +30,7 @@ class HistoryPage extends ConsumerWidget {
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

@ -4,8 +4,10 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:apidash/extensions/extensions.dart'; import 'package:apidash/extensions/extensions.dart';
import 'package:apidash/providers/providers.dart'; import 'package:apidash/providers/providers.dart';
import 'package:apidash/widgets/widgets.dart'; import 'package:apidash/widgets/widgets.dart';
import 'package:apidash/models/models.dart';
import 'package:apidash/utils/utils.dart'; import 'package:apidash/utils/utils.dart';
import 'package:apidash/consts.dart'; import 'package:apidash/consts.dart';
import 'history_widgets/history_widgets.dart';
class HistoryPane extends ConsumerWidget { class HistoryPane extends ConsumerWidget {
const HistoryPane({ const HistoryPane({
@ -19,7 +21,11 @@ class HistoryPane extends ConsumerWidget {
(context.isMediumWindow ? kPb70 : EdgeInsets.zero), (context.isMediumWindow ? kPb70 : EdgeInsets.zero),
child: const Column( child: const Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [kVSpacer10, Expanded(child: HistoryList()), kVSpacer5], children: [
HistorySidebarHeader(),
Expanded(child: HistoryList()),
kVSpacer5,
],
), ),
); );
} }
@ -32,7 +38,6 @@ class HistoryList extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final selectedGroupId = ref.watch(selectedRequestGroupIdStateProvider);
final historySequence = ref.watch(historySequenceProvider); final historySequence = ref.watch(historySequenceProvider);
final alwaysShowHistoryPaneScrollbar = ref.watch(settingsProvider final alwaysShowHistoryPaneScrollbar = ref.watch(settingsProvider
.select((value) => value.alwaysShowCollectionPaneScrollbar)); .select((value) => value.alwaysShowCollectionPaneScrollbar));
@ -43,29 +48,99 @@ class HistoryList extends HookConsumerWidget {
controller: scrollController, controller: scrollController,
thumbVisibility: alwaysShowHistoryPaneScrollbar, thumbVisibility: alwaysShowHistoryPaneScrollbar,
radius: const Radius.circular(12), radius: const Radius.circular(12),
child: ListView( child: ListView.separated(
padding: EdgeInsets.only(bottom: MediaQuery.paddingOf(context).bottom), padding: EdgeInsets.only(bottom: MediaQuery.paddingOf(context).bottom),
controller: scrollController, controller: scrollController,
children: sortedHistoryKeys != null itemCount: sortedHistoryKeys?.length ?? 0,
? sortedHistoryKeys.map((date) { separatorBuilder: (context, index) => Divider(
var items = historySequence![date]!; height: 0,
final requestGroups = getRequestGroups(items); thickness: 2,
return Column( color: Theme.of(context).colorScheme.surfaceContainerHigh,
children: [
ExpansionTile(
title: Text(
humanizeDate(date),
), ),
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, initiallyExpanded: true,
children: requestGroups.values.map((item) { childrenPadding: kPv8 + kPe4,
children: widget.requestGroups.values.map((item) {
return Padding( return Padding(
padding: kPv2 + kPh4, padding: kPv2 + kPh4,
child: SidebarHistoryCard( child: SidebarHistoryCard(
id: item.first.historyId, id: item.first.historyId,
models: item, models: item,
method: item.first.method, method: item.first.method,
isSelected: selectedGroupId == isSelected: selectedGroupId == getHistoryRequestKey(item.first),
getHistoryRequestKey(item.first),
requestGroupSize: item.length, requestGroupSize: item.length,
onTap: () { onTap: () {
ref ref
@ -75,19 +150,6 @@ class HistoryList extends HookConsumerWidget {
), ),
); );
}).toList(), }).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: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/utils/history_utils.dart';
import 'package:apidash/widgets/widgets.dart'; import 'package:apidash/widgets/widgets.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:apidash/consts.dart';
class HistoryRequests extends ConsumerWidget { class HistoryRequests extends ConsumerWidget {
const HistoryRequests({super.key}); const HistoryRequests({
super.key,
this.scrollController,
this.onSelect,
});
final ScrollController? scrollController;
final Function()? onSelect;
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
@ -15,9 +23,12 @@ class HistoryRequests extends ConsumerWidget {
final historyMetas = ref.watch(historyMetaStateNotifier); final historyMetas = ref.watch(historyMetaStateNotifier);
final requestGroup = getRequestGroup( final requestGroup = getRequestGroup(
historyMetas?.values.toList(), selectedRequest?.metaData); historyMetas?.values.toList(), selectedRequest?.metaData);
return Column( return ListView(
shrinkWrap: true,
controller: scrollController,
padding: kPh4,
children: [ children: [
kVSpacer20, kVSpacer10,
...requestGroup.map((request) => Padding( ...requestGroup.map((request) => Padding(
padding: kPv2 + kPh4, padding: kPv2 + kPh4,
child: HistoryRequestCard( child: HistoryRequestCard(
@ -30,10 +41,121 @@ class HistoryRequests extends ConsumerWidget {
ref ref
.read(historyMetaStateNotifier.notifier) .read(historyMetaStateNotifier.notifier)
.loadHistoryRequest(request.historyId); .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';