mirror of
https://github.com/foss42/apidash.git
synced 2025-05-21 08:16:29 +08:00
wip: history details pane
This commit is contained in:
@ -128,6 +128,9 @@ const kPt8 = EdgeInsets.only(
|
||||
const kPt20 = EdgeInsets.only(
|
||||
top: 20,
|
||||
);
|
||||
const kPt24 = EdgeInsets.only(
|
||||
top: 24,
|
||||
);
|
||||
const kPt28 = EdgeInsets.only(
|
||||
top: 28,
|
||||
);
|
||||
|
@ -5,12 +5,12 @@ import '../utils/history_utils.dart';
|
||||
|
||||
final selectedHistoryIdStateProvider = StateProvider<String?>((ref) => null);
|
||||
|
||||
final selectedRequestGroupStateProvider = StateProvider<String?>((ref) {
|
||||
final selectedRequestGroupIdStateProvider = StateProvider<String?>((ref) {
|
||||
final selectedHistoryId = ref.watch(selectedHistoryIdStateProvider);
|
||||
final historyMetaState = ref.read(historyMetaStateNotifier);
|
||||
if (selectedHistoryId == null) {
|
||||
return null;
|
||||
}
|
||||
final historyMetaState = ref.read(historyMetaStateNotifier);
|
||||
return getHistoryRequestKey(historyMetaState![selectedHistoryId]!);
|
||||
});
|
||||
|
||||
|
@ -37,7 +37,7 @@ class NavbarButton extends ConsumerWidget {
|
||||
if (buttonIdx != null) {
|
||||
ref.read(navRailIndexStateProvider.notifier).state =
|
||||
buttonIdx!;
|
||||
if (railIdx > 1 && buttonIdx! <= 1) {
|
||||
if (railIdx > 2 && buttonIdx! <= 2) {
|
||||
ref.read(leftDrawerStateProvider.notifier).state = false;
|
||||
}
|
||||
}
|
||||
@ -62,7 +62,7 @@ class NavbarButton extends ConsumerWidget {
|
||||
if (buttonIdx != null) {
|
||||
ref.read(navRailIndexStateProvider.notifier).state =
|
||||
buttonIdx!;
|
||||
if (railIdx > 1 && buttonIdx! <= 1) {
|
||||
if (railIdx > 2 && buttonIdx! <= 2) {
|
||||
ref.read(leftDrawerStateProvider.notifier).state =
|
||||
false;
|
||||
}
|
||||
|
@ -70,13 +70,16 @@ class EnvironmentEditor extends ConsumerWidget {
|
||||
margin: EdgeInsets.zero,
|
||||
color: kColorTransparent,
|
||||
surfaceTintColor: kColorTransparent,
|
||||
shape: RoundedRectangleBorder(
|
||||
side: BorderSide(
|
||||
color:
|
||||
Theme.of(context).colorScheme.surfaceContainerHighest,
|
||||
),
|
||||
borderRadius: kBorderRadius12,
|
||||
),
|
||||
shape: context.isMediumWindow
|
||||
? null
|
||||
: RoundedRectangleBorder(
|
||||
side: BorderSide(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.surfaceContainerHighest,
|
||||
),
|
||||
borderRadius: kBorderRadius12,
|
||||
),
|
||||
elevation: 0,
|
||||
child: const Padding(
|
||||
padding: kPv6,
|
||||
|
66
lib/screens/history/details_pane/url_card.dart
Normal file
66
lib/screens/history/details_pane/url_card.dart
Normal 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,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
@ -1,10 +1,42 @@
|
||||
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});
|
||||
|
||||
@override
|
||||
ConsumerState<HistoryDetails> createState() => _HistoryDetailsState();
|
||||
}
|
||||
|
||||
class _HistoryDetailsState extends ConsumerState<HistoryDetails>
|
||||
with TickerProviderStateMixin {
|
||||
@override
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
@ -15,9 +15,7 @@ class HistoryPane extends ConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return Padding(
|
||||
padding: (!context.isMediumWindow && kIsMacOS
|
||||
? kP24CollectionPane
|
||||
: kP8CollectionPane) +
|
||||
padding: (!context.isMediumWindow && kIsMacOS ? kPt24 : kPt8) +
|
||||
(context.isMediumWindow ? kPb70 : EdgeInsets.zero),
|
||||
child: const Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
@ -34,6 +32,7 @@ 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));
|
||||
@ -45,12 +44,7 @@ class HistoryList extends HookConsumerWidget {
|
||||
thumbVisibility: alwaysShowHistoryPaneScrollbar,
|
||||
radius: const Radius.circular(12),
|
||||
child: ListView(
|
||||
padding: context.isMediumWindow
|
||||
? EdgeInsets.only(
|
||||
bottom: MediaQuery.paddingOf(context).bottom,
|
||||
right: 8,
|
||||
)
|
||||
: kPe8,
|
||||
padding: EdgeInsets.only(bottom: MediaQuery.paddingOf(context).bottom),
|
||||
controller: scrollController,
|
||||
children: sortedHistoryKeys != null
|
||||
? sortedHistoryKeys.map((date) {
|
||||
@ -69,8 +63,8 @@ class HistoryList extends HookConsumerWidget {
|
||||
id: item.first.historyId,
|
||||
models: item,
|
||||
method: item.first.method,
|
||||
selectedId:
|
||||
ref.watch(selectedRequestGroupStateProvider),
|
||||
isSelected: selectedGroupId ==
|
||||
getHistoryRequestKey(item.first),
|
||||
requestGroupSize: item.length,
|
||||
onTap: () {
|
||||
ref
|
||||
|
@ -1,10 +1,32 @@
|
||||
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});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container();
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
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(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -57,15 +57,6 @@ class BottomNavBar extends ConsumerWidget {
|
||||
label: 'History',
|
||||
),
|
||||
),
|
||||
// Expanded(
|
||||
// child: NavbarButton(
|
||||
// railIdx: railIdx,
|
||||
// buttonIdx: 2,
|
||||
// selectedIcon: Icons.history,
|
||||
// icon: Icons.history_outlined,
|
||||
// label: 'History',
|
||||
// ),
|
||||
// ),
|
||||
Expanded(
|
||||
child: NavbarButton(
|
||||
railIdx: railIdx,
|
||||
|
@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:apidash/widgets/widgets.dart';
|
||||
import 'package:apidash/consts.dart';
|
||||
import '../../home_page/editor_pane/details_card/response_pane.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 {
|
||||
const RequestResponseTabviews({super.key, required this.controller});
|
||||
final TabController controller;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return TabBarView(controller: controller, children: const [
|
||||
RequestEditor(),
|
||||
Padding(
|
||||
padding: kPt8,
|
||||
child: ResponsePane(),
|
||||
),
|
||||
]);
|
||||
return TabBarView(
|
||||
controller: controller,
|
||||
children: const [
|
||||
RequestEditor(),
|
||||
Padding(
|
||||
padding: kPt8,
|
||||
child: ResponsePane(),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -84,3 +84,20 @@ Map<String, List<HistoryMetaModel>> getRequestGroups(
|
||||
});
|
||||
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;
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ class SidebarHistoryCard extends StatelessWidget {
|
||||
required this.id,
|
||||
required this.models,
|
||||
required this.method,
|
||||
this.selectedId,
|
||||
this.isSelected = false,
|
||||
this.requestGroupSize = 1,
|
||||
this.onTap,
|
||||
});
|
||||
@ -18,7 +18,7 @@ class SidebarHistoryCard extends StatelessWidget {
|
||||
final String id;
|
||||
final List<HistoryMetaModel> models;
|
||||
final HTTPVerb method;
|
||||
final String? selectedId;
|
||||
final bool isSelected;
|
||||
final int requestGroupSize;
|
||||
final Function()? onTap;
|
||||
|
||||
@ -29,7 +29,6 @@ class SidebarHistoryCard extends StatelessWidget {
|
||||
Theme.of(context).colorScheme.surfaceContainerHighest.withOpacity(0.5);
|
||||
final model = models.first;
|
||||
final Color surfaceTint = Theme.of(context).colorScheme.primary;
|
||||
bool isSelected = selectedId == getHistoryRequestKey(model);
|
||||
final String name = getHistoryRequestName(model);
|
||||
return Tooltip(
|
||||
message: name,
|
||||
@ -84,9 +83,9 @@ class SidebarHistoryCard extends StatelessWidget {
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
requestGroupSize == 2
|
||||
? requestGroupSize.toString()
|
||||
: "9+",
|
||||
requestGroupSize > 9
|
||||
? "9+"
|
||||
: requestGroupSize.toString(),
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.labelSmall
|
||||
|
@ -20,38 +20,40 @@ class ErrorMessage extends StatelessWidget {
|
||||
return Padding(
|
||||
padding: kPh20v10,
|
||||
child: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
showIcon
|
||||
? Icon(
|
||||
Icons.warning_rounded,
|
||||
size: 40,
|
||||
color: color,
|
||||
)
|
||||
: const SizedBox(),
|
||||
SelectableText(
|
||||
message ?? 'An error occurred. $kUnexpectedRaiseIssue',
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium
|
||||
?.copyWith(color: color),
|
||||
),
|
||||
kVSpacer20,
|
||||
showIssueButton
|
||||
? FilledButton.tonalIcon(
|
||||
onPressed: () {
|
||||
launchUrl(Uri.parse(kGitUrl));
|
||||
},
|
||||
icon: const Icon(Icons.arrow_outward_rounded),
|
||||
label: Text(
|
||||
'Raise Issue',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
)
|
||||
: const SizedBox(),
|
||||
],
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
showIcon
|
||||
? Icon(
|
||||
Icons.warning_rounded,
|
||||
size: 40,
|
||||
color: color,
|
||||
)
|
||||
: const SizedBox(),
|
||||
SelectableText(
|
||||
message ?? 'An error occurred. $kUnexpectedRaiseIssue',
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium
|
||||
?.copyWith(color: color),
|
||||
),
|
||||
kVSpacer20,
|
||||
showIssueButton
|
||||
? FilledButton.tonalIcon(
|
||||
onPressed: () {
|
||||
launchUrl(Uri.parse(kGitUrl));
|
||||
},
|
||||
icon: const Icon(Icons.arrow_outward_rounded),
|
||||
label: Text(
|
||||
'Raise Issue',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
)
|
||||
: const SizedBox(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -8,16 +8,19 @@ class RawTextField extends StatelessWidget {
|
||||
this.controller,
|
||||
this.hintText,
|
||||
this.style,
|
||||
this.readOnly = false,
|
||||
});
|
||||
|
||||
final void Function(String)? onChanged;
|
||||
final TextEditingController? controller;
|
||||
final String? hintText;
|
||||
final TextStyle? style;
|
||||
final bool readOnly;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return TextField(
|
||||
readOnly: readOnly,
|
||||
controller: controller,
|
||||
onChanged: onChanged,
|
||||
style: style,
|
||||
|
@ -19,7 +19,7 @@ class HistorySplitView extends StatefulWidget {
|
||||
class HistorySplitViewState extends State<HistorySplitView> {
|
||||
final MultiSplitViewController _controller = MultiSplitViewController(
|
||||
areas: [
|
||||
Area(id: "sidebar", min: 200, size: 220, max: 300),
|
||||
Area(id: "sidebar", min: 200, size: 250, max: 300),
|
||||
Area(id: "main"),
|
||||
],
|
||||
);
|
||||
|
56
lib/widgets/tabbar_request_response.dart
Normal file
56
lib/widgets/tabbar_request_response.dart
Normal 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,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -45,6 +45,7 @@ export 'splitview_dashboard.dart';
|
||||
export 'splitview_equal.dart';
|
||||
export 'splitview_history.dart';
|
||||
export 'suggestions_menu.dart';
|
||||
export 'tabbar_request_response.dart';
|
||||
export 'tables.dart';
|
||||
export 'tabs.dart';
|
||||
export 'texts.dart';
|
||||
|
Reference in New Issue
Block a user