iPhone Duo changes

master
Haroon Amjad 5 hours ago
parent 150f4bf3c7
commit 6066a7a3c2

@ -109,7 +109,7 @@ class ApiConsts {
//DummyCards for the UI //DummyCards for the UI
static int tabletMinLength = 500; static int tabletMinLength = 600;
// Notification Center GUIDs for OTP // Notification Center GUIDs for OTP
static String notificationCenterAppGUID = static String notificationCenterAppGUID =

@ -39,11 +39,16 @@ bool isTablet = false;
Future<void> main() async { Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]); await SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
DeviceOrientation.landscapeLeft,
DeviceOrientation.landscapeRight,
]);
await EasyLocalization.ensureInitialized(); await EasyLocalization.ensureInitialized();
AppState().setPostParamsInitConfig(); AppState().setPostParamsInitConfig();
HttpOverrides.global = MyHttpOverrides(); HttpOverrides.global = MyHttpOverrides();
isTablet = MediaQueryData.fromWindow(WidgetsBinding.instance.window).size.shortestSide >= ApiConsts.tabletMinLength; isTablet = MediaQueryData.fromView(WidgetsBinding.instance.window).size.shortestSide >= ApiConsts.tabletMinLength;
runApp( runApp(
EasyLocalization( EasyLocalization(
@ -89,6 +94,8 @@ class MyApp extends StatelessWidget {
return SafeArea( return SafeArea(
top: false, // Set to true if you want to avoid the notch area as well top: false, // Set to true if you want to avoid the notch area as well
bottom: Platform.isIOS ? false : true, bottom: Platform.isIOS ? false : true,
left: false,
right: false,
child: MaterialApp( child: MaterialApp(
navigatorKey: AppRoutes.navigatorKey, navigatorKey: AppRoutes.navigatorKey,
builder: (BuildContext context, Widget? child) { builder: (BuildContext context, Widget? child) {

@ -28,6 +28,7 @@ import 'package:mohem_flutter_app/ui/marathon/marathon_provider.dart';
import 'package:mohem_flutter_app/ui/marathon/widgets/marathon_banner.dart'; import 'package:mohem_flutter_app/ui/marathon/widgets/marathon_banner.dart';
import 'package:mohem_flutter_app/widgets/bottom_sheet.dart'; import 'package:mohem_flutter_app/widgets/bottom_sheet.dart';
import 'package:mohem_flutter_app/widgets/mark_attendance_widget.dart'; import 'package:mohem_flutter_app/widgets/mark_attendance_widget.dart';
import 'package:mohem_flutter_app/widgets/responsive/duo_responsive.dart';
import 'package:mohem_flutter_app/widgets/shimmer/dashboard_shimmer_widget.dart'; import 'package:mohem_flutter_app/widgets/shimmer/dashboard_shimmer_widget.dart';
import 'package:mohem_flutter_app/widgets/shimmer/offers_shimmer_widget.dart'; import 'package:mohem_flutter_app/widgets/shimmer/offers_shimmer_widget.dart';
@ -257,9 +258,12 @@ class _DashboardScreenState extends State<DashboardScreen> with WidgetsBindingOb
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
isDisplayMazaya = checkIfPrivilegedForMazaya(); isDisplayMazaya = checkIfPrivilegedForMazaya();
bool isUnfolded = DuoResponsive.isUnfolded(context);
return SafeArea( return SafeArea(
bottom: Platform.isAndroid ? true : false, bottom: Platform.isAndroid ? true : false,
top: false, top: false,
left: false,
right: false,
child: Scaffold( child: Scaffold(
key: _scaffoldState, key: _scaffoldState,
body: Column( body: Column(
@ -321,7 +325,7 @@ class _DashboardScreenState extends State<DashboardScreen> with WidgetsBindingOb
children: [ children: [
Expanded( Expanded(
child: AspectRatio( child: AspectRatio(
aspectRatio: 159 / 159, aspectRatio: isUnfolded ? 1.55 : 159 / 159,
child: Consumer<DashboardProviderModel>( child: Consumer<DashboardProviderModel>(
builder: (BuildContext context, DashboardProviderModel model, Widget? child) { builder: (BuildContext context, DashboardProviderModel model, Widget? child) {
return (model.isAttendanceTrackingLoading return (model.isAttendanceTrackingLoading
@ -465,7 +469,7 @@ class _DashboardScreenState extends State<DashboardScreen> with WidgetsBindingOb
child: ClipRRect( child: ClipRRect(
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
child: AspectRatio( child: AspectRatio(
aspectRatio: 386 / 121.37, aspectRatio: isUnfolded ? 3.5 : 386 / 121.37,
child: SvgPicture.asset( child: SvgPicture.asset(
AppState().isArabic(context) ? "assets/images/mazaya_banner_ar.svg" : "assets/images/mazaya_banner_en.svg", AppState().isArabic(context) ? "assets/images/mazaya_banner_ar.svg" : "assets/images/mazaya_banner_en.svg",
width: double.infinity, width: double.infinity,

@ -18,6 +18,7 @@ import 'package:mohem_flutter_app/provider/dashboard_provider_model.dart';
import 'package:mohem_flutter_app/ui/dialogs/id/business_card_dialog.dart'; import 'package:mohem_flutter_app/ui/dialogs/id/business_card_dialog.dart';
import 'package:mohem_flutter_app/ui/dialogs/id/employee_digital_id_dialog.dart'; import 'package:mohem_flutter_app/ui/dialogs/id/employee_digital_id_dialog.dart';
import 'package:mohem_flutter_app/widgets/dialogs/dialogs.dart'; import 'package:mohem_flutter_app/widgets/dialogs/dialogs.dart';
import 'package:mohem_flutter_app/widgets/responsive/duo_responsive.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
class AppDrawer extends StatefulWidget { class AppDrawer extends StatefulWidget {
@ -44,11 +45,7 @@ class _AppDrawerState extends State<AppDrawer> {
try { try {
GenericResponseModel? response = await ProfileApiClient().getEmployeeBusinessCardInfo(); GenericResponseModel? response = await ProfileApiClient().getEmployeeBusinessCardInfo();
Utils.hideLoading(context); Utils.hideLoading(context);
showMDialog( showMDialog(context, child: BusinessCardDialog(qrBase64: response?.businessCardQROutside, employeeInfo: response?.empBusinessCardInfoModel), isBusniessCard: true);
context,
child: BusinessCardDialog(qrBase64: response?.businessCardQROutside, employeeInfo: response?.empBusinessCardInfoModel),
isBusniessCard: true,
);
} catch (ex) { } catch (ex) {
Utils.hideLoading(context); Utils.hideLoading(context);
Utils.handleException(ex, context, null); Utils.handleException(ex, context, null);
@ -57,42 +54,31 @@ class _AppDrawerState extends State<AppDrawer> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
double screenWidth = MediaQuery.sizeOf(context).width;
double drawerWidth = (screenWidth * 303 / 375).clamp(260.0, DuoResponsive.maxDrawerWidth);
if (drawerMenuItemList.isEmpty) { if (drawerMenuItemList.isEmpty) {
drawerMenuItemList = Provider.of<DashboardProviderModel>(context, listen: false).drawerMenuItemList; drawerMenuItemList = Provider.of<DashboardProviderModel>(context, listen: false).drawerMenuItemList;
} }
return Drawer( return Drawer(
width: MediaQuery.of(context).size.width * 303 / 375, width: drawerWidth,
child: Column( child: Column(
children: <Widget>[ children: <Widget>[
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [Image.asset("assets/images/logos/main_mohemm_logo.png", width: 134, height: 24), const Icon(Icons.clear).onPress(() => Navigator.pop(context))],
Image.asset("assets/images/logos/main_mohemm_logo.png", width: 134, height: 24),
const Icon(Icons.clear).onPress(() => Navigator.pop(context)),
],
).paddingOnly(left: 4, right: 14), ).paddingOnly(left: 4, right: 14),
Row( Row(
children: [ children: [
AppState().memberInformationList!.eMPLOYEEIMAGE == null AppState().memberInformationList!.eMPLOYEEIMAGE == null
? SvgPicture.asset( ? SvgPicture.asset("assets/images/user.svg", height: 52, width: 52)
"assets/images/user.svg", : CircleAvatar(radius: 52 / 2, backgroundImage: MemoryImage(Utils.dataFromBase64String(AppState().memberInformationList!.eMPLOYEEIMAGE!)), backgroundColor: Colors.black),
height: 52,
width: 52,
)
: CircleAvatar(
radius: 52 / 2,
backgroundImage: MemoryImage(Utils.dataFromBase64String(AppState().memberInformationList!.eMPLOYEEIMAGE!)),
backgroundColor: Colors.black,
),
12.width, 12.width,
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [AppState().memberInformationList!.eMPLOYEENAME!.toText18(isBold: true), AppState().memberInformationList!.getPositionName().toText14(weight: FontWeight.w500)],
AppState().memberInformationList!.eMPLOYEENAME!.toText18(isBold: true), ).expanded,
AppState().memberInformationList!.getPositionName().toText14(weight: FontWeight.w500),
],
).expanded
], ],
).paddingOnly(left: 14, right: 14, top: 21, bottom: 21), ).paddingOnly(left: 14, right: 14, top: 21, bottom: 21),
// Row( // Row(
@ -117,67 +103,71 @@ class _AppDrawerState extends State<AppDrawer> {
// ), // ),
// ], // ],
// ).paddingOnly(left: 14, right: 14, bottom: 14), // ).paddingOnly(left: 14, right: 14, bottom: 14),
const Divider( const Divider(height: 1, thickness: 1, color: MyColors.lightGreyEFColor),
height: 1,
thickness: 1,
color: MyColors.lightGreyEFColor,
),
ListView( ListView(
padding: const EdgeInsets.only(top: 21, bottom: 21), padding: const EdgeInsets.only(top: 21, bottom: 21),
children: [ children: [
ListView.builder( ListView.builder(
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
shrinkWrap: true, shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
itemCount: drawerMenuItemList.length, itemCount: drawerMenuItemList.length,
itemBuilder: (cxt, index) { itemBuilder: (cxt, index) {
return menuItem(drawerMenuItemList[index].icon, drawerMenuItemList[index].title, drawerMenuItemList[index].routeName, onPress: () { return menuItem(
drawerMenuItemList[index].icon,
drawerMenuItemList[index].title,
drawerMenuItemList[index].routeName,
onPress: () {
Navigator.pushNamed(context, drawerMenuItemList[index].routeName); Navigator.pushNamed(context, drawerMenuItemList[index].routeName);
}); },
}), );
},
),
menuItem("assets/images/drawer/employee_id.svg", LocaleKeys.employeeDigitalID.tr(), "", closeDrawer: false, onPress: () => showMDialog(context, child: EmployeeDigitialIdDialog())), menuItem("assets/images/drawer/employee_id.svg", LocaleKeys.employeeDigitalID.tr(), "", closeDrawer: false, onPress: () => showMDialog(context, child: EmployeeDigitialIdDialog())),
if (AppState().businessCardPrivilege) if (AppState().businessCardPrivilege)
menuItem("assets/images/drawer/view_business_card.svg", LocaleKeys.viewBusinessCard.tr(), "", closeDrawer: false, onPress: () => _showBusinessCard(context)), menuItem("assets/images/drawer/view_business_card.svg", LocaleKeys.viewBusinessCard.tr(), "", closeDrawer: false, onPress: () => _showBusinessCard(context)),
menuItem("assets/images/drawer/logout.svg", LocaleKeys.logout.tr(), "", color: MyColors.redA3Color, closeDrawer: false, onPress: (){Utils.performLogout(context, chatData);}), menuItem(
"assets/images/drawer/logout.svg",
LocaleKeys.logout.tr(),
"",
color: MyColors.redA3Color,
closeDrawer: false,
onPress: () {
Utils.performLogout(context, chatData);
},
),
// menuItem("assets/images/drawer/logout.svg", LocaleKeys.logout.tr(), "", color: MyColors.redA3Color, closeDrawer: false, onPress: () {Navigator.pushNamed(context, AppRoutes.survey,); // menuItem("assets/images/drawer/logout.svg", LocaleKeys.logout.tr(), "", color: MyColors.redA3Color, closeDrawer: false, onPress: () {Navigator.pushNamed(context, AppRoutes.survey,);
], ],
).expanded, ).expanded,
const Divider( const Divider(height: 1, thickness: 1, color: MyColors.lightGreyEFColor),
height: 1,
thickness: 1,
color: MyColors.lightGreyEFColor,
),
Row( Row(
children: [ children: [
RichText( RichText(
text: TextSpan(text: LocaleKeys.poweredBy.tr() + " ", style: const TextStyle(color: MyColors.grey98Color, fontSize: 14, letterSpacing: -0.56, fontWeight: FontWeight.w600), children: [ text: TextSpan(
TextSpan( text: LocaleKeys.poweredBy.tr() + " ",
text: LocaleKeys.cloudSolutions.tr(), style: const TextStyle(color: MyColors.grey98Color, fontSize: 14, letterSpacing: -0.56, fontWeight: FontWeight.w600),
style: const TextStyle(color: MyColors.grey3AColor, fontSize: 14, letterSpacing: -0.56, fontWeight: FontWeight.w600), children: [TextSpan(text: LocaleKeys.cloudSolutions.tr(), style: const TextStyle(color: MyColors.grey3AColor, fontSize: 14, letterSpacing: -0.56, fontWeight: FontWeight.w600))],
), ),
]),
).expanded, ).expanded,
Image.asset("assets/images/logos/bn_cloud_soloution.jpg", width: 40, height: 40) Image.asset("assets/images/logos/bn_cloud_soloution.jpg", width: 40, height: 40),
], ],
).paddingOnly(left: 21, right: 21, top: 21) ).paddingOnly(left: 21, right: 21, top: 21),
], ],
).paddingOnly(top: 21, bottom: 21), ).paddingOnly(top: 21, bottom: 21),
); );
} }
Widget menuItem(String icon, String title, String routeName, {Color? color, bool closeDrawer = true, VoidCallback? onPress}) { Widget menuItem(String icon, String title, String routeName, {Color? color, bool closeDrawer = true, VoidCallback? onPress}) {
return Row( return Row(children: [SvgPicture.asset(icon, height: 20, width: 20), 9.width, title.toText14(color: color, textAlign: AppState().isArabic(context) ? TextAlign.right : null).expanded])
children: [ .paddingOnly(left: 21, top: 10, bottom: 10, right: 21)
SvgPicture.asset(icon, height: 20, width: 20), .onPress(
9.width, closeDrawer
title.toText14(color: color, textAlign: AppState().isArabic(context) ? TextAlign.right : null).expanded, ? () async {
], Navigator.pop(context);
).paddingOnly(left: 21, top: 10, bottom: 10, right: 21).onPress(closeDrawer Future.delayed(const Duration(microseconds: 200), onPress);
? () async { }
Navigator.pop(context); : onPress!,
Future.delayed(const Duration(microseconds: 200), onPress); );
}
: onPress!);
} }
void postLanguageChange(BuildContext context) { void postLanguageChange(BuildContext context) {
@ -189,13 +179,13 @@ class _AppDrawerState extends State<AppDrawer> {
setState(() {}); setState(() {});
} }
// void performLogout() async { // void performLogout() async {
// // AppState().isAuthenticated = false; // // AppState().isAuthenticated = false;
// // AppState().isLogged = false; // // AppState().isLogged = false;
// // AppState().setPostParamsInitConfig(); // // AppState().setPostParamsInitConfig();
// // chatData.disposeData(); // // chatData.disposeData();
// // // SharedPreferences prefs = await SharedPreferences.getInstance(); // // // SharedPreferences prefs = await SharedPreferences.getInstance();
// // // await prefs.clear(); // // // await prefs.clear();
// // Navigator.pushNamedAndRemoveUntil(context, AppRoutes.login, (Route<dynamic> route) => false, arguments: null); // // Navigator.pushNamedAndRemoveUntil(context, AppRoutes.login, (Route<dynamic> route) => false, arguments: null);
// } // }
} }

@ -13,6 +13,7 @@ import 'package:mohem_flutter_app/models/sso_auth_model.dart';
import 'package:mohem_flutter_app/provider/dashboard_provider_model.dart'; import 'package:mohem_flutter_app/provider/dashboard_provider_model.dart';
import 'package:mohem_flutter_app/ui/my_attendance/dynamic_screens/dynamic_listview_screen.dart'; import 'package:mohem_flutter_app/ui/my_attendance/dynamic_screens/dynamic_listview_screen.dart';
import 'package:mohem_flutter_app/ui/screens/ticket/ticket_detailed_screen.dart'; import 'package:mohem_flutter_app/ui/screens/ticket/ticket_detailed_screen.dart';
import 'package:mohem_flutter_app/widgets/responsive/duo_responsive.dart';
import 'package:mohem_flutter_app/widgets/shimmer/dashboard_shimmer_widget.dart'; import 'package:mohem_flutter_app/widgets/shimmer/dashboard_shimmer_widget.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@ -20,11 +21,12 @@ class MenusWidget extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
List<int> namesColor = [0xff125765, 0xff239D8F, 0xff2BB8A8, 0xff1D92AA]; List<int> namesColor = [0xff125765, 0xff239D8F, 0xff2BB8A8, 0xff1D92AA];
bool isUnfolded = DuoResponsive.isUnfolded(context);
return Consumer<DashboardProviderModel>( return Consumer<DashboardProviderModel>(
builder: (cxt, data, child) { builder: (cxt, data, child) {
return GridView( return GridView(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2, childAspectRatio: 2 / 2, crossAxisSpacing: 9, mainAxisSpacing: 9), gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2, childAspectRatio: isUnfolded ? 1.55 : 2 / 2, crossAxisSpacing: 9, mainAxisSpacing: 9),
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
shrinkWrap: true, shrinkWrap: true,
primary: false, primary: false,

@ -1010,6 +1010,8 @@ class _RetailersListScreenState extends State<RetailersListScreen> {
], ],
), ),
body: SafeArea( body: SafeArea(
left: false,
right: false,
child: RefreshIndicator( child: RefreshIndicator(
onRefresh: _onRefresh, onRefresh: _onRefresh,
color: MyColors.textMixColor, color: MyColors.textMixColor,

@ -32,9 +32,7 @@ class WalaPlusRetailerGrid extends StatelessWidget {
bool get _hasAnyChips { bool get _hasAnyChips {
for (WalaPlusRetailerModel r in retailers) { for (WalaPlusRetailerModel r in retailers) {
if (r.normalizedTypes.isNotEmpty || if (r.normalizedTypes.isNotEmpty || r.displayCity.trim().isNotEmpty || r.displayUsage.trim().isNotEmpty) {
r.displayCity.trim().isNotEmpty ||
r.displayUsage.trim().isNotEmpty) {
return true; return true;
} }
} }
@ -47,50 +45,53 @@ class WalaPlusRetailerGrid extends StatelessWidget {
return 235; return 235;
} }
SliverGridDelegateWithFixedCrossAxisCount get _gridDelegate => SliverGridDelegateWithFixedCrossAxisCount _gridDelegateForWidth(double width) {
SliverGridDelegateWithFixedCrossAxisCount( int columns = width >= 680 ? 4 : (width >= 480 ? 3 : 2);
crossAxisCount: isTablet ? 4 : 2, return SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: columns, mainAxisExtent: _effectiveMainAxisExtent, crossAxisSpacing: 12, mainAxisSpacing: 16);
mainAxisExtent: _effectiveMainAxisExtent, }
crossAxisSpacing: 12,
mainAxisSpacing: 16,
);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (isLoading) { return LayoutBuilder(
return Shimmer.fromColors( builder: (BuildContext context, BoxConstraints constraints) {
baseColor: const Color(0xFFE2E8F0), SliverGridDelegateWithFixedCrossAxisCount gridDelegate = _gridDelegateForWidth(constraints.maxWidth);
highlightColor: const Color(0xFFF8FAFC),
child: GridView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: _gridDelegate,
itemCount: shimmerItemCount,
itemBuilder: (BuildContext _, int __) => _buildShimmerCard(),
),
);
}
if (retailers.isEmpty) { if (isLoading) {
return Padding(padding: const EdgeInsets.only(top: 40), child: Center(child: LocaleKeys.noOffersAvailable.tr().toText14(color: const Color(0xFF64748B)))); return Shimmer.fromColors(
} baseColor: const Color(0xFFE2E8F0),
highlightColor: const Color(0xFFF8FAFC),
child: GridView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: gridDelegate,
itemCount: shimmerItemCount,
itemBuilder: (BuildContext _, int __) => _buildShimmerCard(),
),
);
}
return Column( if (retailers.isEmpty) {
children: <Widget>[ return Padding(padding: const EdgeInsets.only(top: 40), child: Center(child: LocaleKeys.noOffersAvailable.tr().toText14(color: const Color(0xFF64748B))));
GridView.builder( }
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(), return Column(
gridDelegate: _gridDelegate, children: <Widget>[
itemCount: retailers.length, GridView.builder(
itemBuilder: (BuildContext context, int index) { shrinkWrap: true,
return WalaPlusRetailerCard(item: retailers[index], isAr: isAr, onTap: () => onTapRetailer(retailers[index]), onFavoriteTap: () => onFavoriteTap(retailers[index])); physics: const NeverScrollableScrollPhysics(),
}, gridDelegate: gridDelegate,
), itemCount: retailers.length,
if (isLoadingMore) ...<Widget>[ itemBuilder: (BuildContext context, int index) {
16.height, return WalaPlusRetailerCard(item: retailers[index], isAr: isAr, onTap: () => onTapRetailer(retailers[index]), onFavoriteTap: () => onFavoriteTap(retailers[index]));
Center(child: Padding(padding: const EdgeInsets.symmetric(vertical: 12), child: Image.asset("assets/images/logos/loading_mohemm_logo.gif", height: 48.0, width: 48.0))), },
], ),
], if (isLoadingMore) ...<Widget>[
16.height,
Center(child: Padding(padding: const EdgeInsets.symmetric(vertical: 12), child: Image.asset("assets/images/logos/loading_mohemm_logo.gif", height: 48.0, width: 48.0))),
],
],
);
},
); );
} }

@ -0,0 +1,133 @@
import 'package:flutter/material.dart';
/// Responsive & hinge-aware layout utilities for iPhone Duo (folded, unfolded, and Split View postures).
class DuoResponsive {
DuoResponsive._();
/// Viewport width threshold (in logical points) where the layout transitions
/// from folded / narrow Split View to the unfolded inner display posture.
static const double unfoldedBreakpoint = 600.0;
/// Reserved center fold clearance (~40pt) for unfolded dual-pane postures
/// since MediaQuery.displayFeatures does not natively report the iOS fold yet.
static const double foldGap = 40.0;
/// Maximum width for dialogs and modal bottom sheets so they do not stretch
/// awkwardly across the 40pt center fold when unfolded.
static const double maxModalWidth = 420.0;
/// Standard capped width for navigation drawers on wide/unfolded screens.
static const double maxDrawerWidth = 320.0;
/// Width of the adaptive side navigation rail when unfolded.
static const double sideRailWidth = 88.0;
/// Returns true when the current window width is in the unfolded / expanded posture.
static bool isUnfolded(BuildContext context) {
return MediaQuery.sizeOf(context).width >= unfoldedBreakpoint;
}
/// Returns true when the given constraint or viewport width is in the unfolded posture.
static bool isWideWidth(double width) {
return width >= unfoldedBreakpoint;
}
/// Calculates the width of a single pane when split across the center fold gap.
static double singlePaneWidth(double availableWidth, {double horizontalPadding = 0.0}) {
double usable = availableWidth - (horizontalPadding * 2);
if (!isWideWidth(availableWidth)) {
return usable;
}
return ((usable - foldGap) / 2).clamp(0.0, double.infinity);
}
/// Returns adaptive dialog insetPadding so dialogs stay within a single pane
/// or centered with a comfortable width instead of stretching across the hinge.
static EdgeInsets dialogInsetPadding(BuildContext context, {double defaultHorizontal = 21.0, double defaultVertical = 24.0}) {
Size size = MediaQuery.sizeOf(context);
if (size.width >= unfoldedBreakpoint) {
double horizontal = ((size.width - maxModalWidth) / 2).clamp(defaultHorizontal, size.width * 0.35);
return EdgeInsets.symmetric(horizontal: horizontal, vertical: defaultVertical);
}
return EdgeInsets.symmetric(horizontal: defaultHorizontal, vertical: defaultVertical);
}
/// Returns BoxConstraints for modal bottom sheets so they never stretch across the fold.
static BoxConstraints bottomSheetConstraints(BuildContext context, {double? maxHeight}) {
Size size = MediaQuery.sizeOf(context);
bool unfolded = size.width >= unfoldedBreakpoint;
return BoxConstraints(
maxWidth: unfolded ? maxModalWidth : size.width,
maxHeight: maxHeight ?? double.infinity,
);
}
}
/// A two-pane row that automatically inserts [DuoResponsive.foldGap] (~40pt)
/// between [startPane] and [endPane] when in the unfolded posture, or [compactSpacing]
/// when in the folded / compact posture.
class DuoSplitRow extends StatelessWidget {
final Widget startPane;
final Widget endPane;
final double compactSpacing;
final CrossAxisAlignment crossAxisAlignment;
const DuoSplitRow({
Key? key,
required this.startPane,
required this.endPane,
this.compactSpacing = 9.0,
this.crossAxisAlignment = CrossAxisAlignment.start,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
bool isWide = DuoResponsive.isWideWidth(constraints.maxWidth) || DuoResponsive.isUnfolded(context);
double gap = isWide ? DuoResponsive.foldGap : compactSpacing;
return Row(
crossAxisAlignment: crossAxisAlignment,
children: <Widget>[
Expanded(child: startPane),
SizedBox(width: gap),
Expanded(child: endPane),
],
);
},
);
}
}
/// Constrains wide content (such as forms, cards, or lists) on unfolded screens
/// while letting it fill the width naturally when folded.
class DuoContentConstraint extends StatelessWidget {
final Widget child;
final double maxWidth;
final AlignmentGeometry alignment;
const DuoContentConstraint({
Key? key,
required this.child,
this.maxWidth = 720.0,
this.alignment = Alignment.topCenter,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
if (constraints.maxWidth <= maxWidth) {
return child;
}
return Align(
alignment: alignment,
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: maxWidth),
child: child,
),
);
},
);
}
}
Loading…
Cancel
Save