diff --git a/lib/consts.dart b/lib/consts.dart index 5e19d0b4..a7da2f26 100644 --- a/lib/consts.dart +++ b/lib/consts.dart @@ -83,6 +83,7 @@ const kPh20v5 = EdgeInsets.symmetric(horizontal: 20, vertical: 5); const kPh20v10 = EdgeInsets.symmetric(horizontal: 20, vertical: 10); const kP10 = EdgeInsets.all(10); const kPv8 = EdgeInsets.symmetric(vertical: 8); +const kPv6 = EdgeInsets.symmetric(vertical: 6); const kPv2 = EdgeInsets.symmetric(vertical: 2); const kPh2 = EdgeInsets.symmetric(horizontal: 2); const kPt24o8 = EdgeInsets.only(top: 24, left: 8.0, right: 8.0, bottom: 8.0); @@ -118,6 +119,9 @@ const kP8CollectionPane = EdgeInsets.only( //right: 4.0, // bottom: 8.0, ); +const kPt8 = EdgeInsets.only( + top: 8, +); const kPt28 = EdgeInsets.only( top: 28, ); @@ -137,6 +141,7 @@ const kHSpacer4 = SizedBox(width: 4); const kHSpacer5 = SizedBox(width: 5); const kHSpacer10 = SizedBox(width: 10); const kHSpacer20 = SizedBox(width: 20); +const kHSpacer40 = SizedBox(width: 40); const kVSpacer5 = SizedBox(height: 5); const kVSpacer8 = SizedBox(height: 8); const kVSpacer10 = SizedBox(height: 10); diff --git a/lib/screens/common/main_editor_widgets.dart b/lib/screens/common/main_editor_widgets.dart index 5453d226..698dfeb1 100644 --- a/lib/screens/common/main_editor_widgets.dart +++ b/lib/screens/common/main_editor_widgets.dart @@ -74,21 +74,21 @@ class EnvironmentDropdown extends ConsumerWidget { ?.removeWhere((element) => element.id == kGlobalEnvironmentId); final activeEnvironment = ref.watch(activeEnvironmentIdStateProvider); return Container( - decoration: BoxDecoration( - border: Border.all( - color: Theme.of(context).colorScheme.outlineVariant, + decoration: BoxDecoration( + border: Border.all( + color: Theme.of(context).colorScheme.outlineVariant, + ), + borderRadius: kBorderRadius8, ), - borderRadius: kBorderRadius8, - ), - child: DropdownButtonEnvironment( - activeEnvironment: environments?[activeEnvironment], - environments: environmentsList, - onChanged: (value) { - ref.read(activeEnvironmentIdStateProvider.notifier).state = value?.id; - ref.read(hasUnsavedChangesProvider.notifier).state = true; - }, - ), - ); + child: EnvironmentPopupMenu( + activeEnvironment: environments?[activeEnvironment], + environments: environmentsList, + onChanged: (value) { + ref.read(activeEnvironmentIdStateProvider.notifier).state = + value?.id; + ref.read(hasUnsavedChangesProvider.notifier).state = true; + }, + )); } } @@ -122,7 +122,7 @@ class TitleActionsArray extends StatelessWidget { borderRadius: kBorderRadius20, ), child: SizedBox( - height: 36, + height: 32, child: IntrinsicHeight( child: Row( children: [ diff --git a/lib/screens/common/sidebar_widgets.dart b/lib/screens/common/sidebar_widgets.dart index 440a171b..d0381b7b 100644 --- a/lib/screens/common/sidebar_widgets.dart +++ b/lib/screens/common/sidebar_widgets.dart @@ -1,22 +1,23 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:apidash/providers/providers.dart'; +import 'package:apidash/extensions/extensions.dart'; import 'package:apidash/widgets/widgets.dart'; import 'package:apidash/consts.dart'; -class SidebarHeader extends StatelessWidget { +class SidebarHeader extends ConsumerWidget { const SidebarHeader({super.key, this.onAddNew}); final Function()? onAddNew; @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { + final mobileScaffoldKey = ref.read(mobileScaffoldKeyStateProvider); return Padding( padding: kPe8, - child: Wrap( - alignment: WrapAlignment.spaceBetween, + child: Row( children: [ const SaveButton(), - //const Spacer(), + const Spacer(), ElevatedButton( onPressed: onAddNew, child: const Text( @@ -24,6 +25,17 @@ class SidebarHeader extends StatelessWidget { style: kTextStyleButton, ), ), + context.isCompactWindow + ? IconButton( + style: IconButton.styleFrom( + padding: const EdgeInsets.all(4), + minimumSize: const Size(30, 30)), + onPressed: () { + mobileScaffoldKey.currentState?.closeDrawer(); + }, + icon: const Icon(Icons.chevron_left), + ) + : const SizedBox.shrink(), ], ), ); diff --git a/lib/screens/envvar/environment_editor.dart b/lib/screens/envvar/environment_editor.dart index 51fff195..5136b3ae 100644 --- a/lib/screens/envvar/environment_editor.dart +++ b/lib/screens/envvar/environment_editor.dart @@ -4,7 +4,7 @@ import 'package:apidash/providers/providers.dart'; import 'package:apidash/extensions/extensions.dart'; import 'package:apidash/consts.dart'; import '../common/main_editor_widgets.dart'; -import 'editor_pane/variables_tabs.dart'; +import './editor_pane/variables_pane.dart'; class EnvironmentEditor extends ConsumerWidget { const EnvironmentEditor({super.key}); @@ -57,28 +57,43 @@ class EnvironmentEditor extends ConsumerWidget { .removeEnvironment(id!); }, ), - kHSpacer10, - const EnvironmentDropdown(), kHSpacer4, ], ) : const SizedBox.shrink(), kVSpacer5, Expanded( - child: context.isMediumWindow - ? const VariablesTabs() - : Container( - padding: kP6, - margin: kP4, - decoration: BoxDecoration( + child: Container( + padding: context.isMediumWindow ? null : kPv6, + margin: context.isMediumWindow ? null : kP4, + decoration: context.isMediumWindow + ? null + : BoxDecoration( border: Border.all( color: Theme.of(context).colorScheme.outlineVariant, width: 1, ), borderRadius: kBorderRadius12, ), - child: const VariablesTabs(), + child: const Column( + children: [ + kHSpacer40, + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox(width: 30), + Text("Variable"), + SizedBox(width: 30), + Text("Value"), + SizedBox(width: 40), + ], ), + kHSpacer40, + Divider(), + Expanded(child: EditEnvironmentVariables()) + ], + ), + ), ), ], ), diff --git a/lib/screens/envvar/environment_page.dart b/lib/screens/envvar/environment_page.dart index e84b15b0..35bbc11c 100644 --- a/lib/screens/envvar/environment_page.dart +++ b/lib/screens/envvar/environment_page.dart @@ -49,12 +49,7 @@ class EnvironmentPage extends ConsumerWidget { }, ), leftDrawerContent: const EnvironmentsPane(), - actions: const [ - Padding( - padding: kPh8, - child: EnvironmentDropdown(), - ), - ], + actions: const [SizedBox(width: 16)], onDrawerChanged: (value) => ref.read(leftDrawerStateProvider.notifier).state = value, ); diff --git a/lib/screens/envvar/environments_pane.dart b/lib/screens/envvar/environments_pane.dart index 690a5e51..84a40528 100644 --- a/lib/screens/envvar/environments_pane.dart +++ b/lib/screens/envvar/environments_pane.dart @@ -6,7 +6,7 @@ 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'; +import '../common/sidebar_widgets.dart'; class EnvironmentsPane extends ConsumerWidget { const EnvironmentsPane({ @@ -70,7 +70,6 @@ class EnvironmentsList extends HookConsumerWidget { environmentModel: environmentItems[kGlobalEnvironmentId]!, ), ), - const Divider(endIndent: 4), Expanded( child: Scrollbar( controller: scrollController, diff --git a/lib/screens/home_page/editor_pane/editor_request.dart b/lib/screens/home_page/editor_pane/editor_request.dart index ca5d6064..d62d53a5 100644 --- a/lib/screens/home_page/editor_pane/editor_request.dart +++ b/lib/screens/home_page/editor_pane/editor_request.dart @@ -18,12 +18,7 @@ class RequestEditor extends StatelessWidget { padding: kPb10, child: Column( children: [ - kVSpacer5, - Padding( - padding: kPh8, - child: EditorPaneRequestURLCard(), - ), - kVSpacer10, + kVSpacer20, Expanded( child: EditRequestPane(), ), diff --git a/lib/screens/mobile/dashboard.dart b/lib/screens/mobile/dashboard.dart index a502b7f1..d1642be8 100644 --- a/lib/screens/mobile/dashboard.dart +++ b/lib/screens/mobile/dashboard.dart @@ -1,4 +1,3 @@ -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'; @@ -8,7 +7,8 @@ import 'package:apidash/providers/providers.dart'; import '../intro_page.dart'; import '../settings_page.dart'; import 'navbar.dart'; -import 'requests_page.dart'; +import 'requests_page/requests_page.dart'; +import '../envvar/environment_page.dart'; import 'widgets/page_base.dart'; class MobileDashboard extends ConsumerStatefulWidget { @@ -84,7 +84,7 @@ class PageBranch extends ConsumerWidget { scaffoldBody: SettingsPage(), ); default: - return RequestsPage( + return RequestResponsePage( scaffoldKey: scaffoldKey, ); } diff --git a/lib/screens/mobile/requests_page/request_response_tabs.dart b/lib/screens/mobile/requests_page/request_response_tabs.dart new file mode 100644 index 00000000..33b42742 --- /dev/null +++ b/lib/screens/mobile/requests_page/request_response_tabs.dart @@ -0,0 +1,101 @@ +import 'package:flutter/material.dart'; +import 'package:apidash/consts.dart'; +import '../../home_page/editor_pane/details_card/response_pane.dart'; +import '../../home_page/editor_pane/editor_request.dart'; +import '../../home_page/editor_pane/url_card.dart'; + +class RequestResponseTabs extends StatelessWidget { + const RequestResponseTabs({super.key, required this.controller}); + final TabController controller; + + @override + Widget build(BuildContext context) { + return Column( + children: [ + kVSpacer5, + const Padding( + padding: kPh4, + child: EditorPaneRequestURLCard(), + ), + kVSpacer10, + RequestResponseTabbar( + controller: controller, + ), + Expanded( + child: RequestResponseTabviews( + controller: controller, + )) + ], + ); + } +} + +class RequestResponseTabbar extends StatelessWidget { + const RequestResponseTabbar({ + super.key, + required this.controller, + }); + + final TabController controller; + + @override + Widget build(BuildContext context) { + return Center( + child: Container( + width: 280, + height: 32, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(50), + border: Border.all( + color: Theme.of(context).colorScheme.outlineVariant, + ), + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(50), + 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: BorderRadius.circular(50), + indicator: BoxDecoration( + borderRadius: BorderRadius.circular(50), + color: Theme.of(context).colorScheme.primary, + ), + controller: controller, + tabs: const [ + Tab( + text: "Request", + ), + Tab( + text: "Response", + ), + ], + ), + ), + ), + ); + } +} + +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(), + ), + ]); + } +} diff --git a/lib/screens/mobile/requests_page.dart b/lib/screens/mobile/requests_page/requests_page.dart similarity index 65% rename from lib/screens/mobile/requests_page.dart rename to lib/screens/mobile/requests_page/requests_page.dart index 15216fa1..3cde85cf 100644 --- a/lib/screens/mobile/requests_page.dart +++ b/lib/screens/mobile/requests_page/requests_page.dart @@ -1,19 +1,19 @@ import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:apidash/providers/providers.dart'; import 'package:apidash/utils/http_utils.dart'; import 'package:apidash/consts.dart'; import 'package:apidash/widgets/widgets.dart'; -import '../home_page/collection_pane.dart'; -import '../home_page/editor_pane/editor_pane.dart'; -import '../home_page/editor_pane/url_card.dart'; -import '../home_page/editor_pane/details_card/code_pane.dart'; -import '../common/main_editor_widgets.dart'; -import 'response_drawer.dart'; -import 'widgets/page_base.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import '../../home_page/collection_pane.dart'; +import '../../home_page/editor_pane/url_card.dart'; +import '../../home_page/editor_pane/details_card/code_pane.dart'; +import '../../common/main_editor_widgets.dart'; +import '../widgets/page_base.dart'; +import 'request_response_tabs.dart'; -class RequestsPage extends ConsumerWidget { - const RequestsPage({ +class RequestResponsePage extends StatefulHookConsumerWidget { + const RequestResponsePage({ super.key, required this.scaffoldKey, }); @@ -21,12 +21,21 @@ class RequestsPage extends ConsumerWidget { final GlobalKey scaffoldKey; @override - Widget build(BuildContext context, WidgetRef ref) { + ConsumerState createState() => + _RequestResponsePageState(); +} + +class _RequestResponsePageState extends ConsumerState + with TickerProviderStateMixin { + @override + Widget build(BuildContext context) { final id = ref.watch(selectedIdStateProvider); final name = getRequestTitleFromUrl( ref.watch(selectedRequestModelProvider.select((value) => value?.name))); + final TabController requestResponseTabController = + useTabController(initialLength: 2, vsync: this); return TwoDrawerScaffold( - scaffoldKey: scaffoldKey, + scaffoldKey: widget.scaffoldKey, title: ScaffoldTitle( title: name, onSelected: (ItemMenuOption item) { @@ -46,19 +55,25 @@ class RequestsPage extends ConsumerWidget { }, ), leftDrawerContent: const CollectionPane(), - rightDrawerContent: const ResponseDrawer(), - mainContent: const RequestEditorPane(), - bottomNavigationBar: const RequestPageBottombar(), + actions: const [Padding(padding: kPh8, child: EnvironmentDropdown())], + mainContent: RequestResponseTabs( + controller: requestResponseTabController, + ), + bottomNavigationBar: RequestResponsePageBottombar( + requestResponseTabController: requestResponseTabController, + ), onDrawerChanged: (value) => ref.read(leftDrawerStateProvider.notifier).state = value, ); } } -class RequestPageBottombar extends ConsumerWidget { - const RequestPageBottombar({ +class RequestResponsePageBottombar extends ConsumerWidget { + const RequestResponsePageBottombar({ super.key, + required this.requestResponseTabController, }); + final TabController requestResponseTabController; @override Widget build(BuildContext context, WidgetRef ref) { @@ -104,10 +119,9 @@ class RequestPageBottombar extends ConsumerWidget { ), SendButton( onTap: () { - ref - .read(mobileScaffoldKeyStateProvider) - .currentState! - .openEndDrawer(); + if (requestResponseTabController.index != 1) { + requestResponseTabController.animateTo(1); + } }, ), ], diff --git a/lib/screens/mobile/response_drawer.dart b/lib/screens/mobile/response_drawer.dart deleted file mode 100644 index e7295c27..00000000 --- a/lib/screens/mobile/response_drawer.dart +++ /dev/null @@ -1,33 +0,0 @@ -import 'package:flutter/material.dart'; -import '../home_page/editor_pane/details_card/response_pane.dart'; - -class ResponseDrawer extends StatelessWidget { - const ResponseDrawer({super.key}); - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - backgroundColor: Theme.of(context).colorScheme.surface, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.vertical(top: Radius.circular(8)), - ), - leading: IconButton( - icon: const Icon(Icons.arrow_back_rounded), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - scrolledUnderElevation: 0, - centerTitle: true, - title: const Text("Response"), - ), - body: Padding( - padding: EdgeInsets.only( - bottom: MediaQuery.paddingOf(context).bottom, - ), - child: const ResponsePane(), - ), - ); - } -} diff --git a/lib/widgets/cards.dart b/lib/widgets/cards.dart index 1d4615e7..03861925 100644 --- a/lib/widgets/cards.dart +++ b/lib/widgets/cards.dart @@ -190,7 +190,8 @@ class SidebarEnvironmentCard extends StatelessWidget { @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; - final Color color = colorScheme.surface; + final Color color = + isGlobal ? colorScheme.secondaryContainer : colorScheme.surface; final Color colorVariant = colorScheme.surfaceVariant.withOpacity(0.5); final Color surfaceTint = colorScheme.primary; bool isSelected = selectedId == id; @@ -206,7 +207,7 @@ class SidebarEnvironmentCard extends StatelessWidget { ), elevation: isSelected ? 1 : 0, surfaceTintColor: isSelected ? surfaceTint : null, - color: isSelected + color: isSelected && !isGlobal ? colorScheme.brightness == Brightness.dark ? colorVariant : color diff --git a/lib/widgets/dropdowns.dart b/lib/widgets/dropdowns.dart index d421beea..d21c3be4 100644 --- a/lib/widgets/dropdowns.dart +++ b/lib/widgets/dropdowns.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:apidash/utils/utils.dart'; import 'package:apidash/consts.dart'; import 'package:apidash/extensions/extensions.dart'; -import 'package:apidash/models/models.dart'; class DropdownButtonHttpMethod extends StatelessWidget { const DropdownButtonHttpMethod({ @@ -188,67 +187,3 @@ class DropdownButtonCodegenLanguage extends StatelessWidget { ); } } - -class DropdownButtonEnvironment extends StatelessWidget { - const DropdownButtonEnvironment({ - super.key, - this.activeEnvironment, - this.onChanged, - this.environments, - }); - - final EnvironmentModel? activeEnvironment; - final void Function(EnvironmentModel? value)? onChanged; - final List? environments; - final EnvironmentModel? noneEnvironmentModel = null; - @override - Widget build(BuildContext context) { - final surfaceColor = Theme.of(context).colorScheme.surface; - final characterLimit = context.isCompactWindow ? 12 : 15; - return DropdownButton( - isDense: true, - padding: kPs0o6, - focusColor: surfaceColor, - value: activeEnvironment, - icon: const Icon(Icons.unfold_more_rounded), - elevation: 4, - underline: Container( - height: 0, - ), - borderRadius: kBorderRadius8, - onChanged: onChanged, - items: [ - DropdownMenuItem( - value: noneEnvironmentModel, - child: Padding( - padding: EdgeInsets.only(left: context.isMediumWindow ? 8 : 16), - child: Text( - "No Environment", - style: kTextStyleButtonSmall.copyWith( - fontWeight: FontWeight.bold, - ), - ), - ), - ), - ...environments?.map>( - (EnvironmentModel environmentModel) { - final name = getEnvironmentTitle(environmentModel.name); - return DropdownMenuItem( - value: environmentModel, - child: Padding( - padding: - EdgeInsets.only(left: context.isMediumWindow ? 8 : 16), - child: Text( - name.clip(characterLimit), - style: kTextStyleButtonSmall.copyWith( - fontWeight: FontWeight.bold, - ), - ), - ), - ); - }).toList() ?? - [] - ], - ); - } -} diff --git a/lib/widgets/popup_menus.dart b/lib/widgets/popup_menus.dart new file mode 100644 index 00000000..a5c682bf --- /dev/null +++ b/lib/widgets/popup_menus.dart @@ -0,0 +1,72 @@ +import 'package:apidash/consts.dart'; +import 'package:apidash/extensions/extensions.dart'; +import 'package:flutter/material.dart'; +import 'package:apidash/models/models.dart'; +import 'package:apidash/utils/utils.dart'; + +class EnvironmentPopupMenu extends StatelessWidget { + const EnvironmentPopupMenu({ + super.key, + this.activeEnvironment, + this.onChanged, + this.environments, + }); + + final EnvironmentModel? activeEnvironment; + final void Function(EnvironmentModel? value)? onChanged; + final List? environments; + final EnvironmentModel? noneEnvironmentModel = null; + @override + Widget build(BuildContext context) { + final activeEnvironmentName = getEnvironmentTitle(activeEnvironment?.name); + final textClipLength = context.isCompactWindow ? 6 : 10; + final double boxLength = context.isCompactWindow ? 100 : 130; + return PopupMenuButton( + tooltip: "Select Environment", + surfaceTintColor: kColorTransparent, + constraints: BoxConstraints(minWidth: boxLength), + itemBuilder: (BuildContext context) { + return [ + PopupMenuItem( + value: noneEnvironmentModel, + onTap: () { + onChanged?.call(null); + }, + child: const Text("None"), + ), + ...environments!.map((EnvironmentModel environment) { + final name = getEnvironmentTitle(environment.name); + return PopupMenuItem( + value: environment, + child: Text( + name, + softWrap: false, + overflow: TextOverflow.ellipsis, + ), + ); + }) + ]; + }, + onSelected: onChanged, + child: Container( + width: boxLength, + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + activeEnvironment == null + ? "None" + : activeEnvironmentName.clip(textClipLength), + softWrap: false, + ), + const Icon( + Icons.unfold_more, + size: 16, + ) + ], + ), + ), + ); + } +} diff --git a/lib/widgets/widgets.dart b/lib/widgets/widgets.dart index 4d249939..bba295f5 100644 --- a/lib/widgets/widgets.dart +++ b/lib/widgets/widgets.dart @@ -13,6 +13,7 @@ export 'json_previewer.dart'; export 'markdown.dart'; export 'menus.dart'; export 'overlay_widget.dart'; +export 'popup_menus.dart'; export 'previewer.dart'; export 'request_widgets.dart'; export 'response_widgets.dart';