From 8228b28a242bfd4417839cf4e5bb2febd397e26d Mon Sep 17 00:00:00 2001 From: DenserMeerkat Date: Thu, 13 Jun 2024 13:21:35 +0530 Subject: [PATCH] wip: environment pane --- lib/models/environment_list_model.dart | 81 ---------------- lib/models/environment_model.dart | 2 +- lib/models/environment_model.freezed.dart | 2 +- lib/models/environment_model.g.dart | 2 +- lib/models/http_response_model.g.dart | 4 +- lib/models/request_model.g.dart | 2 +- lib/providers/environment_provider.dart | 13 +-- lib/screens/dashboard.dart | 7 +- lib/screens/envvar/environment_page.dart | 21 +++++ lib/screens/envvar/environments_pane.dart | 61 +++++++++++- lib/screens/mobile/dashboard.dart | 3 +- lib/screens/mobile/navbar.dart | 5 +- lib/screens/mobile/widgets/page_base.dart | 54 +++++++---- lib/services/hive_services.dart | 5 +- lib/widgets/cards.dart | 110 +++++++++++++++++++++- 15 files changed, 245 insertions(+), 127 deletions(-) delete mode 100644 lib/models/environment_list_model.dart diff --git a/lib/models/environment_list_model.dart b/lib/models/environment_list_model.dart deleted file mode 100644 index 98394904..00000000 --- a/lib/models/environment_list_model.dart +++ /dev/null @@ -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 environments; - - EnvironmentListModel copyWith({ - EnvironmentModel? actveEnvironment, - EnvironmentModel? globalEnvironment, - List? environments, - }) { - return EnvironmentListModel( - actveEnvironment: actveEnvironment ?? this.actveEnvironment, - globalEnvironment: globalEnvironment ?? this.globalEnvironment, - environments: environments ?? this.environments, - ); - } - - factory EnvironmentListModel.fromJson(Map data) { - final actveEnvironment = data["actveEnvironment"] != null - ? EnvironmentModel.fromJson( - data["actveEnvironment"] as Map) - : null; - final globalEnvironment = EnvironmentModel.fromJson( - data["globalEnvironment"] as Map); - final List environments = data["environments"] as List; - - const em = EnvironmentListModel(); - - return em.copyWith( - actveEnvironment: actveEnvironment, - globalEnvironment: globalEnvironment, - environments: environments - .map((dynamic e) => - EnvironmentModel.fromJson(e as Map)) - .toList(), - ); - } - - Map 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, - ); - } -} diff --git a/lib/models/environment_model.dart b/lib/models/environment_model.dart index 4a125c58..241c7107 100644 --- a/lib/models/environment_model.dart +++ b/lib/models/environment_model.dart @@ -9,7 +9,7 @@ part 'environment_model.g.dart'; class EnvironmentModel with _$EnvironmentModel { const factory EnvironmentModel({ required String id, - @Default("") String name, + @Default("New Environment") String name, @Default([]) List values, }) = _EnvironmentModel; diff --git a/lib/models/environment_model.freezed.dart b/lib/models/environment_model.freezed.dart index bd36e6c2..76ef9171 100644 --- a/lib/models/environment_model.freezed.dart +++ b/lib/models/environment_model.freezed.dart @@ -122,7 +122,7 @@ class __$$EnvironmentModelImplCopyWithImpl<$Res> class _$EnvironmentModelImpl implements _EnvironmentModel { const _$EnvironmentModelImpl( {required this.id, - this.name = "", + this.name = "New Environment", final List values = const []}) : _values = values; diff --git a/lib/models/environment_model.g.dart b/lib/models/environment_model.g.dart index ce35862f..eb98a99a 100644 --- a/lib/models/environment_model.g.dart +++ b/lib/models/environment_model.g.dart @@ -10,7 +10,7 @@ _$EnvironmentModelImpl _$$EnvironmentModelImplFromJson( Map json) => _$EnvironmentModelImpl( id: json['id'] as String, - name: json['name'] as String? ?? "", + name: json['name'] as String? ?? "New Environment", values: (json['values'] as List?) ?.map((e) => EnvironmentVariableModel.fromJson(e as Map)) diff --git a/lib/models/http_response_model.g.dart b/lib/models/http_response_model.g.dart index 956ad6bd..186fd152 100644 --- a/lib/models/http_response_model.g.dart +++ b/lib/models/http_response_model.g.dart @@ -8,7 +8,7 @@ part of 'http_response_model.dart'; _$HttpResponseModelImpl _$$HttpResponseModelImplFromJson(Map json) => _$HttpResponseModelImpl( - statusCode: json['statusCode'] as int?, + statusCode: (json['statusCode'] as num?)?.toInt(), headers: (json['headers'] as Map?)?.map( (k, e) => MapEntry(k as String, e as String), ), @@ -19,7 +19,7 @@ _$HttpResponseModelImpl _$$HttpResponseModelImplFromJson(Map json) => formattedBody: json['formattedBody'] as String?, bodyBytes: const Uint8ListConverter().fromJson(json['bodyBytes'] as List?), - time: const DurationConverter().fromJson(json['time'] as int?), + time: const DurationConverter().fromJson((json['time'] as num?)?.toInt()), ); Map _$$HttpResponseModelImplToJson( diff --git a/lib/models/request_model.g.dart b/lib/models/request_model.g.dart index 4a6fb3a1..e3e17008 100644 --- a/lib/models/request_model.g.dart +++ b/lib/models/request_model.g.dart @@ -15,7 +15,7 @@ _$RequestModelImpl _$$RequestModelImplFromJson(Map json) => _$RequestModelImpl( ? null : HttpRequestModel.fromJson( Map.from(json['httpRequestModel'] as Map)), - responseStatus: json['responseStatus'] as int?, + responseStatus: (json['responseStatus'] as num?)?.toInt(), message: json['message'] as String?, httpResponseModel: json['httpResponseModel'] == null ? null diff --git a/lib/providers/environment_provider.dart b/lib/providers/environment_provider.dart index 9d1fd067..da68913f 100644 --- a/lib/providers/environment_provider.dart +++ b/lib/providers/environment_provider.dart @@ -5,7 +5,8 @@ import 'package:apidash/utils/file_utils.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../services/services.dart' show hiveHandler, HiveHandler; -final selectedEnvironmentIdProvider = StateProvider((ref) => null); +final selectedEnvironmentIdStateProvider = + StateProvider((ref) => null); final environmentsStateNotifierProvider = StateNotifierProvider< EnvironmentsStateNotifier, Map?>((ref) { @@ -27,8 +28,8 @@ class EnvironmentsStateNotifier state!.keys.first, ]; } - ref.read(selectedEnvironmentIdProvider.notifier).state = - ref.read(environmentSequenceProvider)[0]; + ref.read(selectedEnvironmentIdStateProvider.notifier).state = + kGlobalEnvironmentId; }); } @@ -80,7 +81,7 @@ class EnvironmentsStateNotifier ref .read(environmentSequenceProvider.notifier) .update((state) => [id, ...state]); - ref.read(selectedEnvironmentIdProvider.notifier).state = + ref.read(selectedEnvironmentIdStateProvider.notifier).state = newEnvironmentModel.id; ref.read(hasUnsavedChangesProvider.notifier).state = true; } @@ -123,7 +124,7 @@ class EnvironmentsStateNotifier ref .read(environmentSequenceProvider.notifier) .update((state) => [...environmentIds]); - ref.read(selectedEnvironmentIdProvider.notifier).state = newId; + ref.read(selectedEnvironmentIdStateProvider.notifier).state = newId; ref.read(hasUnsavedChangesProvider.notifier).state = true; } @@ -142,7 +143,7 @@ class EnvironmentsStateNotifier newId = kGlobalEnvironmentId; } - ref.read(selectedEnvironmentIdProvider.notifier).state = newId; + ref.read(selectedEnvironmentIdStateProvider.notifier).state = newId; state = { ...state!, diff --git a/lib/screens/dashboard.dart b/lib/screens/dashboard.dart index 227c4a01..604e5d7d 100644 --- a/lib/screens/dashboard.dart +++ b/lib/screens/dashboard.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:apidash/providers/providers.dart'; import 'package:apidash/consts.dart'; +import 'envvar/environment_page.dart'; import 'home_page/home_page.dart'; import 'intro_page.dart'; import 'settings_page.dart'; @@ -43,8 +44,8 @@ class Dashboard extends ConsumerWidget { onPressed: () { ref.read(navRailIndexStateProvider.notifier).state = 1; }, - icon: const Icon(Icons.auto_awesome_mosaic_outlined), - selectedIcon: const Icon(Icons.auto_awesome_mosaic), + icon: const Icon(Icons.computer_outlined), + selectedIcon: const Icon(Icons.computer_rounded), ), Text( 'Variables', @@ -94,7 +95,7 @@ class Dashboard extends ConsumerWidget { index: railIdx, children: const [ HomePage(), - SizedBox(), + EnvironmentPage(), IntroPage(), SettingsPage(), ], diff --git a/lib/screens/envvar/environment_page.dart b/lib/screens/envvar/environment_page.dart index e69de29b..06dc4249 100644 --- a/lib/screens/envvar/environment_page.dart +++ b/lib/screens/envvar/environment_page.dart @@ -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(), + ), + ), + ], + ); + } +} diff --git a/lib/screens/envvar/environments_pane.dart b/lib/screens/envvar/environments_pane.dart index e5200889..fa884b2b 100644 --- a/lib/screens/envvar/environments_pane.dart +++ b/lib/screens/envvar/environments_pane.dart @@ -2,6 +2,7 @@ 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'; @@ -13,7 +14,34 @@ class EnvironmentsPane extends ConsumerWidget { @override 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 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; + }, + ); } } diff --git a/lib/screens/mobile/dashboard.dart b/lib/screens/mobile/dashboard.dart index 123ccaea..80ef32ce 100644 --- a/lib/screens/mobile/dashboard.dart +++ b/lib/screens/mobile/dashboard.dart @@ -1,3 +1,4 @@ +import 'package:apidash/screens/envvar/environments_pane.dart'; import 'package:apidash/widgets/splitviews.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -81,7 +82,7 @@ class PageBranch extends StatelessWidget { offset: !context.isCompactWindow ? const IDOffset.only(left: 0.1) : const IDOffset.only(left: 0.7), - leftDrawerContent: const SizedBox(), + leftDrawerContent: const EnvironmentsPane(), mainContent: const SizedBox(), ); case 2: diff --git a/lib/screens/mobile/navbar.dart b/lib/screens/mobile/navbar.dart index ab516c5b..e401c305 100644 --- a/lib/screens/mobile/navbar.dart +++ b/lib/screens/mobile/navbar.dart @@ -83,10 +83,11 @@ class NavRail extends ConsumerWidget { final railIdx = ref.watch(navRailIndexStateProvider); return Material( type: MaterialType.transparency, - child: Container( + child: Ink( width: 70, padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 8), decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surface, border: Border( right: BorderSide( color: Theme.of(context).colorScheme.onInverseSurface, @@ -124,6 +125,7 @@ class NavRail extends ConsumerWidget { Icons.help, Icons.help_outline, 'About', + showLabel: false, ), const SizedBox(height: 24), customNavigationDestination( @@ -134,6 +136,7 @@ class NavRail extends ConsumerWidget { Icons.settings, Icons.settings_outlined, 'Settings', + showLabel: false, ), ], ), diff --git a/lib/screens/mobile/widgets/page_base.dart b/lib/screens/mobile/widgets/page_base.dart index 42460831..6df5a566 100644 --- a/lib/screens/mobile/widgets/page_base.dart +++ b/lib/screens/mobile/widgets/page_base.dart @@ -1,8 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:apidash/consts.dart'; +import 'package:apidash/extensions/extensions.dart'; import 'package:apidash/providers/providers.dart'; import 'package:apidash/widgets/window_caption.dart'; +import '../navbar.dart'; class PageBase extends ConsumerWidget { final String title; @@ -13,28 +15,40 @@ class PageBase extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final isDarkMode = 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( children: [ - Container( - padding: const EdgeInsets.only(bottom: 70) + - (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, - ), - ), - ), + !context.isCompactWindow + ? Row( + children: [ + const NavRail(), + Expanded( + child: scaffold, + ), + ], + ) + : scaffold, if (kIsWindows) SizedBox( height: 29, diff --git a/lib/services/hive_services.dart b/lib/services/hive_services.dart index 05cbd1cc..0b5a86fc 100644 --- a/lib/services/hive_services.dart +++ b/lib/services/hive_services.dart @@ -38,7 +38,6 @@ class HiveHandler { late final Box dataBox; late final Box settingsBox; late final Box environmentBox; - late final Box environmentIdsBox; HiveHandler() { dataBox = Hive.box(kDataBox); @@ -59,9 +58,9 @@ class HiveHandler { void delete(String key) => dataBox.delete(key); - dynamic getEnvironmentIds() => environmentIdsBox.get(kKeyEnvironmentBoxIds); + dynamic getEnvironmentIds() => environmentBox.get(kKeyEnvironmentBoxIds); Future setEnvironmentIds(List? ids) => - environmentIdsBox.put(kKeyEnvironmentBoxIds, ids); + environmentBox.put(kKeyEnvironmentBoxIds, ids); dynamic getEnvironment(String id) => environmentBox.get(id); Future setEnvironment( diff --git a/lib/widgets/cards.dart b/lib/widgets/cards.dart index 5983608d..3c25b8b1 100644 --- a/lib/widgets/cards.dart +++ b/lib/widgets/cards.dart @@ -158,10 +158,11 @@ class SidebarEnvironmentCard extends StatelessWidget { super.key, required this.id, this.isGlobal = false, - this.isSelected = false, this.isActive = false, this.name, + this.selectedId, this.editRequestId, + this.setActive, this.onTap, this.onDoubleTap, this.onSecondaryTap, @@ -173,10 +174,11 @@ class SidebarEnvironmentCard extends StatelessWidget { final String id; final bool isGlobal; - final bool isSelected; final bool isActive; final String? name; + final String? selectedId; final String? editRequestId; + final void Function(bool?)? setActive; final void Function()? onTap; final void Function()? onDoubleTap; final void Function()? onSecondaryTap; @@ -187,6 +189,108 @@ class SidebarEnvironmentCard extends StatelessWidget { @override 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( + (Set 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, + ), + ), + ), + ], + ), + ), + ), + ), + ), + ); } }