wip: environment pane

This commit is contained in:
DenserMeerkat
2024-06-13 13:21:35 +05:30
parent 74ce9799f4
commit 8228b28a24
15 changed files with 245 additions and 127 deletions

View File

@ -1,81 +0,0 @@
import 'package:flutter/foundation.dart';
import 'environment_model.dart';
@immutable
class EnvironmentListModel {
const EnvironmentListModel({
this.actveEnvironment,
this.globalEnvironment = const EnvironmentModel(id: "global"),
this.environments = const [],
});
final EnvironmentModel? actveEnvironment;
final EnvironmentModel globalEnvironment;
final List<EnvironmentModel> environments;
EnvironmentListModel copyWith({
EnvironmentModel? actveEnvironment,
EnvironmentModel? globalEnvironment,
List<EnvironmentModel>? environments,
}) {
return EnvironmentListModel(
actveEnvironment: actveEnvironment ?? this.actveEnvironment,
globalEnvironment: globalEnvironment ?? this.globalEnvironment,
environments: environments ?? this.environments,
);
}
factory EnvironmentListModel.fromJson(Map<dynamic, dynamic> data) {
final actveEnvironment = data["actveEnvironment"] != null
? EnvironmentModel.fromJson(
data["actveEnvironment"] as Map<String, dynamic>)
: null;
final globalEnvironment = EnvironmentModel.fromJson(
data["globalEnvironment"] as Map<String, dynamic>);
final List<dynamic> environments = data["environments"] as List<dynamic>;
const em = EnvironmentListModel();
return em.copyWith(
actveEnvironment: actveEnvironment,
globalEnvironment: globalEnvironment,
environments: environments
.map((dynamic e) =>
EnvironmentModel.fromJson(e as Map<String, dynamic>))
.toList(),
);
}
Map<String, dynamic> toJson() {
return {
"actveEnvironment": actveEnvironment?.toJson(),
"globalEnvironment": globalEnvironment.toJson(),
"environments": environments.map((e) => e.toJson()).toList(),
};
}
EnvironmentModel getEnvironment(String id) {
if (id == "global") {
return globalEnvironment;
}
return environments.firstWhere((e) => e.id == id);
}
@override
bool operator ==(Object other) {
return other is EnvironmentListModel &&
other.actveEnvironment == actveEnvironment &&
other.globalEnvironment == globalEnvironment &&
listEquals(other.environments, environments);
}
@override
int get hashCode {
return Object.hash(
actveEnvironment,
globalEnvironment,
environments,
);
}
}

View File

@ -9,7 +9,7 @@ part 'environment_model.g.dart';
class EnvironmentModel with _$EnvironmentModel { class EnvironmentModel with _$EnvironmentModel {
const factory EnvironmentModel({ const factory EnvironmentModel({
required String id, required String id,
@Default("") String name, @Default("New Environment") String name,
@Default([]) List<EnvironmentVariableModel> values, @Default([]) List<EnvironmentVariableModel> values,
}) = _EnvironmentModel; }) = _EnvironmentModel;

View File

@ -122,7 +122,7 @@ class __$$EnvironmentModelImplCopyWithImpl<$Res>
class _$EnvironmentModelImpl implements _EnvironmentModel { class _$EnvironmentModelImpl implements _EnvironmentModel {
const _$EnvironmentModelImpl( const _$EnvironmentModelImpl(
{required this.id, {required this.id,
this.name = "", this.name = "New Environment",
final List<EnvironmentVariableModel> values = const []}) final List<EnvironmentVariableModel> values = const []})
: _values = values; : _values = values;

View File

@ -10,7 +10,7 @@ _$EnvironmentModelImpl _$$EnvironmentModelImplFromJson(
Map<String, dynamic> json) => Map<String, dynamic> json) =>
_$EnvironmentModelImpl( _$EnvironmentModelImpl(
id: json['id'] as String, id: json['id'] as String,
name: json['name'] as String? ?? "", name: json['name'] as String? ?? "New Environment",
values: (json['values'] as List<dynamic>?) values: (json['values'] as List<dynamic>?)
?.map((e) => ?.map((e) =>
EnvironmentVariableModel.fromJson(e as Map<String, dynamic>)) EnvironmentVariableModel.fromJson(e as Map<String, dynamic>))

View File

@ -8,7 +8,7 @@ part of 'http_response_model.dart';
_$HttpResponseModelImpl _$$HttpResponseModelImplFromJson(Map json) => _$HttpResponseModelImpl _$$HttpResponseModelImplFromJson(Map json) =>
_$HttpResponseModelImpl( _$HttpResponseModelImpl(
statusCode: json['statusCode'] as int?, statusCode: (json['statusCode'] as num?)?.toInt(),
headers: (json['headers'] as Map?)?.map( headers: (json['headers'] as Map?)?.map(
(k, e) => MapEntry(k as String, e as String), (k, e) => MapEntry(k as String, e as String),
), ),
@ -19,7 +19,7 @@ _$HttpResponseModelImpl _$$HttpResponseModelImplFromJson(Map json) =>
formattedBody: json['formattedBody'] as String?, formattedBody: json['formattedBody'] as String?,
bodyBytes: bodyBytes:
const Uint8ListConverter().fromJson(json['bodyBytes'] as List<int>?), const Uint8ListConverter().fromJson(json['bodyBytes'] as List<int>?),
time: const DurationConverter().fromJson(json['time'] as int?), time: const DurationConverter().fromJson((json['time'] as num?)?.toInt()),
); );
Map<String, dynamic> _$$HttpResponseModelImplToJson( Map<String, dynamic> _$$HttpResponseModelImplToJson(

View File

@ -15,7 +15,7 @@ _$RequestModelImpl _$$RequestModelImplFromJson(Map json) => _$RequestModelImpl(
? null ? null
: HttpRequestModel.fromJson( : HttpRequestModel.fromJson(
Map<String, Object?>.from(json['httpRequestModel'] as Map)), Map<String, Object?>.from(json['httpRequestModel'] as Map)),
responseStatus: json['responseStatus'] as int?, responseStatus: (json['responseStatus'] as num?)?.toInt(),
message: json['message'] as String?, message: json['message'] as String?,
httpResponseModel: json['httpResponseModel'] == null httpResponseModel: json['httpResponseModel'] == null
? null ? null

View File

@ -5,7 +5,8 @@ import 'package:apidash/utils/file_utils.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../services/services.dart' show hiveHandler, HiveHandler; import '../services/services.dart' show hiveHandler, HiveHandler;
final selectedEnvironmentIdProvider = StateProvider<String?>((ref) => null); final selectedEnvironmentIdStateProvider =
StateProvider<String?>((ref) => null);
final environmentsStateNotifierProvider = StateNotifierProvider< final environmentsStateNotifierProvider = StateNotifierProvider<
EnvironmentsStateNotifier, Map<String, EnvironmentModel>?>((ref) { EnvironmentsStateNotifier, Map<String, EnvironmentModel>?>((ref) {
@ -27,8 +28,8 @@ class EnvironmentsStateNotifier
state!.keys.first, state!.keys.first,
]; ];
} }
ref.read(selectedEnvironmentIdProvider.notifier).state = ref.read(selectedEnvironmentIdStateProvider.notifier).state =
ref.read(environmentSequenceProvider)[0]; kGlobalEnvironmentId;
}); });
} }
@ -80,7 +81,7 @@ class EnvironmentsStateNotifier
ref ref
.read(environmentSequenceProvider.notifier) .read(environmentSequenceProvider.notifier)
.update((state) => [id, ...state]); .update((state) => [id, ...state]);
ref.read(selectedEnvironmentIdProvider.notifier).state = ref.read(selectedEnvironmentIdStateProvider.notifier).state =
newEnvironmentModel.id; newEnvironmentModel.id;
ref.read(hasUnsavedChangesProvider.notifier).state = true; ref.read(hasUnsavedChangesProvider.notifier).state = true;
} }
@ -123,7 +124,7 @@ class EnvironmentsStateNotifier
ref ref
.read(environmentSequenceProvider.notifier) .read(environmentSequenceProvider.notifier)
.update((state) => [...environmentIds]); .update((state) => [...environmentIds]);
ref.read(selectedEnvironmentIdProvider.notifier).state = newId; ref.read(selectedEnvironmentIdStateProvider.notifier).state = newId;
ref.read(hasUnsavedChangesProvider.notifier).state = true; ref.read(hasUnsavedChangesProvider.notifier).state = true;
} }
@ -142,7 +143,7 @@ class EnvironmentsStateNotifier
newId = kGlobalEnvironmentId; newId = kGlobalEnvironmentId;
} }
ref.read(selectedEnvironmentIdProvider.notifier).state = newId; ref.read(selectedEnvironmentIdStateProvider.notifier).state = newId;
state = { state = {
...state!, ...state!,

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:apidash/providers/providers.dart'; import 'package:apidash/providers/providers.dart';
import 'package:apidash/consts.dart'; import 'package:apidash/consts.dart';
import 'envvar/environment_page.dart';
import 'home_page/home_page.dart'; import 'home_page/home_page.dart';
import 'intro_page.dart'; import 'intro_page.dart';
import 'settings_page.dart'; import 'settings_page.dart';
@ -43,8 +44,8 @@ class Dashboard extends ConsumerWidget {
onPressed: () { onPressed: () {
ref.read(navRailIndexStateProvider.notifier).state = 1; ref.read(navRailIndexStateProvider.notifier).state = 1;
}, },
icon: const Icon(Icons.auto_awesome_mosaic_outlined), icon: const Icon(Icons.computer_outlined),
selectedIcon: const Icon(Icons.auto_awesome_mosaic), selectedIcon: const Icon(Icons.computer_rounded),
), ),
Text( Text(
'Variables', 'Variables',
@ -94,7 +95,7 @@ class Dashboard extends ConsumerWidget {
index: railIdx, index: railIdx,
children: const [ children: const [
HomePage(), HomePage(),
SizedBox(), EnvironmentPage(),
IntroPage(), IntroPage(),
SettingsPage(), SettingsPage(),
], ],

View File

@ -0,0 +1,21 @@
import 'package:flutter/material.dart';
import 'package:apidash/widgets/widgets.dart';
import 'environments_pane.dart';
class EnvironmentPage extends StatelessWidget {
const EnvironmentPage({super.key});
@override
Widget build(BuildContext context) {
return const Column(
children: [
Expanded(
child: DashboardSplitView(
sidebarWidget: EnvironmentsPane(),
mainWidget: SizedBox(),
),
),
],
);
}
}

View File

@ -2,6 +2,7 @@ import 'package:apidash/consts.dart';
import 'package:apidash/extensions/extensions.dart'; import 'package:apidash/extensions/extensions.dart';
import 'package:apidash/models/environment_model.dart'; import 'package:apidash/models/environment_model.dart';
import 'package:apidash/providers/providers.dart'; import 'package:apidash/providers/providers.dart';
import 'package:apidash/widgets/cards.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
@ -13,7 +14,34 @@ class EnvironmentsPane extends ConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
return const SizedBox(); return Padding(
padding: kIsMacOS ? kP24CollectionPane : kP8CollectionPane,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Padding(
padding: kPe8,
child: Wrap(
alignment: WrapAlignment.spaceBetween,
children: [
ElevatedButton(
onPressed: () {
ref
.read(environmentsStateNotifierProvider.notifier)
.addEnvironment();
},
child: const Text(
kLabelPlusNew,
style: kTextStyleButton,
),
)
],
),
),
const Expanded(child: EnvironmentsList()),
],
),
);
} }
} }
@ -122,8 +150,35 @@ class EnvironmentItem extends ConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final selectedId = ref.watch(selectedEnvironmentIdProvider); final selectedId = ref.watch(selectedEnvironmentIdStateProvider);
final activeEnvironmentId = ref.watch(activeEnvironmentIdStateProvider);
final editRequestId = ref.watch(selectedIdEditStateProvider);
final mobileDrawerKey = ref.watch(mobileDrawerKeyProvider);
return Text(environmentModel.name); return SidebarEnvironmentCard(
id: id,
isActive: id == activeEnvironmentId,
isGlobal: id == kGlobalEnvironmentId,
name: environmentModel.name,
selectedId: selectedId,
editRequestId: editRequestId,
setActive: (value) {
ref.read(activeEnvironmentIdStateProvider.notifier).state = id;
},
onTap: () {
mobileDrawerKey.currentState?.close();
ref.read(selectedEnvironmentIdStateProvider.notifier).state = id;
},
focusNode: ref.watch(nameTextFieldFocusNodeProvider),
onChangedNameEditor: (value) {
value = value.trim();
ref
.read(environmentsStateNotifierProvider.notifier)
.updateEnvironment(editRequestId!, name: value);
},
onTapOutsideNameEditor: () {
ref.read(selectedEnvironmentIdStateProvider.notifier).state = null;
},
);
} }
} }

View File

@ -1,3 +1,4 @@
import 'package:apidash/screens/envvar/environments_pane.dart';
import 'package:apidash/widgets/splitviews.dart'; import 'package:apidash/widgets/splitviews.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
@ -81,7 +82,7 @@ class PageBranch extends StatelessWidget {
offset: !context.isCompactWindow offset: !context.isCompactWindow
? const IDOffset.only(left: 0.1) ? const IDOffset.only(left: 0.1)
: const IDOffset.only(left: 0.7), : const IDOffset.only(left: 0.7),
leftDrawerContent: const SizedBox(), leftDrawerContent: const EnvironmentsPane(),
mainContent: const SizedBox(), mainContent: const SizedBox(),
); );
case 2: case 2:

View File

@ -83,10 +83,11 @@ class NavRail extends ConsumerWidget {
final railIdx = ref.watch(navRailIndexStateProvider); final railIdx = ref.watch(navRailIndexStateProvider);
return Material( return Material(
type: MaterialType.transparency, type: MaterialType.transparency,
child: Container( child: Ink(
width: 70, width: 70,
padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 8), padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 8),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
border: Border( border: Border(
right: BorderSide( right: BorderSide(
color: Theme.of(context).colorScheme.onInverseSurface, color: Theme.of(context).colorScheme.onInverseSurface,
@ -124,6 +125,7 @@ class NavRail extends ConsumerWidget {
Icons.help, Icons.help,
Icons.help_outline, Icons.help_outline,
'About', 'About',
showLabel: false,
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
customNavigationDestination( customNavigationDestination(
@ -134,6 +136,7 @@ class NavRail extends ConsumerWidget {
Icons.settings, Icons.settings,
Icons.settings_outlined, Icons.settings_outlined,
'Settings', 'Settings',
showLabel: false,
), ),
], ],
), ),

View File

@ -1,8 +1,10 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:apidash/consts.dart'; import 'package:apidash/consts.dart';
import 'package:apidash/extensions/extensions.dart';
import 'package:apidash/providers/providers.dart'; import 'package:apidash/providers/providers.dart';
import 'package:apidash/widgets/window_caption.dart'; import 'package:apidash/widgets/window_caption.dart';
import '../navbar.dart';
class PageBase extends ConsumerWidget { class PageBase extends ConsumerWidget {
final String title; final String title;
@ -13,28 +15,40 @@ class PageBase extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final isDarkMode = final isDarkMode =
ref.watch(settingsProvider.select((value) => value.isDark)); ref.watch(settingsProvider.select((value) => value.isDark));
final scaffold = Container(
padding: (context.isCompactWindow
? const EdgeInsets.only(bottom: 70)
: EdgeInsets.zero) +
(kIsWindows || kIsMacOS ? kPt28 : EdgeInsets.zero),
color: Theme.of(context).colorScheme.surface,
child: Scaffold(
backgroundColor: Theme.of(context).colorScheme.background,
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.background,
primary: true,
title: Text(title),
centerTitle: true,
),
body: Padding(
padding: EdgeInsets.only(
bottom: MediaQuery.paddingOf(context).bottom,
),
child: scaffoldBody,
),
),
);
return Stack( return Stack(
children: [ children: [
Container( !context.isCompactWindow
padding: const EdgeInsets.only(bottom: 70) + ? Row(
(kIsWindows || kIsMacOS ? kPt28 : EdgeInsets.zero), children: [
color: Theme.of(context).colorScheme.surface, const NavRail(),
child: Scaffold( Expanded(
backgroundColor: Theme.of(context).colorScheme.background, child: scaffold,
appBar: AppBar( ),
backgroundColor: Theme.of(context).colorScheme.background, ],
primary: true, )
title: Text(title), : scaffold,
centerTitle: true,
),
body: Padding(
padding: EdgeInsets.only(
bottom: MediaQuery.paddingOf(context).bottom,
),
child: scaffoldBody,
),
),
),
if (kIsWindows) if (kIsWindows)
SizedBox( SizedBox(
height: 29, height: 29,

View File

@ -38,7 +38,6 @@ class HiveHandler {
late final Box dataBox; late final Box dataBox;
late final Box settingsBox; late final Box settingsBox;
late final Box environmentBox; late final Box environmentBox;
late final Box environmentIdsBox;
HiveHandler() { HiveHandler() {
dataBox = Hive.box(kDataBox); dataBox = Hive.box(kDataBox);
@ -59,9 +58,9 @@ class HiveHandler {
void delete(String key) => dataBox.delete(key); void delete(String key) => dataBox.delete(key);
dynamic getEnvironmentIds() => environmentIdsBox.get(kKeyEnvironmentBoxIds); dynamic getEnvironmentIds() => environmentBox.get(kKeyEnvironmentBoxIds);
Future<void> setEnvironmentIds(List<String>? ids) => Future<void> setEnvironmentIds(List<String>? ids) =>
environmentIdsBox.put(kKeyEnvironmentBoxIds, ids); environmentBox.put(kKeyEnvironmentBoxIds, ids);
dynamic getEnvironment(String id) => environmentBox.get(id); dynamic getEnvironment(String id) => environmentBox.get(id);
Future<void> setEnvironment( Future<void> setEnvironment(

View File

@ -158,10 +158,11 @@ class SidebarEnvironmentCard extends StatelessWidget {
super.key, super.key,
required this.id, required this.id,
this.isGlobal = false, this.isGlobal = false,
this.isSelected = false,
this.isActive = false, this.isActive = false,
this.name, this.name,
this.selectedId,
this.editRequestId, this.editRequestId,
this.setActive,
this.onTap, this.onTap,
this.onDoubleTap, this.onDoubleTap,
this.onSecondaryTap, this.onSecondaryTap,
@ -173,10 +174,11 @@ class SidebarEnvironmentCard extends StatelessWidget {
final String id; final String id;
final bool isGlobal; final bool isGlobal;
final bool isSelected;
final bool isActive; final bool isActive;
final String? name; final String? name;
final String? selectedId;
final String? editRequestId; final String? editRequestId;
final void Function(bool?)? setActive;
final void Function()? onTap; final void Function()? onTap;
final void Function()? onDoubleTap; final void Function()? onDoubleTap;
final void Function()? onSecondaryTap; final void Function()? onSecondaryTap;
@ -187,6 +189,108 @@ class SidebarEnvironmentCard extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return const SizedBox(); 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;
bool isSelected = selectedId == id;
bool inEditMode = editRequestId == id;
final colorScheme = Theme.of(context).colorScheme;
return Tooltip(
message: name,
triggerMode: TooltipTriggerMode.manual,
waitDuration: const Duration(seconds: 1),
child: Card(
shape: const RoundedRectangleBorder(
borderRadius: kBorderRadius8,
),
elevation: isSelected ? 1 : 0,
surfaceTintColor: isSelected ? surfaceTint : null,
color: isSelected
? colorScheme.brightness == Brightness.dark
? colorVariant
: color
: color,
margin: EdgeInsets.zero,
child: InkWell(
borderRadius: kBorderRadius8,
hoverColor: colorVariant,
focusColor: colorVariant.withOpacity(0.5),
onTap: inEditMode ? null : onTap,
// onDoubleTap: inEditMode ? null : onDoubleTap,
onSecondaryTap: onSecondaryTap,
child: Padding(
padding: EdgeInsets.only(
left: 6,
right: isSelected ? 6 : 10,
top: 5,
bottom: 5,
),
child: SizedBox(
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
? TextFormField(
key: ValueKey("$id-name"),
initialValue: name,
// controller: controller,
focusNode: focusNode,
//autofocus: true,
style: Theme.of(context).textTheme.bodyMedium,
onTapOutside: (_) {
onTapOutsideNameEditor?.call();
//FocusScope.of(context).unfocus();
},
onFieldSubmitted: (value) {
onTapOutsideNameEditor?.call();
},
onChanged: onChangedNameEditor,
decoration: const InputDecoration(
isCollapsed: true,
contentPadding: EdgeInsets.zero,
border: InputBorder.none,
),
)
: Text(
name ?? "h",
softWrap: false,
overflow: TextOverflow.fade,
),
),
Visibility(
visible: isSelected && !inEditMode,
child: SizedBox(
width: 28,
child: RequestCardMenu(
onSelected: onMenuSelected,
),
),
),
],
),
),
),
),
),
);
} }
} }