From 5ef37914b320d7dab002ec6dee1bbb4e821d1fab Mon Sep 17 00:00:00 2001 From: DenserMeerkat Date: Sat, 11 May 2024 22:12:25 +0530 Subject: [PATCH] feat: large mobile device responsiveness --- lib/app.dart | 6 +- lib/consts.dart | 2 + lib/screens/home_page/collection_pane.dart | 4 +- .../home_page/editor_pane/editor_request.dart | 53 ++++--- .../home_page/editor_pane/url_card.dart | 39 +++-- lib/screens/mobile/dashboard.dart | 149 ++++++++++-------- lib/screens/mobile/left_drawer.dart | 19 ++- .../{bottom_navbar.dart => navbar.dart} | 89 +++++++++++ lib/screens/settings_page.dart | 4 +- lib/widgets/dropdowns.dart | 6 +- lib/widgets/intro_message.dart | 4 +- lib/widgets/request_widgets.dart | 4 +- lib/widgets/tabs.dart | 4 +- 13 files changed, 266 insertions(+), 117 deletions(-) rename lib/screens/mobile/{bottom_navbar.dart => navbar.dart} (68%) diff --git a/lib/app.dart b/lib/app.dart index 71948aa3..61efa48f 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -105,6 +105,8 @@ class DashApp extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final isDarkMode = ref.watch(settingsProvider.select((value) => value.isDark)); + final isLargeMobile = + MediaQuery.of(context).size.width > kMinWindowSize.width; return MaterialApp( debugShowCheckedModeBanner: false, theme: ThemeData( @@ -125,7 +127,9 @@ class DashApp extends ConsumerWidget { ), themeMode: isDarkMode ? ThemeMode.dark : ThemeMode.light, home: kIsMobile - ? const MobileDashboard() + ? isLargeMobile + ? const Dashboard() + : const MobileDashboard() : Stack( children: [ kIsLinux ? const Dashboard() : const App(), diff --git a/lib/consts.dart b/lib/consts.dart index 62fa8640..3b2f5529 100644 --- a/lib/consts.dart +++ b/lib/consts.dart @@ -39,6 +39,7 @@ const kMinWindowSize = Size(900, 600); const kMinInitialWindowWidth = 1200.0; const kMinInitialWindowHeight = 800.0; const kMinRequestEditorDetailsCardPaneSize = 300.0; +const kLargeMobileWidth = 600.0; const kColorSchemeSeed = Colors.blue; final kFontFamily = GoogleFonts.openSans().fontFamily; @@ -123,6 +124,7 @@ const kVSpacer40 = SizedBox(height: 40); const kTabAnimationDuration = Duration(milliseconds: 200); const kTabHeight = 32.0; +const kMobileTabHeight = 40.0; const kHeaderHeight = 32.0; const kSegmentHeight = 24.0; const kTextButtonMinWidth = 44.0; diff --git a/lib/screens/home_page/collection_pane.dart b/lib/screens/home_page/collection_pane.dart index 9ac38949..796cb067 100644 --- a/lib/screens/home_page/collection_pane.dart +++ b/lib/screens/home_page/collection_pane.dart @@ -142,6 +142,8 @@ class _RequestListState extends ConsumerState { final alwaysShowCollectionPaneScrollbar = ref.watch(settingsProvider .select((value) => value.alwaysShowCollectionPaneScrollbar)); final filterQuery = ref.watch(searchQueryProvider).trim(); + final isMobile = + kIsMobile && MediaQuery.of(context).size.width < kMinWindowSize.width; return Scrollbar( controller: controller, @@ -149,7 +151,7 @@ class _RequestListState extends ConsumerState { radius: const Radius.circular(12), child: filterQuery.isEmpty ? ReorderableListView.builder( - padding: kIsMobile + padding: isMobile ? EdgeInsets.only( bottom: MediaQuery.of(context).padding.bottom, right: 8, diff --git a/lib/screens/home_page/editor_pane/editor_request.dart b/lib/screens/home_page/editor_pane/editor_request.dart index b2fe4a3f..df72b88a 100644 --- a/lib/screens/home_page/editor_pane/editor_request.dart +++ b/lib/screens/home_page/editor_pane/editor_request.dart @@ -11,27 +11,38 @@ class RequestEditor extends StatelessWidget { @override Widget build(BuildContext context) { - return Padding( - padding: kIsMacOS || kIsWindows - ? kPt24o8 - : !kIsMobile - ? kP8 - : kPb10, - child: Column( - children: [ - !kIsMobile ? const RequestEditorTopBar() : kVSpacer5, - Padding( - padding: !kIsMobile ? EdgeInsets.zero : kPh8, - child: const EditorPaneRequestURLCard()), - kVSpacer10, - Expanded( - child: kIsMobile - ? const EditRequestPane() - : const EditorPaneRequestDetailsCard(), - ), - ], - ), - ); + final isMobile = + kIsMobile && MediaQuery.of(context).size.width < kMinWindowSize.width; + return isMobile + ? const Padding( + padding: kPb10, + child: Column( + children: [ + kVSpacer5, + Padding( + padding: kPh8, + child: EditorPaneRequestURLCard(), + ), + kVSpacer10, + Expanded( + child: EditRequestPane(), + ), + ], + ), + ) + : Padding( + padding: kIsMacOS || kIsWindows ? kPt24o8 : kP8, + child: const Column( + children: [ + RequestEditorTopBar(), + EditorPaneRequestURLCard(), + kVSpacer10, + Expanded( + child: EditorPaneRequestDetailsCard(), + ), + ], + ), + ); } } diff --git a/lib/screens/home_page/editor_pane/url_card.dart b/lib/screens/home_page/editor_pane/url_card.dart index 91bdb9b4..0e3ef663 100644 --- a/lib/screens/home_page/editor_pane/url_card.dart +++ b/lib/screens/home_page/editor_pane/url_card.dart @@ -9,6 +9,8 @@ class EditorPaneRequestURLCard extends StatelessWidget { @override Widget build(BuildContext context) { + final isMobile = + kIsMobile && MediaQuery.of(context).size.width < kMinWindowSize.width; return Card( elevation: 0, shape: RoundedRectangleBorder( @@ -20,24 +22,33 @@ class EditorPaneRequestURLCard extends StatelessWidget { child: Padding( padding: EdgeInsets.symmetric( vertical: 5, - horizontal: !kIsMobile ? 20 : 6, + horizontal: !isMobile ? 20 : 6, ), - child: Row( - children: [ - const DropdownButtonHTTPMethod(), - !kIsMobile ? kHSpacer20 : kHSpacer5, - const Expanded( - child: URLTextField(), - ), - !kIsMobile ? kHSpacer20 : const SizedBox.shrink(), - !kIsMobile - ? const SizedBox( + child: isMobile + ? const Row( + children: [ + DropdownButtonHTTPMethod(), + kHSpacer5, + Expanded( + child: URLTextField(), + ), + SizedBox.shrink(), + ], + ) + : const Row( + children: [ + DropdownButtonHTTPMethod(), + kHSpacer20, + Expanded( + child: URLTextField(), + ), + kHSpacer20, + SizedBox( height: 36, child: SendButton(), ) - : const SizedBox.shrink(), - ], - ), + ], + ), ), ); } diff --git a/lib/screens/mobile/dashboard.dart b/lib/screens/mobile/dashboard.dart index 9e1e5eb6..e3ea5e3e 100644 --- a/lib/screens/mobile/dashboard.dart +++ b/lib/screens/mobile/dashboard.dart @@ -1,10 +1,11 @@ +import 'package:apidash/consts.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:inner_drawer/inner_drawer.dart'; import 'package:flex_color_scheme/flex_color_scheme.dart'; import '../../providers/providers.dart'; -import 'bottom_navbar.dart'; +import 'navbar.dart'; import 'left_drawer.dart'; import 'requests_page/requests_page.dart'; import 'requests_page/response_drawer.dart'; @@ -49,78 +50,86 @@ class _MobileDashboardState extends ConsumerState { opacity: 0, noAppBar: true, ), - child: Stack( - alignment: AlignmentDirectional.bottomCenter, - children: [ - InnerDrawer( - key: innerDrawerKey, - swipe: true, - swipeChild: true, - onTapClose: true, - offset: const IDOffset.only(left: 0.7, right: 1), - boxShadow: [ - BoxShadow( - offset: const Offset(1, 0), - color: Theme.of(context).colorScheme.onInverseSurface, - blurRadius: 0, - ), - ], - colorTransitionChild: Colors.transparent, - colorTransitionScaffold: Colors.transparent, - rightAnimationType: InnerDrawerAnimation.linear, - backgroundDecoration: - BoxDecoration(color: Theme.of(context).colorScheme.surface), - onDragUpdate: (value, direction) { - drawerDirection.value = direction; - if (value > 0.98 && direction == InnerDrawerDirection.start) { - dragPosition.value = 1; - } else { - dragPosition.value = 0; - } - }, - innerDrawerCallback: (isOpened) { - if (drawerDirection.value == InnerDrawerDirection.start) { - setState(() { - isLeftDrawerOpen = isOpened; - }); - } - }, - leftChild: const LeftDrawer( - drawerContent: CollectionPane(), - ), - rightChild: const ResponseDrawer(), - scaffold: ValueListenableBuilder( - valueListenable: dragPosition, - builder: (context, value, child) { - return Container( - color: calculateBackgroundColor(value), - child: child, - ); - }, - child: ClipRRect( - borderRadius: - const BorderRadius.only(topLeft: Radius.circular(8)), - child: SafeArea( - bottom: false, - child: RequestsPage( - innerDrawerKey: innerDrawerKey, + child: LayoutBuilder( + builder: (context, constraints) { + final isLargeMobile = constraints.maxWidth > kLargeMobileWidth; + return Stack( + alignment: AlignmentDirectional.bottomCenter, + children: [ + InnerDrawer( + key: innerDrawerKey, + swipe: true, + swipeChild: true, + onTapClose: true, + offset: isLargeMobile + ? const IDOffset.only(left: 0.1, right: 1) + : const IDOffset.only(left: 0.7, right: 1), + boxShadow: [ + BoxShadow( + offset: const Offset(1, 0), + color: Theme.of(context).colorScheme.onInverseSurface, + blurRadius: 0, + ), + ], + colorTransitionChild: Colors.transparent, + colorTransitionScaffold: Colors.transparent, + rightAnimationType: InnerDrawerAnimation.linear, + backgroundDecoration: + BoxDecoration(color: Theme.of(context).colorScheme.surface), + onDragUpdate: (value, direction) { + drawerDirection.value = direction; + if (value > 0.98 && direction == InnerDrawerDirection.start) { + dragPosition.value = 1; + } else { + dragPosition.value = 0; + } + }, + innerDrawerCallback: (isOpened) { + if (drawerDirection.value == InnerDrawerDirection.start) { + setState(() { + isLeftDrawerOpen = isOpened; + }); + } + }, + leftChild: const LeftDrawer( + drawerContent: CollectionPane(), + ), + rightChild: const ResponseDrawer(), + scaffold: ValueListenableBuilder( + valueListenable: dragPosition, + builder: (context, value, child) { + return Container( + color: calculateBackgroundColor(value), + child: child, + ); + }, + child: ClipRRect( + borderRadius: + const BorderRadius.only(topLeft: Radius.circular(8)), + child: SafeArea( + bottom: false, + child: RequestsPage( + innerDrawerKey: innerDrawerKey, + ), + ), ), ), ), - ), - ), - AnimatedPositioned( - bottom: isLeftDrawerOpen - ? 0 - : -(72 + MediaQuery.of(context).padding.bottom), - left: 0, - right: 0, - height: 70 + MediaQuery.of(context).padding.bottom, - duration: const Duration(milliseconds: 200), - curve: Curves.easeOut, - child: const BottomNavBar(), - ), - ], + if (!isLargeMobile) + AnimatedPositioned( + bottom: isLeftDrawerOpen + ? 0 + : -(72 + MediaQuery.of(context).padding.bottom), + left: 0, + right: 0, + height: 70 + MediaQuery.of(context).padding.bottom, + duration: const Duration(milliseconds: 200), + curve: Curves.easeOut, + child: const BottomNavBar(), + ), + ], + ); + }, ), ); } diff --git a/lib/screens/mobile/left_drawer.dart b/lib/screens/mobile/left_drawer.dart index b9cb0d4a..8ce3243d 100644 --- a/lib/screens/mobile/left_drawer.dart +++ b/lib/screens/mobile/left_drawer.dart @@ -1,3 +1,5 @@ +import 'package:apidash/consts.dart'; +import 'package:apidash/screens/mobile/navbar.dart'; import 'package:flutter/material.dart'; class LeftDrawer extends StatelessWidget { @@ -6,6 +8,7 @@ class LeftDrawer extends StatelessWidget { @override Widget build(BuildContext context) { + final isLargeMobile = MediaQuery.of(context).size.width > kLargeMobileWidth; return Container( padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top), color: Theme.of(context).colorScheme.onInverseSurface, @@ -13,18 +16,26 @@ class LeftDrawer extends StatelessWidget { backgroundColor: Colors.transparent, surfaceTintColor: Colors.transparent, child: Padding( - padding: - EdgeInsets.only(right: MediaQuery.of(context).size.width * 0.03), + padding: const EdgeInsets.only(right: 16), child: Container( padding: EdgeInsets.only( - bottom: 70 + MediaQuery.of(context).padding.bottom), + bottom: isLargeMobile + ? MediaQuery.of(context).padding.bottom + : 70 + MediaQuery.of(context).padding.bottom), clipBehavior: Clip.hardEdge, decoration: BoxDecoration( color: Theme.of(context).colorScheme.surface, borderRadius: const BorderRadius.only(topRight: Radius.circular(8)), ), - child: drawerContent, + child: isLargeMobile + ? Row( + children: [ + const NavRail(), + Expanded(child: drawerContent), + ], + ) + : drawerContent, ), ), ), diff --git a/lib/screens/mobile/bottom_navbar.dart b/lib/screens/mobile/navbar.dart similarity index 68% rename from lib/screens/mobile/bottom_navbar.dart rename to lib/screens/mobile/navbar.dart index 28c85815..a3954465 100644 --- a/lib/screens/mobile/bottom_navbar.dart +++ b/lib/screens/mobile/navbar.dart @@ -81,6 +81,95 @@ class BottomNavBar extends ConsumerWidget { } } +class NavRail extends ConsumerWidget { + const NavRail({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final railIdx = ref.watch(navRailIndexStateProvider); + return Material( + type: MaterialType.transparency, + child: Container( + width: 70, + padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 8), + decoration: BoxDecoration( + border: Border( + right: BorderSide( + color: Theme.of(context).colorScheme.onInverseSurface, + width: 1, + ), + ), + ), + child: Column( + children: [ + customNavigationDestination( + context, + ref, + railIdx, + 0, + Icons.dashboard, + Icons.dashboard_outlined, + 'Requests', + ), + const SizedBox(height: 16), + customNavigationDestination( + context, + ref, + railIdx, + 1, + Icons.laptop_windows, + Icons.laptop_windows_outlined, + 'Variables', + ), + const Expanded(child: SizedBox()), + customNavigationDestination( + context, + ref, + railIdx, + 2, + Icons.help, + Icons.help_outline, + 'About', + isNavigator: true, + showLabel: false, + onTap: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => const PageBase( + title: 'About', + scaffoldBody: IntroPage(), + )), + ); + }, + ), + const SizedBox(height: 24), + customNavigationDestination( + context, + ref, + railIdx, + 3, + Icons.settings, + Icons.settings_outlined, + 'Settings', + isNavigator: true, + showLabel: false, + onTap: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => const PageBase( + title: 'Settings', + scaffoldBody: SettingsPage(), + )), + ); + }, + ), + ], + ), + ), + ); + } +} + Widget customNavigationDestination( BuildContext context, WidgetRef ref, diff --git a/lib/screens/settings_page.dart b/lib/screens/settings_page.dart index d14cdc6c..1fadcebd 100644 --- a/lib/screens/settings_page.dart +++ b/lib/screens/settings_page.dart @@ -13,10 +13,12 @@ class SettingsPage extends ConsumerWidget { final settings = ref.watch(settingsProvider); final clearingData = ref.watch(clearDataStateProvider); var sm = ScaffoldMessenger.of(context); + final isMobile = + kIsMobile && MediaQuery.of(context).size.width < kMinWindowSize.width; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - !kIsMobile + !isMobile ? Padding( padding: kPh20t40, child: kIsDesktop diff --git a/lib/widgets/dropdowns.dart b/lib/widgets/dropdowns.dart index a0c4903c..10c86a11 100644 --- a/lib/widgets/dropdowns.dart +++ b/lib/widgets/dropdowns.dart @@ -15,6 +15,8 @@ class DropdownButtonHttpMethod extends StatelessWidget { @override Widget build(BuildContext context) { final surfaceColor = Theme.of(context).colorScheme.surface; + final isMobile = + kIsMobile && MediaQuery.of(context).size.width < kMinWindowSize.width; return DropdownButton( focusColor: surfaceColor, value: method, @@ -29,11 +31,11 @@ class DropdownButtonHttpMethod extends StatelessWidget { return DropdownMenuItem( value: value, child: Padding( - padding: EdgeInsets.only(left: kIsMobile ? 8 : 16), + padding: EdgeInsets.only(left: isMobile ? 8 : 16), child: Text( value.name.toUpperCase(), style: kCodeStyle.copyWith( - fontSize: kIsMobile ? 13 : null, + fontSize: isMobile ? 13 : null, fontWeight: FontWeight.bold, color: getHTTPMethodColor( value, diff --git a/lib/widgets/intro_message.dart b/lib/widgets/intro_message.dart index 85db1542..e7c0e0df 100644 --- a/lib/widgets/intro_message.dart +++ b/lib/widgets/intro_message.dart @@ -23,6 +23,8 @@ class IntroMessage extends StatelessWidget { return FutureBuilder( future: introData(), builder: (BuildContext context, AsyncSnapshot snapshot) { + final isMobile = kIsMobile && + MediaQuery.of(context).size.width < kMinWindowSize.width; if (snapshot.hasError) { return const ErrorMessage(message: "An error occured"); } @@ -37,7 +39,7 @@ class IntroMessage extends StatelessWidget { return CustomMarkdown( data: text, - padding: !kIsMobile ? kPh60 : kPh20, + padding: !isMobile ? kPh60 : kPh20, ); } return const Center(child: CircularProgressIndicator()); diff --git a/lib/widgets/request_widgets.dart b/lib/widgets/request_widgets.dart index e84b4e32..28f7ff89 100644 --- a/lib/widgets/request_widgets.dart +++ b/lib/widgets/request_widgets.dart @@ -45,9 +45,11 @@ class _RequestPaneState extends State if (widget.tabIndex != null) { _controller.index = widget.tabIndex!; } + final isMobile = + kIsMobile && MediaQuery.of(context).size.width < kMinWindowSize.width; return Column( children: [ - kIsMobile + isMobile ? const SizedBox.shrink() : Padding( padding: kP8, diff --git a/lib/widgets/tabs.dart b/lib/widgets/tabs.dart index c2542a26..d96e05ea 100644 --- a/lib/widgets/tabs.dart +++ b/lib/widgets/tabs.dart @@ -12,8 +12,10 @@ class TabLabel extends StatelessWidget { @override Widget build(BuildContext context) { + final isMobile = + kIsMobile && MediaQuery.of(context).size.width < kMinWindowSize.width; return SizedBox( - height: kTabHeight, + height: isMobile ? kMobileTabHeight : kTabHeight, child: Stack( children: [ Center(