mirror of
https://github.com/foss42/apidash.git
synced 2025-05-21 00:09:55 +08:00
feat: environment pane
This commit is contained in:
@ -265,7 +265,7 @@ final kColorHttpMethodPut = Colors.amber.shade900;
|
||||
final kColorHttpMethodPatch = kColorHttpMethodPut;
|
||||
final kColorHttpMethodDelete = Colors.red.shade800;
|
||||
|
||||
enum RequestItemMenuOption { edit, delete, duplicate }
|
||||
enum ItemMenuOption { edit, delete, duplicate }
|
||||
|
||||
enum HTTPVerb { get, head, post, put, patch, delete }
|
||||
|
||||
|
@ -7,9 +7,13 @@ part 'environment_model.g.dart';
|
||||
|
||||
@freezed
|
||||
class EnvironmentModel with _$EnvironmentModel {
|
||||
@JsonSerializable(
|
||||
explicitToJson: true,
|
||||
anyMap: true,
|
||||
)
|
||||
const factory EnvironmentModel({
|
||||
required String id,
|
||||
@Default("New Environment") String name,
|
||||
@Default("") String name,
|
||||
@Default([]) List<EnvironmentVariableModel> values,
|
||||
}) = _EnvironmentModel;
|
||||
|
||||
@ -19,6 +23,10 @@ class EnvironmentModel with _$EnvironmentModel {
|
||||
|
||||
@freezed
|
||||
class EnvironmentVariableModel with _$EnvironmentVariableModel {
|
||||
@JsonSerializable(
|
||||
explicitToJson: true,
|
||||
anyMap: true,
|
||||
)
|
||||
const factory EnvironmentVariableModel({
|
||||
required String key,
|
||||
required String value,
|
||||
|
@ -118,11 +118,12 @@ class __$$EnvironmentModelImplCopyWithImpl<$Res>
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
|
||||
@JsonSerializable(explicitToJson: true, anyMap: true)
|
||||
class _$EnvironmentModelImpl implements _EnvironmentModel {
|
||||
const _$EnvironmentModelImpl(
|
||||
{required this.id,
|
||||
this.name = "New Environment",
|
||||
this.name = "",
|
||||
final List<EnvironmentVariableModel> values = const []})
|
||||
: _values = values;
|
||||
|
||||
@ -320,7 +321,8 @@ class __$$EnvironmentVariableModelImplCopyWithImpl<$Res>
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
|
||||
@JsonSerializable(explicitToJson: true, anyMap: true)
|
||||
class _$EnvironmentVariableModelImpl implements _EnvironmentVariableModel {
|
||||
const _$EnvironmentVariableModelImpl(
|
||||
{required this.key,
|
||||
|
@ -6,14 +6,13 @@ part of 'environment_model.dart';
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_$EnvironmentModelImpl _$$EnvironmentModelImplFromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
_$EnvironmentModelImpl _$$EnvironmentModelImplFromJson(Map json) =>
|
||||
_$EnvironmentModelImpl(
|
||||
id: json['id'] as String,
|
||||
name: json['name'] as String? ?? "New Environment",
|
||||
name: json['name'] as String? ?? "",
|
||||
values: (json['values'] as List<dynamic>?)
|
||||
?.map((e) =>
|
||||
EnvironmentVariableModel.fromJson(e as Map<String, dynamic>))
|
||||
?.map((e) => EnvironmentVariableModel.fromJson(
|
||||
Map<String, Object?>.from(e as Map)))
|
||||
.toList() ??
|
||||
const [],
|
||||
);
|
||||
@ -23,11 +22,11 @@ Map<String, dynamic> _$$EnvironmentModelImplToJson(
|
||||
<String, dynamic>{
|
||||
'id': instance.id,
|
||||
'name': instance.name,
|
||||
'values': instance.values,
|
||||
'values': instance.values.map((e) => e.toJson()).toList(),
|
||||
};
|
||||
|
||||
_$EnvironmentVariableModelImpl _$$EnvironmentVariableModelImplFromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
Map json) =>
|
||||
_$EnvironmentVariableModelImpl(
|
||||
key: json['key'] as String,
|
||||
value: json['value'] as String,
|
||||
|
@ -1,12 +1,10 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
import '../consts.dart';
|
||||
import 'package:apidash/consts.dart';
|
||||
import 'providers.dart';
|
||||
import '../models/models.dart';
|
||||
import '../services/services.dart' show hiveHandler, HiveHandler, request;
|
||||
import '../utils/utils.dart' show getNewUuid, collectionToHAR;
|
||||
import 'settings_providers.dart';
|
||||
import 'ui_providers.dart';
|
||||
|
||||
final selectedIdStateProvider = StateProvider<String?>((ref) => null);
|
||||
|
||||
|
@ -8,14 +8,13 @@ import '../services/services.dart' show hiveHandler, HiveHandler;
|
||||
final selectedEnvironmentIdStateProvider =
|
||||
StateProvider<String?>((ref) => null);
|
||||
|
||||
final environmentsStateNotifierProvider = StateNotifierProvider<
|
||||
EnvironmentsStateNotifier, Map<String, EnvironmentModel>?>((ref) {
|
||||
return EnvironmentsStateNotifier(ref, hiveHandler);
|
||||
});
|
||||
final StateNotifierProvider<EnvironmentsStateNotifier,
|
||||
Map<String, EnvironmentModel>?> environmentsStateNotifierProvider =
|
||||
StateNotifierProvider((ref) => EnvironmentsStateNotifier(ref, hiveHandler));
|
||||
|
||||
final environmentSequenceProvider = StateProvider<List<String>>((ref) {
|
||||
var ids = hiveHandler.getEnvironmentIds();
|
||||
return ids ?? [];
|
||||
return ids ?? [kGlobalEnvironmentId];
|
||||
});
|
||||
|
||||
class EnvironmentsStateNotifier
|
||||
@ -25,7 +24,7 @@ class EnvironmentsStateNotifier
|
||||
Future.microtask(() {
|
||||
if (status) {
|
||||
ref.read(environmentSequenceProvider.notifier).state = [
|
||||
state!.keys.first,
|
||||
...state!.keys,
|
||||
];
|
||||
}
|
||||
ref.read(selectedEnvironmentIdStateProvider.notifier).state =
|
||||
@ -52,9 +51,10 @@ class EnvironmentsStateNotifier
|
||||
} else {
|
||||
Map<String, EnvironmentModel> environmentsMap = {};
|
||||
for (var environmentId in environmentIds) {
|
||||
var environment = hiveHandler.getEnvironment(environmentId);
|
||||
EnvironmentModel environmentModelFromJson =
|
||||
EnvironmentModel.fromJson(environment);
|
||||
var jsonModel = hiveHandler.getEnvironment(environmentId);
|
||||
if (jsonModel != null) {
|
||||
var jsonMap = Map<String, Object?>.from(jsonModel);
|
||||
var environmentModelFromJson = EnvironmentModel.fromJson(jsonMap);
|
||||
|
||||
EnvironmentModel environmentModel = EnvironmentModel(
|
||||
id: environmentModelFromJson.id,
|
||||
@ -63,6 +63,7 @@ class EnvironmentsStateNotifier
|
||||
);
|
||||
environmentsMap[environmentId] = environmentModel;
|
||||
}
|
||||
}
|
||||
state = environmentsMap;
|
||||
return true;
|
||||
}
|
||||
@ -160,10 +161,10 @@ class EnvironmentsStateNotifier
|
||||
ref.read(hasUnsavedChangesProvider.notifier).state = true;
|
||||
}
|
||||
|
||||
void saveEnvironments() async {
|
||||
Future<void> saveEnvironments() async {
|
||||
ref.read(saveDataStateProvider.notifier).state = true;
|
||||
final environmentIds = ref.read(environmentSequenceProvider);
|
||||
hiveHandler.setEnvironmentIds(environmentIds);
|
||||
await hiveHandler.setEnvironmentIds(environmentIds);
|
||||
for (var environmentId in environmentIds) {
|
||||
var environment = state![environmentId]!;
|
||||
await hiveHandler.setEnvironment(environmentId, environment.toJson());
|
||||
|
112
lib/screens/common/sidebar_widgets.dart
Normal file
112
lib/screens/common/sidebar_widgets.dart
Normal file
@ -0,0 +1,112 @@
|
||||
import 'package:flutter/material.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';
|
||||
|
||||
class SidebarHeader extends StatelessWidget {
|
||||
const SidebarHeader({super.key, this.onAddNew});
|
||||
final Function()? onAddNew;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: kPe8,
|
||||
child: Wrap(
|
||||
alignment: WrapAlignment.spaceBetween,
|
||||
children: [
|
||||
const SaveButton(),
|
||||
//const Spacer(),
|
||||
ElevatedButton(
|
||||
onPressed: onAddNew,
|
||||
child: const Text(
|
||||
kLabelPlusNew,
|
||||
style: kTextStyleButton,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SidebarFilter extends StatelessWidget {
|
||||
const SidebarFilter({
|
||||
super.key,
|
||||
this.onFilterFieldChanged,
|
||||
this.filterHintText,
|
||||
});
|
||||
|
||||
final Function(String)? onFilterFieldChanged;
|
||||
final String? filterHintText;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(right: 8),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: kBorderRadius8,
|
||||
border: Border.all(
|
||||
color: Theme.of(context).colorScheme.surfaceVariant,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
kHSpacer5,
|
||||
Icon(
|
||||
Icons.filter_alt,
|
||||
size: 18,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
kHSpacer5,
|
||||
Expanded(
|
||||
child: RawTextField(
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
hintText: filterHintText ?? "Filter by name",
|
||||
onChanged: onFilterFieldChanged,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SaveButton extends ConsumerWidget {
|
||||
const SaveButton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final overlayWidget = OverlayWidgetTemplate(context: context);
|
||||
final savingData = ref.watch(saveDataStateProvider);
|
||||
final hasUnsavedChanges = ref.watch(hasUnsavedChangesProvider);
|
||||
return TextButton.icon(
|
||||
onPressed: (savingData || !hasUnsavedChanges)
|
||||
? null
|
||||
: () async {
|
||||
overlayWidget.show(
|
||||
widget: const SavingOverlay(saveCompleted: false));
|
||||
|
||||
await ref
|
||||
.read(collectionStateNotifierProvider.notifier)
|
||||
.saveData();
|
||||
await ref
|
||||
.read(environmentsStateNotifierProvider.notifier)
|
||||
.saveEnvironments();
|
||||
overlayWidget.hide();
|
||||
overlayWidget.show(
|
||||
widget: const SavingOverlay(saveCompleted: true));
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
overlayWidget.hide();
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.save,
|
||||
size: 20,
|
||||
),
|
||||
label: const Text(
|
||||
kLabelSave,
|
||||
style: kTextStyleButton,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -13,6 +13,7 @@ class Dashboard extends ConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final railIdx = ref.watch(navRailIndexStateProvider);
|
||||
final mobileScaffoldKey = ref.watch(mobileScaffoldKeyStateProvider);
|
||||
return Scaffold(
|
||||
body: SafeArea(
|
||||
child: Row(
|
||||
@ -93,11 +94,13 @@ class Dashboard extends ConsumerWidget {
|
||||
child: IndexedStack(
|
||||
alignment: AlignmentDirectional.topCenter,
|
||||
index: railIdx,
|
||||
children: const [
|
||||
HomePage(),
|
||||
EnvironmentPage(),
|
||||
IntroPage(),
|
||||
SettingsPage(),
|
||||
children: [
|
||||
const HomePage(),
|
||||
EnvironmentPage(
|
||||
scaffoldKey: mobileScaffoldKey,
|
||||
),
|
||||
const IntroPage(),
|
||||
const SettingsPage(),
|
||||
],
|
||||
),
|
||||
)
|
||||
|
@ -1,12 +1,29 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:apidash/widgets/widgets.dart';
|
||||
import 'package:apidash/extensions/extensions.dart';
|
||||
import 'package:apidash/providers/providers.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'environments_pane.dart';
|
||||
|
||||
class EnvironmentPage extends StatelessWidget {
|
||||
const EnvironmentPage({super.key});
|
||||
class EnvironmentPage extends ConsumerWidget {
|
||||
const EnvironmentPage({
|
||||
super.key,
|
||||
required this.scaffoldKey,
|
||||
});
|
||||
|
||||
final GlobalKey<ScaffoldState> scaffoldKey;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
if (context.isMediumWindow) {
|
||||
return TwoDrawerScaffold(
|
||||
scaffoldKey: scaffoldKey,
|
||||
mainContent: const SizedBox(), // TODO: replace placeholder
|
||||
title: const Text("Environments"), // TODO: replace placeholder
|
||||
leftDrawerContent: const EnvironmentsPane(),
|
||||
onDrawerChanged: (value) =>
|
||||
ref.read(leftDrawerStateProvider.notifier).state = value,
|
||||
);
|
||||
}
|
||||
return const Column(
|
||||
children: [
|
||||
Expanded(
|
||||
|
@ -1,11 +1,12 @@
|
||||
import 'package:apidash/consts.dart';
|
||||
import 'package:apidash/extensions/extensions.dart';
|
||||
import 'package:apidash/models/environment_model.dart';
|
||||
import 'package:apidash/providers/providers.dart';
|
||||
import 'package:apidash/widgets/cards.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:apidash/extensions/extensions.dart';
|
||||
import 'package:apidash/models/environment_model.dart';
|
||||
import 'package:apidash/providers/providers.dart';
|
||||
import 'package:apidash/widgets/widgets.dart';
|
||||
import 'package:apidash/consts.dart';
|
||||
import 'package:apidash/screens/common/sidebar_widgets.dart';
|
||||
|
||||
class EnvironmentsPane extends ConsumerWidget {
|
||||
const EnvironmentsPane({
|
||||
@ -15,30 +16,31 @@ class EnvironmentsPane extends ConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return Padding(
|
||||
padding: kIsMacOS ? kP24CollectionPane : kP8CollectionPane,
|
||||
padding: (!context.isMediumWindow && kIsMacOS
|
||||
? kP24CollectionPane
|
||||
: kP8CollectionPane) +
|
||||
(context.isMediumWindow ? kPb70 : EdgeInsets.zero),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Padding(
|
||||
padding: kPe8,
|
||||
child: Wrap(
|
||||
alignment: WrapAlignment.spaceBetween,
|
||||
children: [
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
SidebarHeader(
|
||||
onAddNew: () {
|
||||
ref
|
||||
.read(environmentsStateNotifierProvider.notifier)
|
||||
.addEnvironment();
|
||||
},
|
||||
child: const Text(
|
||||
kLabelPlusNew,
|
||||
style: kTextStyleButton,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
kVSpacer10,
|
||||
SidebarFilter(
|
||||
filterHintText: "Filter by name",
|
||||
onFilterFieldChanged: (value) {
|
||||
ref.read(environmentSearchQueryProvider.notifier).state =
|
||||
value.toLowerCase();
|
||||
},
|
||||
),
|
||||
kVSpacer10,
|
||||
const Expanded(child: EnvironmentsList()),
|
||||
kVSpacer5
|
||||
],
|
||||
),
|
||||
);
|
||||
@ -59,7 +61,18 @@ class EnvironmentsList extends HookConsumerWidget {
|
||||
final filterQuery = ref.watch(environmentSearchQueryProvider).trim();
|
||||
|
||||
ScrollController scrollController = useScrollController();
|
||||
return Scrollbar(
|
||||
return Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: kP1 + kPe8,
|
||||
child: EnvironmentItem(
|
||||
id: kGlobalEnvironmentId,
|
||||
environmentModel: environmentItems[kGlobalEnvironmentId]!,
|
||||
),
|
||||
),
|
||||
const Divider(endIndent: 4),
|
||||
Expanded(
|
||||
child: Scrollbar(
|
||||
controller: scrollController,
|
||||
thumbVisibility: alwaysShowEnvironmentsPaneScrollbar,
|
||||
radius: const Radius.circular(12),
|
||||
@ -80,12 +93,17 @@ class EnvironmentsList extends HookConsumerWidget {
|
||||
}
|
||||
if (oldIndex != newIndex) {
|
||||
ref
|
||||
.read(collectionStateNotifierProvider.notifier)
|
||||
.read(environmentsStateNotifierProvider.notifier)
|
||||
.reorder(oldIndex, newIndex);
|
||||
}
|
||||
},
|
||||
itemBuilder: (context, index) {
|
||||
var id = environmentSequence[index];
|
||||
if (id == kGlobalEnvironmentId) {
|
||||
return SizedBox.shrink(
|
||||
key: ValueKey(id),
|
||||
);
|
||||
}
|
||||
if (kIsMobile) {
|
||||
return ReorderableDelayedDragStartListener(
|
||||
key: ValueKey(id),
|
||||
@ -122,6 +140,11 @@ class EnvironmentsList extends HookConsumerWidget {
|
||||
controller: scrollController,
|
||||
children: environmentSequence.map((id) {
|
||||
var item = environmentItems[id]!;
|
||||
if (id == kGlobalEnvironmentId) {
|
||||
return SizedBox.shrink(
|
||||
key: ValueKey(id),
|
||||
);
|
||||
}
|
||||
if (item.name.toLowerCase().contains(filterQuery)) {
|
||||
return Padding(
|
||||
padding: kP1,
|
||||
@ -134,6 +157,9 @@ class EnvironmentsList extends HookConsumerWidget {
|
||||
return const SizedBox();
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -176,7 +202,29 @@ class EnvironmentItem extends ConsumerWidget {
|
||||
.updateEnvironment(editRequestId!, name: value);
|
||||
},
|
||||
onTapOutsideNameEditor: () {
|
||||
ref.read(selectedEnvironmentIdStateProvider.notifier).state = null;
|
||||
ref.read(selectedIdEditStateProvider.notifier).state = null;
|
||||
},
|
||||
onMenuSelected: (ItemMenuOption item) {
|
||||
if (item == ItemMenuOption.edit) {
|
||||
ref.read(selectedIdEditStateProvider.notifier).state = id;
|
||||
Future.delayed(
|
||||
const Duration(milliseconds: 150),
|
||||
() => ref
|
||||
.read(nameTextFieldFocusNodeProvider.notifier)
|
||||
.state
|
||||
.requestFocus(),
|
||||
);
|
||||
}
|
||||
if (item == ItemMenuOption.delete) {
|
||||
ref
|
||||
.read(environmentsStateNotifierProvider.notifier)
|
||||
.removeEnvironment(id);
|
||||
}
|
||||
if (item == ItemMenuOption.duplicate) {
|
||||
ref
|
||||
.read(environmentsStateNotifierProvider.notifier)
|
||||
.duplicateEnvironment(id);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -1,10 +1,11 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:apidash/providers/providers.dart';
|
||||
import 'package:apidash/extensions/extensions.dart';
|
||||
import 'package:apidash/widgets/widgets.dart';
|
||||
import 'package:apidash/models/models.dart';
|
||||
import 'package:apidash/consts.dart';
|
||||
import 'package:apidash/extensions/extensions.dart';
|
||||
import 'package:apidash/screens/common/sidebar_widgets.dart';
|
||||
|
||||
class CollectionPane extends ConsumerWidget {
|
||||
const CollectionPane({
|
||||
@ -13,20 +14,13 @@ class CollectionPane extends ConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final overlayWidget = OverlayWidgetTemplate(context: context);
|
||||
final collection = ref.watch(collectionStateNotifierProvider);
|
||||
final savingData = ref.watch(saveDataStateProvider);
|
||||
final hasUnsavedChanges = ref.watch(hasUnsavedChangesProvider);
|
||||
if (collection == null) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
return Drawer(
|
||||
shape: const ContinuousRectangleBorder(),
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
surfaceTintColor: kColorTransparent,
|
||||
child: Padding(
|
||||
return Padding(
|
||||
padding: (!context.isMediumWindow && kIsMacOS
|
||||
? kP24CollectionPane
|
||||
: kP8CollectionPane) +
|
||||
@ -34,82 +28,19 @@ class CollectionPane extends ConsumerWidget {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Padding(
|
||||
padding: kPe8,
|
||||
child: Wrap(
|
||||
alignment: WrapAlignment.spaceBetween,
|
||||
children: [
|
||||
TextButton.icon(
|
||||
onPressed: (savingData || !hasUnsavedChanges)
|
||||
? null
|
||||
: () async {
|
||||
overlayWidget.show(
|
||||
widget:
|
||||
const SavingOverlay(saveCompleted: false));
|
||||
|
||||
await ref
|
||||
.read(collectionStateNotifierProvider.notifier)
|
||||
.saveData();
|
||||
overlayWidget.hide();
|
||||
overlayWidget.show(
|
||||
widget:
|
||||
const SavingOverlay(saveCompleted: true));
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
overlayWidget.hide();
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.save,
|
||||
size: 20,
|
||||
),
|
||||
label: const Text(
|
||||
kLabelSave,
|
||||
style: kTextStyleButton,
|
||||
),
|
||||
),
|
||||
//const Spacer(),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
SidebarHeader(
|
||||
onAddNew: () {
|
||||
ref.read(collectionStateNotifierProvider.notifier).add();
|
||||
},
|
||||
child: const Text(
|
||||
kLabelPlusNew,
|
||||
style: kTextStyleButton,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
kVSpacer10,
|
||||
Container(
|
||||
margin: const EdgeInsets.only(right: 8),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: kBorderRadius8,
|
||||
border: Border.all(
|
||||
color: Theme.of(context).colorScheme.surfaceVariant,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
kHSpacer5,
|
||||
Icon(
|
||||
Icons.filter_alt,
|
||||
size: 18,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
kHSpacer5,
|
||||
Expanded(
|
||||
child: RawTextField(
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
hintText: "Filter by name or URL",
|
||||
onChanged: (value) {
|
||||
SidebarFilter(
|
||||
filterHintText: "Filter by name or url",
|
||||
onFilterFieldChanged: (value) {
|
||||
ref.read(collectionSearchQueryProvider.notifier).state =
|
||||
value.toLowerCase();
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
kVSpacer10,
|
||||
const Expanded(
|
||||
child: RequestList(),
|
||||
@ -117,7 +48,6 @@ class CollectionPane extends ConsumerWidget {
|
||||
kVSpacer5
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -277,8 +207,8 @@ class RequestItem extends ConsumerWidget {
|
||||
onTapOutsideNameEditor: () {
|
||||
ref.read(selectedIdEditStateProvider.notifier).state = null;
|
||||
},
|
||||
onMenuSelected: (RequestItemMenuOption item) {
|
||||
if (item == RequestItemMenuOption.edit) {
|
||||
onMenuSelected: (ItemMenuOption item) {
|
||||
if (item == ItemMenuOption.edit) {
|
||||
// var controller =
|
||||
// ref.read(nameTextFieldControllerProvider.notifier).state;
|
||||
// controller.text = requestModel.name;
|
||||
@ -294,10 +224,10 @@ class RequestItem extends ConsumerWidget {
|
||||
.requestFocus(),
|
||||
);
|
||||
}
|
||||
if (item == RequestItemMenuOption.delete) {
|
||||
if (item == ItemMenuOption.delete) {
|
||||
ref.read(collectionStateNotifierProvider.notifier).remove(id);
|
||||
}
|
||||
if (item == RequestItemMenuOption.duplicate) {
|
||||
if (item == ItemMenuOption.duplicate) {
|
||||
ref.read(collectionStateNotifierProvider.notifier).duplicate(id);
|
||||
}
|
||||
},
|
||||
|
@ -1,4 +1,4 @@
|
||||
import 'package:apidash/consts.dart';
|
||||
import 'package:apidash/screens/envvar/environment_page.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
@ -69,19 +69,8 @@ class PageBranch extends ConsumerWidget {
|
||||
switch (pageIndex) {
|
||||
case 1:
|
||||
// Temporary Environment Page
|
||||
return Scaffold(
|
||||
key: scaffoldKey,
|
||||
appBar: AppBar(
|
||||
title: const Text('Environments'),
|
||||
),
|
||||
onDrawerChanged: (isOpened) {
|
||||
ref.read(leftDrawerStateProvider.notifier).state = isOpened;
|
||||
},
|
||||
drawer: const Drawer(
|
||||
surfaceTintColor: kColorTransparent,
|
||||
shape: ContinuousRectangleBorder(),
|
||||
),
|
||||
body: const SizedBox(),
|
||||
return EnvironmentPage(
|
||||
scaffoldKey: scaffoldKey,
|
||||
);
|
||||
case 2:
|
||||
return const PageBase(
|
||||
|
@ -1,6 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:apidash/extensions/extensions.dart';
|
||||
import 'package:apidash/providers/providers.dart';
|
||||
|
||||
class BottomNavBar extends ConsumerWidget {
|
||||
|
@ -47,7 +47,7 @@ class RequestTitle extends ConsumerWidget {
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: RequestCardMenu(
|
||||
child: ItemCardMenu(
|
||||
offset: const Offset(0, 40),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||
splashRadius: 0,
|
||||
@ -75,18 +75,18 @@ class RequestTitle extends ConsumerWidget {
|
||||
],
|
||||
),
|
||||
),
|
||||
onSelected: (RequestItemMenuOption item) {
|
||||
if (item == RequestItemMenuOption.edit) {
|
||||
onSelected: (ItemMenuOption item) {
|
||||
if (item == ItemMenuOption.edit) {
|
||||
showRenameDialog(context, name, (val) {
|
||||
ref
|
||||
.read(collectionStateNotifierProvider.notifier)
|
||||
.update(id!, name: val);
|
||||
});
|
||||
}
|
||||
if (item == RequestItemMenuOption.delete) {
|
||||
if (item == ItemMenuOption.delete) {
|
||||
ref.read(collectionStateNotifierProvider.notifier).remove(id!);
|
||||
}
|
||||
if (item == RequestItemMenuOption.duplicate) {
|
||||
if (item == ItemMenuOption.duplicate) {
|
||||
ref.read(collectionStateNotifierProvider.notifier).duplicate(id!);
|
||||
}
|
||||
},
|
||||
|
6
lib/utils/envvar_utils.dart
Normal file
6
lib/utils/envvar_utils.dart
Normal file
@ -0,0 +1,6 @@
|
||||
String getEnvironmentTitle(String? name) {
|
||||
if (name == null || name.trim() == "") {
|
||||
return "untitled";
|
||||
}
|
||||
return name;
|
||||
}
|
@ -4,3 +4,4 @@ export 'http_utils.dart';
|
||||
export 'file_utils.dart';
|
||||
export 'window_utils.dart';
|
||||
export 'har_utils.dart';
|
||||
export 'envvar_utils.dart';
|
||||
|
@ -212,30 +212,6 @@ class DiscordButton extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class SaveButton extends StatelessWidget {
|
||||
const SaveButton({
|
||||
super.key,
|
||||
this.onPressed,
|
||||
});
|
||||
|
||||
final VoidCallback? onPressed;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return TextButton.icon(
|
||||
onPressed: onPressed,
|
||||
icon: const Icon(
|
||||
Icons.save,
|
||||
size: 20,
|
||||
),
|
||||
label: const Text(
|
||||
kLabelSave,
|
||||
style: kTextStyleButton,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ClearResponseButton extends StatelessWidget {
|
||||
const ClearResponseButton({
|
||||
super.key,
|
||||
|
@ -1,7 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:apidash/consts.dart';
|
||||
import 'package:apidash/utils/utils.dart';
|
||||
import 'menus.dart' show RequestCardMenu;
|
||||
import 'menus.dart' show ItemCardMenu;
|
||||
import 'texts.dart' show MethodBox;
|
||||
|
||||
class SidebarRequestCard extends StatelessWidget {
|
||||
@ -36,7 +36,7 @@ class SidebarRequestCard extends StatelessWidget {
|
||||
// final TextEditingController? controller;
|
||||
final FocusNode? focusNode;
|
||||
final Function()? onTapOutsideNameEditor;
|
||||
final Function(RequestItemMenuOption)? onMenuSelected;
|
||||
final Function(ItemMenuOption)? onMenuSelected;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -118,7 +118,7 @@ class SidebarRequestCard extends StatelessWidget {
|
||||
visible: isSelected && !inEditMode,
|
||||
child: SizedBox(
|
||||
width: 28,
|
||||
child: RequestCardMenu(
|
||||
child: ItemCardMenu(
|
||||
onSelected: onMenuSelected,
|
||||
),
|
||||
),
|
||||
@ -185,19 +185,19 @@ class SidebarEnvironmentCard extends StatelessWidget {
|
||||
final Function(String)? onChangedNameEditor;
|
||||
final FocusNode? focusNode;
|
||||
final Function()? onTapOutsideNameEditor;
|
||||
final Function(RequestItemMenuOption)? onMenuSelected;
|
||||
final Function(ItemMenuOption)? onMenuSelected;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final Color color = Theme.of(context).colorScheme.surface;
|
||||
final Color colorVariant =
|
||||
Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.5);
|
||||
final Color surfaceTint = Theme.of(context).colorScheme.primary;
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final Color color = colorScheme.surface;
|
||||
final Color colorVariant = colorScheme.surfaceVariant.withOpacity(0.5);
|
||||
final Color surfaceTint = colorScheme.primary;
|
||||
bool isSelected = selectedId == id;
|
||||
bool inEditMode = editRequestId == id;
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
String nm = getEnvironmentTitle(name);
|
||||
return Tooltip(
|
||||
message: name,
|
||||
message: nm,
|
||||
triggerMode: TooltipTriggerMode.manual,
|
||||
waitDuration: const Duration(seconds: 1),
|
||||
child: Card(
|
||||
@ -230,22 +230,6 @@ class SidebarEnvironmentCard extends StatelessWidget {
|
||||
height: 20,
|
||||
child: Row(
|
||||
children: [
|
||||
isGlobal
|
||||
? const SizedBox.shrink()
|
||||
: Checkbox(
|
||||
value: isActive,
|
||||
onChanged: isActive ? null : setActive,
|
||||
shape: const CircleBorder(),
|
||||
checkColor: colorScheme.onPrimary,
|
||||
fillColor: MaterialStateProperty.resolveWith<Color?>(
|
||||
(Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.selected)) {
|
||||
return colorScheme.primary;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
),
|
||||
kHSpacer4,
|
||||
Expanded(
|
||||
child: inEditMode
|
||||
@ -271,16 +255,16 @@ class SidebarEnvironmentCard extends StatelessWidget {
|
||||
),
|
||||
)
|
||||
: Text(
|
||||
name ?? "h",
|
||||
nm,
|
||||
softWrap: false,
|
||||
overflow: TextOverflow.fade,
|
||||
),
|
||||
),
|
||||
Visibility(
|
||||
visible: isSelected && !inEditMode,
|
||||
visible: isSelected && !inEditMode && !isGlobal,
|
||||
child: SizedBox(
|
||||
width: 28,
|
||||
child: RequestCardMenu(
|
||||
child: ItemCardMenu(
|
||||
onSelected: onMenuSelected,
|
||||
),
|
||||
),
|
||||
|
@ -1,8 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:apidash/consts.dart';
|
||||
|
||||
class RequestCardMenu extends StatelessWidget {
|
||||
const RequestCardMenu({
|
||||
class ItemCardMenu extends StatelessWidget {
|
||||
const ItemCardMenu({
|
||||
super.key,
|
||||
this.onSelected,
|
||||
this.child,
|
||||
@ -17,11 +17,11 @@ class RequestCardMenu extends StatelessWidget {
|
||||
final String? tooltip;
|
||||
final ShapeBorder? shape;
|
||||
|
||||
final Function(RequestItemMenuOption)? onSelected;
|
||||
final Function(ItemMenuOption)? onSelected;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return PopupMenuButton<RequestItemMenuOption>(
|
||||
return PopupMenuButton<ItemMenuOption>(
|
||||
tooltip: tooltip,
|
||||
padding: EdgeInsets.zero,
|
||||
splashRadius: splashRadius,
|
||||
@ -29,18 +29,17 @@ class RequestCardMenu extends StatelessWidget {
|
||||
offset: offset,
|
||||
onSelected: onSelected,
|
||||
shape: shape,
|
||||
itemBuilder: (BuildContext context) =>
|
||||
<PopupMenuEntry<RequestItemMenuOption>>[
|
||||
const PopupMenuItem<RequestItemMenuOption>(
|
||||
value: RequestItemMenuOption.edit,
|
||||
itemBuilder: (BuildContext context) => <PopupMenuEntry<ItemMenuOption>>[
|
||||
const PopupMenuItem<ItemMenuOption>(
|
||||
value: ItemMenuOption.edit,
|
||||
child: Text('Rename'),
|
||||
),
|
||||
const PopupMenuItem<RequestItemMenuOption>(
|
||||
value: RequestItemMenuOption.delete,
|
||||
const PopupMenuItem<ItemMenuOption>(
|
||||
value: ItemMenuOption.delete,
|
||||
child: Text('Delete'),
|
||||
),
|
||||
const PopupMenuItem<RequestItemMenuOption>(
|
||||
value: RequestItemMenuOption.duplicate,
|
||||
const PopupMenuItem<ItemMenuOption>(
|
||||
value: ItemMenuOption.duplicate,
|
||||
child: Text('Duplicate'),
|
||||
),
|
||||
],
|
||||
|
@ -179,7 +179,12 @@ class TwoDrawerScaffold extends StatelessWidget {
|
||||
: const SizedBox.shrink()),
|
||||
],
|
||||
),
|
||||
drawer: leftDrawerContent,
|
||||
drawer: Drawer(
|
||||
shape: const ContinuousRectangleBorder(),
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
surfaceTintColor: kColorTransparent,
|
||||
child: leftDrawerContent,
|
||||
),
|
||||
endDrawer: rightDrawerContent,
|
||||
body: mainContent,
|
||||
bottomNavigationBar: bottomNavigationBar,
|
||||
|
@ -2,6 +2,7 @@ import 'dart:typed_data';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:apidash/widgets/buttons.dart';
|
||||
import 'package:apidash/screens/common/sidebar_widgets.dart';
|
||||
import 'package:apidash/consts.dart';
|
||||
import '../test_consts.dart';
|
||||
|
||||
|
@ -5,7 +5,7 @@ import 'package:apidash/consts.dart';
|
||||
import '../test_consts.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('Testing RequestCardMenu', (tester) async {
|
||||
testWidgets('Testing ItemCardMenu', (tester) async {
|
||||
dynamic changedValue;
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
@ -15,7 +15,7 @@ void main() {
|
||||
body: Center(
|
||||
child: Column(
|
||||
children: [
|
||||
RequestCardMenu(
|
||||
ItemCardMenu(
|
||||
onSelected: (value) {
|
||||
changedValue = value;
|
||||
},
|
||||
@ -27,9 +27,9 @@ void main() {
|
||||
),
|
||||
);
|
||||
|
||||
expect(find.byType(PopupMenuButton<RequestItemMenuOption>), findsOneWidget);
|
||||
expect(find.byType(PopupMenuButton<ItemMenuOption>), findsOneWidget);
|
||||
|
||||
await tester.tap(find.byType(PopupMenuButton<RequestItemMenuOption>));
|
||||
await tester.tap(find.byType(PopupMenuButton<ItemMenuOption>));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(seconds: 1));
|
||||
|
||||
@ -37,9 +37,9 @@ void main() {
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(seconds: 1));
|
||||
|
||||
expect(changedValue, RequestItemMenuOption.delete);
|
||||
expect(changedValue, ItemMenuOption.delete);
|
||||
|
||||
await tester.tap(find.byType(PopupMenuButton<RequestItemMenuOption>));
|
||||
await tester.tap(find.byType(PopupMenuButton<ItemMenuOption>));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(seconds: 1));
|
||||
|
||||
@ -47,6 +47,6 @@ void main() {
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(seconds: 1));
|
||||
|
||||
expect(changedValue, RequestItemMenuOption.duplicate);
|
||||
expect(changedValue, ItemMenuOption.duplicate);
|
||||
});
|
||||
}
|
||||
|
Reference in New Issue
Block a user