mirror of
https://github.com/foss42/apidash.git
synced 2025-05-19 15:26:28 +08:00
wip: history requests sheet
This commit is contained in:
@ -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 =
|
||||
|
@ -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();
|
||||
|
@ -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});
|
||||
|
@ -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: [
|
||||
|
@ -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<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,
|
||||
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(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
75
lib/screens/history/history_widgets/his_bottombar.dart
Normal file
75
lib/screens/history/history_widgets/his_bottombar.dart
Normal 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(),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
50
lib/screens/history/history_widgets/his_sidebar_header.dart
Normal file
50
lib/screens/history/history_widgets/his_sidebar_header.dart
Normal 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(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
5
lib/screens/history/history_widgets/history_widgets.dart
Normal file
5
lib/screens/history/history_widgets/history_widgets.dart
Normal 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';
|
Reference in New Issue
Block a user