iPhone Duo changes

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

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

@ -39,11 +39,16 @@ bool isTablet = false;
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);
await SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
DeviceOrientation.landscapeLeft,
DeviceOrientation.landscapeRight,
]);
await EasyLocalization.ensureInitialized();
AppState().setPostParamsInitConfig();
HttpOverrides.global = MyHttpOverrides();
isTablet = MediaQueryData.fromWindow(WidgetsBinding.instance.window).size.shortestSide >= ApiConsts.tabletMinLength;
isTablet = MediaQueryData.fromView(WidgetsBinding.instance.window).size.shortestSide >= ApiConsts.tabletMinLength;
runApp(
EasyLocalization(
@ -89,6 +94,8 @@ class MyApp extends StatelessWidget {
return SafeArea(
top: false, // Set to true if you want to avoid the notch area as well
bottom: Platform.isIOS ? false : true,
left: false,
right: false,
child: MaterialApp(
navigatorKey: AppRoutes.navigatorKey,
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/widgets/bottom_sheet.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/offers_shimmer_widget.dart';
@ -257,9 +258,12 @@ class _DashboardScreenState extends State<DashboardScreen> with WidgetsBindingOb
@override
Widget build(BuildContext context) {
isDisplayMazaya = checkIfPrivilegedForMazaya();
bool isUnfolded = DuoResponsive.isUnfolded(context);
return SafeArea(
bottom: Platform.isAndroid ? true : false,
top: false,
left: false,
right: false,
child: Scaffold(
key: _scaffoldState,
body: Column(
@ -321,7 +325,7 @@ class _DashboardScreenState extends State<DashboardScreen> with WidgetsBindingOb
children: [
Expanded(
child: AspectRatio(
aspectRatio: 159 / 159,
aspectRatio: isUnfolded ? 1.55 : 159 / 159,
child: Consumer<DashboardProviderModel>(
builder: (BuildContext context, DashboardProviderModel model, Widget? child) {
return (model.isAttendanceTrackingLoading
@ -465,7 +469,7 @@ class _DashboardScreenState extends State<DashboardScreen> with WidgetsBindingOb
child: ClipRRect(
borderRadius: BorderRadius.circular(20),
child: AspectRatio(
aspectRatio: 386 / 121.37,
aspectRatio: isUnfolded ? 3.5 : 386 / 121.37,
child: SvgPicture.asset(
AppState().isArabic(context) ? "assets/images/mazaya_banner_ar.svg" : "assets/images/mazaya_banner_en.svg",
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/employee_digital_id_dialog.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';
class AppDrawer extends StatefulWidget {
@ -44,11 +45,7 @@ class _AppDrawerState extends State<AppDrawer> {
try {
GenericResponseModel? response = await ProfileApiClient().getEmployeeBusinessCardInfo();
Utils.hideLoading(context);
showMDialog(
context,
child: BusinessCardDialog(qrBase64: response?.businessCardQROutside, employeeInfo: response?.empBusinessCardInfoModel),
isBusniessCard: true,
);
showMDialog(context, child: BusinessCardDialog(qrBase64: response?.businessCardQROutside, employeeInfo: response?.empBusinessCardInfoModel), isBusniessCard: true);
} catch (ex) {
Utils.hideLoading(context);
Utils.handleException(ex, context, null);
@ -57,42 +54,31 @@ class _AppDrawerState extends State<AppDrawer> {
@override
Widget build(BuildContext context) {
double screenWidth = MediaQuery.sizeOf(context).width;
double drawerWidth = (screenWidth * 303 / 375).clamp(260.0, DuoResponsive.maxDrawerWidth);
if (drawerMenuItemList.isEmpty) {
drawerMenuItemList = Provider.of<DashboardProviderModel>(context, listen: false).drawerMenuItemList;
}
return Drawer(
width: MediaQuery.of(context).size.width * 303 / 375,
width: drawerWidth,
child: Column(
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Image.asset("assets/images/logos/main_mohemm_logo.png", width: 134, height: 24),
const Icon(Icons.clear).onPress(() => Navigator.pop(context)),
],
children: [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),
Row(
children: [
AppState().memberInformationList!.eMPLOYEEIMAGE == null
? SvgPicture.asset(
"assets/images/user.svg",
height: 52,
width: 52,
)
: CircleAvatar(
radius: 52 / 2,
backgroundImage: MemoryImage(Utils.dataFromBase64String(AppState().memberInformationList!.eMPLOYEEIMAGE!)),
backgroundColor: Colors.black,
),
? SvgPicture.asset("assets/images/user.svg", height: 52, width: 52)
: CircleAvatar(radius: 52 / 2, backgroundImage: MemoryImage(Utils.dataFromBase64String(AppState().memberInformationList!.eMPLOYEEIMAGE!)), backgroundColor: Colors.black),
12.width,
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AppState().memberInformationList!.eMPLOYEENAME!.toText18(isBold: true),
AppState().memberInformationList!.getPositionName().toText14(weight: FontWeight.w500),
],
).expanded
children: [AppState().memberInformationList!.eMPLOYEENAME!.toText18(isBold: true), AppState().memberInformationList!.getPositionName().toText14(weight: FontWeight.w500)],
).expanded,
],
).paddingOnly(left: 14, right: 14, top: 21, bottom: 21),
// Row(
@ -117,67 +103,71 @@ class _AppDrawerState extends State<AppDrawer> {
// ),
// ],
// ).paddingOnly(left: 14, right: 14, bottom: 14),
const Divider(
height: 1,
thickness: 1,
color: MyColors.lightGreyEFColor,
),
const Divider(height: 1, thickness: 1, color: MyColors.lightGreyEFColor),
ListView(
padding: const EdgeInsets.only(top: 21, bottom: 21),
children: [
ListView.builder(
padding: EdgeInsets.zero,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: drawerMenuItemList.length,
itemBuilder: (cxt, index) {
return menuItem(drawerMenuItemList[index].icon, drawerMenuItemList[index].title, drawerMenuItemList[index].routeName, onPress: () {
padding: EdgeInsets.zero,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: drawerMenuItemList.length,
itemBuilder: (cxt, index) {
return menuItem(
drawerMenuItemList[index].icon,
drawerMenuItemList[index].title,
drawerMenuItemList[index].routeName,
onPress: () {
Navigator.pushNamed(context, drawerMenuItemList[index].routeName);
});
}),
},
);
},
),
menuItem("assets/images/drawer/employee_id.svg", LocaleKeys.employeeDigitalID.tr(), "", closeDrawer: false, onPress: () => showMDialog(context, child: EmployeeDigitialIdDialog())),
if (AppState().businessCardPrivilege)
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,);
],
).expanded,
const Divider(
height: 1,
thickness: 1,
color: MyColors.lightGreyEFColor,
),
const Divider(height: 1, thickness: 1, color: MyColors.lightGreyEFColor),
Row(
children: [
RichText(
text: TextSpan(text: LocaleKeys.poweredBy.tr() + " ", style: const TextStyle(color: MyColors.grey98Color, 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),
),
]),
text: TextSpan(
text: LocaleKeys.poweredBy.tr() + " ",
style: const TextStyle(color: MyColors.grey98Color, 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,
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),
);
}
Widget menuItem(String icon, String title, String routeName, {Color? color, bool closeDrawer = true, VoidCallback? onPress}) {
return Row(
children: [
SvgPicture.asset(icon, height: 20, width: 20),
9.width,
title.toText14(color: color, textAlign: AppState().isArabic(context) ? TextAlign.right : null).expanded,
],
).paddingOnly(left: 21, top: 10, bottom: 10, right: 21).onPress(closeDrawer
? () async {
Navigator.pop(context);
Future.delayed(const Duration(microseconds: 200), onPress);
}
: onPress!);
return Row(children: [SvgPicture.asset(icon, height: 20, width: 20), 9.width, title.toText14(color: color, textAlign: AppState().isArabic(context) ? TextAlign.right : null).expanded])
.paddingOnly(left: 21, top: 10, bottom: 10, right: 21)
.onPress(
closeDrawer
? () async {
Navigator.pop(context);
Future.delayed(const Duration(microseconds: 200), onPress);
}
: onPress!,
);
}
void postLanguageChange(BuildContext context) {
@ -189,13 +179,13 @@ class _AppDrawerState extends State<AppDrawer> {
setState(() {});
}
// void performLogout() async {
// // AppState().isAuthenticated = false;
// // AppState().isLogged = false;
// // AppState().setPostParamsInitConfig();
// // chatData.disposeData();
// // // SharedPreferences prefs = await SharedPreferences.getInstance();
// // // await prefs.clear();
// // Navigator.pushNamedAndRemoveUntil(context, AppRoutes.login, (Route<dynamic> route) => false, arguments: null);
// }
// void performLogout() async {
// // AppState().isAuthenticated = false;
// // AppState().isLogged = false;
// // AppState().setPostParamsInitConfig();
// // chatData.disposeData();
// // // SharedPreferences prefs = await SharedPreferences.getInstance();
// // // await prefs.clear();
// // 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/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/widgets/responsive/duo_responsive.dart';
import 'package:mohem_flutter_app/widgets/shimmer/dashboard_shimmer_widget.dart';
import 'package:provider/provider.dart';
@ -20,11 +21,12 @@ class MenusWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
List<int> namesColor = [0xff125765, 0xff239D8F, 0xff2BB8A8, 0xff1D92AA];
bool isUnfolded = DuoResponsive.isUnfolded(context);
return Consumer<DashboardProviderModel>(
builder: (cxt, data, child) {
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,
shrinkWrap: true,
primary: false,

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

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