wip: history details pane

This commit is contained in:
DenserMeerkat
2024-07-19 18:11:37 +05:30
parent d5feb0b091
commit cad6c97f89
17 changed files with 275 additions and 136 deletions

View File

@ -128,6 +128,9 @@ const kPt8 = EdgeInsets.only(
const kPt20 = EdgeInsets.only( const kPt20 = EdgeInsets.only(
top: 20, top: 20,
); );
const kPt24 = EdgeInsets.only(
top: 24,
);
const kPt28 = EdgeInsets.only( const kPt28 = EdgeInsets.only(
top: 28, top: 28,
); );

View File

@ -5,12 +5,12 @@ import '../utils/history_utils.dart';
final selectedHistoryIdStateProvider = StateProvider<String?>((ref) => null); final selectedHistoryIdStateProvider = StateProvider<String?>((ref) => null);
final selectedRequestGroupStateProvider = StateProvider<String?>((ref) { final selectedRequestGroupIdStateProvider = StateProvider<String?>((ref) {
final selectedHistoryId = ref.watch(selectedHistoryIdStateProvider); final selectedHistoryId = ref.watch(selectedHistoryIdStateProvider);
final historyMetaState = ref.read(historyMetaStateNotifier);
if (selectedHistoryId == null) { if (selectedHistoryId == null) {
return null; return null;
} }
final historyMetaState = ref.read(historyMetaStateNotifier);
return getHistoryRequestKey(historyMetaState![selectedHistoryId]!); return getHistoryRequestKey(historyMetaState![selectedHistoryId]!);
}); });

View File

@ -37,7 +37,7 @@ class NavbarButton extends ConsumerWidget {
if (buttonIdx != null) { if (buttonIdx != null) {
ref.read(navRailIndexStateProvider.notifier).state = ref.read(navRailIndexStateProvider.notifier).state =
buttonIdx!; buttonIdx!;
if (railIdx > 1 && buttonIdx! <= 1) { if (railIdx > 2 && buttonIdx! <= 2) {
ref.read(leftDrawerStateProvider.notifier).state = false; ref.read(leftDrawerStateProvider.notifier).state = false;
} }
} }
@ -62,7 +62,7 @@ class NavbarButton extends ConsumerWidget {
if (buttonIdx != null) { if (buttonIdx != null) {
ref.read(navRailIndexStateProvider.notifier).state = ref.read(navRailIndexStateProvider.notifier).state =
buttonIdx!; buttonIdx!;
if (railIdx > 1 && buttonIdx! <= 1) { if (railIdx > 2 && buttonIdx! <= 2) {
ref.read(leftDrawerStateProvider.notifier).state = ref.read(leftDrawerStateProvider.notifier).state =
false; false;
} }

View File

@ -70,13 +70,16 @@ class EnvironmentEditor extends ConsumerWidget {
margin: EdgeInsets.zero, margin: EdgeInsets.zero,
color: kColorTransparent, color: kColorTransparent,
surfaceTintColor: kColorTransparent, surfaceTintColor: kColorTransparent,
shape: RoundedRectangleBorder( shape: context.isMediumWindow
side: BorderSide( ? null
color: : RoundedRectangleBorder(
Theme.of(context).colorScheme.surfaceContainerHighest, side: BorderSide(
), color: Theme.of(context)
borderRadius: kBorderRadius12, .colorScheme
), .surfaceContainerHighest,
),
borderRadius: kBorderRadius12,
),
elevation: 0, elevation: 0,
child: const Padding( child: const Padding(
padding: kPv6, padding: kPv6,

View File

@ -0,0 +1,66 @@
import 'package:flutter/material.dart';
import 'package:apidash/widgets/widgets.dart';
import 'package:apidash/utils/utils.dart';
import 'package:apidash/consts.dart';
class HistoryURLCard extends StatelessWidget {
const HistoryURLCard({
super.key,
required this.method,
required this.url,
});
final HTTPVerb method;
final String url;
@override
Widget build(BuildContext context) {
final fontSize = Theme.of(context).textTheme.titleMedium?.fontSize;
return LayoutBuilder(builder: (context, constraints) {
final isCompact = constraints.maxWidth <= kMinWindowSize.width;
return Card(
color: kColorTransparent,
surfaceTintColor: kColorTransparent,
elevation: 0,
shape: RoundedRectangleBorder(
side: BorderSide(
color: Theme.of(context).colorScheme.surfaceContainerHighest,
),
borderRadius: kBorderRadius8,
),
child: Padding(
padding: EdgeInsets.symmetric(
vertical: 12,
horizontal: isCompact ? 10 : 16,
),
child: Row(
children: [
isCompact ? const SizedBox.shrink() : kHSpacer10,
Text(
method.name.toUpperCase(),
style: kCodeStyle.copyWith(
fontSize: fontSize,
fontWeight: FontWeight.bold,
color: getHTTPMethodColor(
method,
brightness: Theme.of(context).brightness,
),
),
),
isCompact ? kHSpacer10 : kHSpacer20,
Expanded(
child: RawTextField(
readOnly: true,
controller: TextEditingController(text: url),
style: kCodeStyle.copyWith(
fontSize: fontSize,
),
),
)
],
),
),
);
});
}
}

View File

@ -1,10 +1,42 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:apidash/providers/providers.dart';
import 'package:apidash/widgets/widgets.dart';
import 'package:apidash/consts.dart';
import './details_pane/url_card.dart';
class HistoryDetails extends StatelessWidget { class HistoryDetails extends StatefulHookConsumerWidget {
const HistoryDetails({super.key}); const HistoryDetails({super.key});
@override
ConsumerState<HistoryDetails> createState() => _HistoryDetailsState();
}
class _HistoryDetailsState extends ConsumerState<HistoryDetails>
with TickerProviderStateMixin {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container(); final selectedHistoryRequest =
ref.watch(selectedHistoryRequestModelProvider);
final metaData = selectedHistoryRequest?.metaData;
final TabController controller =
useTabController(initialLength: 2, vsync: this);
return selectedHistoryRequest != null
? Column(
children: [
Padding(
padding: kP4,
child: HistoryURLCard(
method: metaData!.method, url: metaData.url)),
kVSpacer10,
RequestResponseTabbar(
controller: controller,
),
],
)
: const Text("No Request Selected");
} }
} }

View File

@ -15,9 +15,7 @@ class HistoryPane extends ConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
return Padding( return Padding(
padding: (!context.isMediumWindow && kIsMacOS padding: (!context.isMediumWindow && kIsMacOS ? kPt24 : kPt8) +
? kP24CollectionPane
: kP8CollectionPane) +
(context.isMediumWindow ? kPb70 : EdgeInsets.zero), (context.isMediumWindow ? kPb70 : EdgeInsets.zero),
child: const Column( child: const Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
@ -34,6 +32,7 @@ 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));
@ -45,12 +44,7 @@ class HistoryList extends HookConsumerWidget {
thumbVisibility: alwaysShowHistoryPaneScrollbar, thumbVisibility: alwaysShowHistoryPaneScrollbar,
radius: const Radius.circular(12), radius: const Radius.circular(12),
child: ListView( child: ListView(
padding: context.isMediumWindow padding: EdgeInsets.only(bottom: MediaQuery.paddingOf(context).bottom),
? EdgeInsets.only(
bottom: MediaQuery.paddingOf(context).bottom,
right: 8,
)
: kPe8,
controller: scrollController, controller: scrollController,
children: sortedHistoryKeys != null children: sortedHistoryKeys != null
? sortedHistoryKeys.map((date) { ? sortedHistoryKeys.map((date) {
@ -69,8 +63,8 @@ class HistoryList extends HookConsumerWidget {
id: item.first.historyId, id: item.first.historyId,
models: item, models: item,
method: item.first.method, method: item.first.method,
selectedId: isSelected: selectedGroupId ==
ref.watch(selectedRequestGroupStateProvider), getHistoryRequestKey(item.first),
requestGroupSize: item.length, requestGroupSize: item.length,
onTap: () { onTap: () {
ref ref

View File

@ -1,10 +1,32 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:apidash/providers/history_providers.dart';
import 'package:apidash/utils/history_utils.dart';
import 'package:apidash/widgets/widgets.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class HistoryRequests extends StatelessWidget { class HistoryRequests extends ConsumerWidget {
const HistoryRequests({super.key}); const HistoryRequests({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context, WidgetRef ref) {
return Container(); final selectedRequestId = ref.watch(selectedHistoryIdStateProvider);
final selectedRequest = ref.read(selectedHistoryRequestModelProvider);
final historyMetas = ref.read(historyMetaStateNotifier);
final requestGroup = getRequestGroup(
historyMetas?.values.toList(), selectedRequest?.metaData);
return Column(
children: requestGroup
.map((request) => SidebarHistoryCard(
id: request.historyId,
method: request.method,
isSelected: selectedRequestId == request.historyId,
onTap: () {
ref.read(selectedHistoryIdStateProvider.notifier).state =
request.historyId;
},
models: [request],
))
.toList(),
);
} }
} }

View File

@ -57,15 +57,6 @@ class BottomNavBar extends ConsumerWidget {
label: 'History', label: 'History',
), ),
), ),
// Expanded(
// child: NavbarButton(
// railIdx: railIdx,
// buttonIdx: 2,
// selectedIcon: Icons.history,
// icon: Icons.history_outlined,
// label: 'History',
// ),
// ),
Expanded( Expanded(
child: NavbarButton( child: NavbarButton(
railIdx: railIdx, railIdx: railIdx,

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:apidash/widgets/widgets.dart';
import 'package:apidash/consts.dart'; import 'package:apidash/consts.dart';
import '../../home_page/editor_pane/details_card/response_pane.dart'; import '../../home_page/editor_pane/details_card/response_pane.dart';
import '../../home_page/editor_pane/editor_request.dart'; import '../../home_page/editor_pane/editor_request.dart';
@ -30,72 +31,21 @@ class RequestResponseTabs extends StatelessWidget {
} }
} }
class RequestResponseTabbar extends StatelessWidget {
const RequestResponseTabbar({
super.key,
required this.controller,
});
final TabController controller;
@override
Widget build(BuildContext context) {
return Center(
child: Container(
width: kReqResTabWidth,
height: kReqResTabHeight,
decoration: BoxDecoration(
borderRadius: kBorderRadius20,
border: Border.all(
color: Theme.of(context).colorScheme.outlineVariant,
),
),
child: ClipRRect(
borderRadius: kBorderRadius20,
child: TabBar(
dividerColor: Colors.transparent,
indicatorWeight: 0.0,
indicatorSize: TabBarIndicatorSize.tab,
unselectedLabelColor:
Theme.of(context).colorScheme.onSurface.withOpacity(0.4),
labelStyle: kTextStyleTab.copyWith(
fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.onPrimary,
),
unselectedLabelStyle: kTextStyleTab,
splashBorderRadius: kBorderRadius20,
indicator: BoxDecoration(
borderRadius: kBorderRadius20,
color: Theme.of(context).colorScheme.primary,
),
controller: controller,
tabs: const <Widget>[
Tab(
text: kLabelRequest,
),
Tab(
text: kLabelResponse,
),
],
),
),
),
);
}
}
class RequestResponseTabviews extends StatelessWidget { class RequestResponseTabviews extends StatelessWidget {
const RequestResponseTabviews({super.key, required this.controller}); const RequestResponseTabviews({super.key, required this.controller});
final TabController controller; final TabController controller;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return TabBarView(controller: controller, children: const [ return TabBarView(
RequestEditor(), controller: controller,
Padding( children: const [
padding: kPt8, RequestEditor(),
child: ResponsePane(), Padding(
), padding: kPt8,
]); child: ResponsePane(),
),
],
);
} }
} }

View File

@ -84,3 +84,20 @@ Map<String, List<HistoryMetaModel>> getRequestGroups(
}); });
return historyGroups; return historyGroups;
} }
List<HistoryMetaModel> getRequestGroup(
List<HistoryMetaModel>? models, HistoryMetaModel? selectedModel) {
List<HistoryMetaModel> requestGroup = [];
if (selectedModel == null || (models?.isEmpty ?? true)) {
return requestGroup;
}
String selectedModelKey = getHistoryRequestKey(selectedModel);
for (HistoryMetaModel model in models!) {
String key = getHistoryRequestKey(model);
if (key == selectedModelKey) {
requestGroup.add(model);
}
}
requestGroup.sort((a, b) => b.timeStamp.compareTo(a.timeStamp));
return requestGroup;
}

View File

@ -10,7 +10,7 @@ class SidebarHistoryCard extends StatelessWidget {
required this.id, required this.id,
required this.models, required this.models,
required this.method, required this.method,
this.selectedId, this.isSelected = false,
this.requestGroupSize = 1, this.requestGroupSize = 1,
this.onTap, this.onTap,
}); });
@ -18,7 +18,7 @@ class SidebarHistoryCard extends StatelessWidget {
final String id; final String id;
final List<HistoryMetaModel> models; final List<HistoryMetaModel> models;
final HTTPVerb method; final HTTPVerb method;
final String? selectedId; final bool isSelected;
final int requestGroupSize; final int requestGroupSize;
final Function()? onTap; final Function()? onTap;
@ -29,7 +29,6 @@ class SidebarHistoryCard extends StatelessWidget {
Theme.of(context).colorScheme.surfaceContainerHighest.withOpacity(0.5); Theme.of(context).colorScheme.surfaceContainerHighest.withOpacity(0.5);
final model = models.first; final model = models.first;
final Color surfaceTint = Theme.of(context).colorScheme.primary; final Color surfaceTint = Theme.of(context).colorScheme.primary;
bool isSelected = selectedId == getHistoryRequestKey(model);
final String name = getHistoryRequestName(model); final String name = getHistoryRequestName(model);
return Tooltip( return Tooltip(
message: name, message: name,
@ -84,9 +83,9 @@ class SidebarHistoryCard extends StatelessWidget {
), ),
child: Center( child: Center(
child: Text( child: Text(
requestGroupSize == 2 requestGroupSize > 9
? requestGroupSize.toString() ? "9+"
: "9+", : requestGroupSize.toString(),
style: Theme.of(context) style: Theme.of(context)
.textTheme .textTheme
.labelSmall .labelSmall

View File

@ -20,38 +20,40 @@ class ErrorMessage extends StatelessWidget {
return Padding( return Padding(
padding: kPh20v10, padding: kPh20v10,
child: Center( child: Center(
child: Column( child: SingleChildScrollView(
mainAxisAlignment: MainAxisAlignment.center, child: Column(
children: [ mainAxisAlignment: MainAxisAlignment.center,
showIcon children: [
? Icon( showIcon
Icons.warning_rounded, ? Icon(
size: 40, Icons.warning_rounded,
color: color, size: 40,
) color: color,
: const SizedBox(), )
SelectableText( : const SizedBox(),
message ?? 'An error occurred. $kUnexpectedRaiseIssue', SelectableText(
textAlign: TextAlign.center, message ?? 'An error occurred. $kUnexpectedRaiseIssue',
style: Theme.of(context) textAlign: TextAlign.center,
.textTheme style: Theme.of(context)
.titleMedium .textTheme
?.copyWith(color: color), .titleMedium
), ?.copyWith(color: color),
kVSpacer20, ),
showIssueButton kVSpacer20,
? FilledButton.tonalIcon( showIssueButton
onPressed: () { ? FilledButton.tonalIcon(
launchUrl(Uri.parse(kGitUrl)); onPressed: () {
}, launchUrl(Uri.parse(kGitUrl));
icon: const Icon(Icons.arrow_outward_rounded), },
label: Text( icon: const Icon(Icons.arrow_outward_rounded),
'Raise Issue', label: Text(
style: Theme.of(context).textTheme.titleMedium, 'Raise Issue',
), style: Theme.of(context).textTheme.titleMedium,
) ),
: const SizedBox(), )
], : const SizedBox(),
],
),
), ),
), ),
); );

View File

@ -8,16 +8,19 @@ class RawTextField extends StatelessWidget {
this.controller, this.controller,
this.hintText, this.hintText,
this.style, this.style,
this.readOnly = false,
}); });
final void Function(String)? onChanged; final void Function(String)? onChanged;
final TextEditingController? controller; final TextEditingController? controller;
final String? hintText; final String? hintText;
final TextStyle? style; final TextStyle? style;
final bool readOnly;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return TextField( return TextField(
readOnly: readOnly,
controller: controller, controller: controller,
onChanged: onChanged, onChanged: onChanged,
style: style, style: style,

View File

@ -19,7 +19,7 @@ class HistorySplitView extends StatefulWidget {
class HistorySplitViewState extends State<HistorySplitView> { class HistorySplitViewState extends State<HistorySplitView> {
final MultiSplitViewController _controller = MultiSplitViewController( final MultiSplitViewController _controller = MultiSplitViewController(
areas: [ areas: [
Area(id: "sidebar", min: 200, size: 220, max: 300), Area(id: "sidebar", min: 200, size: 250, max: 300),
Area(id: "main"), Area(id: "main"),
], ],
); );

View File

@ -0,0 +1,56 @@
import 'package:flutter/material.dart';
import 'package:apidash/consts.dart';
class RequestResponseTabbar extends StatelessWidget {
const RequestResponseTabbar({
super.key,
required this.controller,
});
final TabController controller;
@override
Widget build(BuildContext context) {
return Center(
child: Container(
width: kReqResTabWidth,
height: kReqResTabHeight,
decoration: BoxDecoration(
borderRadius: kBorderRadius20,
border: Border.all(
color: Theme.of(context).colorScheme.outlineVariant,
),
),
child: ClipRRect(
borderRadius: kBorderRadius20,
child: TabBar(
dividerColor: Colors.transparent,
indicatorWeight: 0.0,
indicatorSize: TabBarIndicatorSize.tab,
unselectedLabelColor:
Theme.of(context).colorScheme.onSurface.withOpacity(0.4),
labelStyle: kTextStyleTab.copyWith(
fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.onPrimary,
),
unselectedLabelStyle: kTextStyleTab,
splashBorderRadius: kBorderRadius20,
indicator: BoxDecoration(
borderRadius: kBorderRadius20,
color: Theme.of(context).colorScheme.primary,
),
controller: controller,
tabs: const <Widget>[
Tab(
text: kLabelRequest,
),
Tab(
text: kLabelResponse,
),
],
),
),
),
);
}
}

View File

@ -45,6 +45,7 @@ export 'splitview_dashboard.dart';
export 'splitview_equal.dart'; export 'splitview_equal.dart';
export 'splitview_history.dart'; export 'splitview_history.dart';
export 'suggestions_menu.dart'; export 'suggestions_menu.dart';
export 'tabbar_request_response.dart';
export 'tables.dart'; export 'tables.dart';
export 'tabs.dart'; export 'tabs.dart';
export 'texts.dart'; export 'texts.dart';