updated packages

This commit is contained in:
Vaibhav Surve 2025-12-10 14:54:27 +05:30
parent 03a3c1e06c
commit 1279b0e00f
14 changed files with 219 additions and 172 deletions

View File

@ -1,4 +1,4 @@
# On Field Work
# OnFieldWork.com
A new Flutter project.

View File

@ -39,7 +39,7 @@ android {
// Specify your unique Application ID. This identifies your app on Google Play.
applicationId = "com.marcoonfieldwork.aiot"
// Set minimum and target SDK versions based on Flutter's configuration
minSdk = 23
minSdkVersion = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion
// Set version code and name based on Flutter's configuration (from pubspec.yaml)
versionCode = flutter.versionCode

View File

@ -8,7 +8,7 @@
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<application
android:label="On Field Work"
android:label="OnFieldWork.com"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity

View File

@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-all.zip

View File

@ -18,7 +18,7 @@ pluginManagement {
plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version "8.6.0" apply false
id "com.android.application" version "8.9.1" apply false
id "org.jetbrains.kotlin.android" version "2.2.21" apply false
id("com.google.gms.google-services") version "4.4.2" apply false
}

View File

@ -14,7 +14,7 @@ YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# App info
APP_NAME="On Field Work"
APP_NAME="OnFieldWork.com"
BUILD_DIR="build/app/outputs"
echo -e "${CYAN}🚀 Starting Flutter build script for $APP_NAME...${NC}"

View File

@ -5,7 +5,7 @@
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>On Field Work</string>
<string>OnFieldWork.com</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>

View File

@ -8,5 +8,5 @@ class AppConstant {
static int iOSAppVersion = 1;
static String version = "1.0.0";
static String get appName => 'On Field Work';
static String get appName => 'OnFieldWork.com';
}

View File

@ -7,7 +7,6 @@ import 'package:on_field_work/helpers/services/storage/local_storage.dart';
import 'package:on_field_work/controller/permission_controller.dart';
class TenantSelectionController extends GetxController {
// Tenant list
final tenants = <Tenant>[].obs;
@ -35,6 +34,7 @@ class TenantSelectionController extends GetxController {
if (data == null || data.isEmpty) {
tenants.clear();
logSafe("⚠️ No tenants found for the user.", level: LogLevel.warning);
await LocalStorage.logout();
return;
}

View File

@ -124,7 +124,7 @@ class AuthService {
method: _HttpMethod.post,
body: body,
);
if (data != null && data['success'] == true && data['data'] != null) {
await LocalStorage.setJwtToken(data['data']['token']);
await LocalStorage.setRefreshToken(data['data']['refreshToken']);
@ -181,9 +181,10 @@ class AuthService {
_wrapErrorHandling(
() async {
final employeeInfo = await LocalStorage.getEmployeeInfo();
if (employeeInfo == null) return null; // Fails immediately if info is missing
if (employeeInfo == null)
return null; // Fails immediately if info is missing
final token = await LocalStorage.getJwtToken();
final responseData = await _networkRequest(
path: "/auth/login-mpin",
method: _HttpMethod.post,
@ -196,7 +197,7 @@ class AuthService {
authToken: token,
);
// Handle token updates from MPIN login success if necessary,
// Handle token updates from MPIN login success if necessary,
// though typically refresh or a separate login handles this.
if (responseData?['data'] != null) {
await _handleLoginSuccess(responseData!['data']);
@ -299,11 +300,10 @@ class AuthService {
final refreshed = await refreshToken();
if (refreshed) return getTenants(hasRetried: true);
}
// Fallback on all other failures
if (data != null && data['statusCode'] != 401) {
_handleApiError(
data['statusCode'], data, "Fetching tenants");
_handleApiError(data['statusCode'], data, "Fetching tenants");
} else if (data?['statusCode'] == 401 && hasRetried) {
await _handleUnauthorized();
}
@ -354,7 +354,7 @@ class AuthService {
if (refreshed) return selectTenant(tenantId, hasRetried: true);
await _handleUnauthorized();
}
// Fallback on all other failures
if (data != null) {
_handleApiError(data['statusCode'], data, "Selecting tenant");
@ -385,33 +385,37 @@ class AuthService {
authToken: token,
);
if (data != null && data['success'] == true && data['data'] is Map<String, dynamic>) {
final responseData = data['data'] as Map<String, dynamic>;
final result = {
'permissions': _parsePermissions(responseData['featurePermissions']),
'employeeInfo': await _parseEmployeeInfo(responseData['employeeInfo']),
'projects': _parseProjectsInfo(responseData['projects']),
};
if (data != null &&
data['success'] == true &&
data['data'] is Map<String, dynamic>) {
final responseData = data['data'] as Map<String, dynamic>;
_userDataCache[token] = result;
logSafe("User data fetched and decrypted successfully.");
return result;
final result = {
'permissions': _parsePermissions(responseData['featurePermissions']),
'employeeInfo': await _parseEmployeeInfo(responseData['employeeInfo']),
'projects': _parseProjectsInfo(responseData['projects']),
};
_userDataCache[token] = result;
logSafe("User data fetched and decrypted successfully.");
return result;
}
// Handle 401 Unauthorized via refreshToken/retry logic
if (data?['statusCode'] == 401 && !hasRetried) {
final refreshed = await refreshToken();
final newToken = await LocalStorage.getJwtToken();
if (refreshed && newToken != null) {
return fetchAllUserData(newToken, hasRetried: true);
}
final refreshed = await refreshToken();
final newToken = await LocalStorage.getJwtToken();
if (refreshed && newToken != null) {
return fetchAllUserData(newToken, hasRetried: true);
}
}
// Handle failure and unauthorized
if (data?['statusCode'] == 401 || data?['statusCode'] == 403 || data == null) {
await _handleUnauthorized();
throw Exception('Unauthorized or Network Error. Token refresh failed.');
if (data?['statusCode'] == 401 ||
data?['statusCode'] == 403 ||
data == null) {
await _handleUnauthorized();
throw Exception('Unauthorized or Network Error. Token refresh failed.');
}
final errorMsg = data['message'] ?? 'Unknown error';
@ -469,7 +473,6 @@ class AuthService {
logSafe("$context failed: $message [Status: $statusCode]", level: level);
}
/// General network request handler for both GET and POST.
static Future<Map<String, dynamic>?> _networkRequest({
required String path,
@ -490,8 +493,10 @@ class AuthService {
level: LogLevel.info);
if (method == _HttpMethod.post) {
response = await http.post(uri, headers: headers, body: jsonEncode(body));
} else { // GET
response =
await http.post(uri, headers: headers, body: jsonEncode(body));
} else {
// GET
response = await http.get(uri, headers: headers);
}
@ -501,26 +506,34 @@ class AuthService {
if (response.statusCode == 401) {
await _handleUnauthorized();
}
return {"statusCode": response.statusCode, "success": false, "message": "Empty response body"};
return {
"statusCode": response.statusCode,
"success": false,
"message": "Empty response body"
};
}
final decrypted = decryptResponse(response.body);
if (decrypted == null) {
logSafe("❌ Response decryption failed for $path", level: LogLevel.error);
return {"statusCode": response.statusCode, "success": false, "message": "Failed to decrypt response"};
logSafe("❌ Response decryption failed for $path",
level: LogLevel.error);
return {
"statusCode": response.statusCode,
"success": false,
"message": "Failed to decrypt response"
};
}
final Map<String, dynamic> result = decrypted is Map<String, dynamic>
? decrypted
final Map<String, dynamic> result = decrypted is Map<String, dynamic>
? decrypted
: {"data": decrypted}; // Wrap non-map responses
logSafe(
"⬅️ Response: ${jsonEncode(result)} [Status: ${response.statusCode}]",
level: LogLevel.info);
return {"statusCode": response.statusCode, ...result};
return {"statusCode": response.statusCode, ...result};
} catch (e, st) {
_handleError("$path ${method.name.toUpperCase()} error", e, st);
return null;
@ -561,4 +574,4 @@ class AuthService {
}
isLoggedIn = true;
}
}
}

View File

@ -25,7 +25,8 @@ int get flexColumns => MyScreenMedia.flexColumns;
class MaterialRadius {
double xs, small, medium, large;
MaterialRadius({this.xs = 2, this.small = 4, this.medium = 6, this.large = 8});
MaterialRadius(
{this.xs = 2, this.small = 4, this.medium = 6, this.large = 8});
}
class ColorGroup {
@ -41,10 +42,12 @@ class AppTheme {
static Color primaryColor = Color(0xff663399);
static ThemeData getThemeFromThemeMode() {
return ThemeCustomizer.instance.theme == ThemeMode.light ? lightTheme : darkTheme;
return ThemeCustomizer.instance.theme == ThemeMode.light
? lightTheme
: darkTheme;
}
/// -------------------------- Light Theme -------------------------------------------- ///
/// -------------------------- Light Theme  -------------------------------------------- ///
static final ThemeData lightTheme = ThemeData(
/// Brightness
@ -60,14 +63,18 @@ class AppTheme {
/// AppBar Theme
appBarTheme: AppBarTheme(
backgroundColor: Color(0xffF5F5F5), iconTheme: IconThemeData(color: Color(0xff495057)), actionsIconTheme: IconThemeData(color: Color(0xff495057))),
backgroundColor: Color(0xffF5F5F5),
iconTheme: IconThemeData(color: Color(0xff495057)),
actionsIconTheme: IconThemeData(color: Color(0xff495057))),
/// Card Theme
cardTheme: CardTheme(color: Color(0xffffffff)),
// FIX: Use CardThemeData
cardTheme: CardThemeData(color: Color(0xffffffff)),
cardColor: Color(0xffffffff),
/// Colorscheme
colorScheme: ColorScheme.fromSeed(seedColor: Color(0xff663399), brightness: Brightness.light),
colorScheme: ColorScheme.fromSeed(
seedColor: Color(0xff663399), brightness: Brightness.light),
snackBarTheme: SnackBarThemeData(actionTextColor: Colors.white),
@ -86,10 +93,12 @@ class AppTheme {
dividerColor: Color(0xffdddddd),
/// Bottom AppBar Theme
bottomAppBarTheme: BottomAppBarTheme(color: Color(0xffeeeeee), elevation: 2),
// FIX: Use BottomAppBarThemeData
bottomAppBarTheme:
BottomAppBarThemeData(color: Color(0xffeeeeee), elevation: 2),
/// Tab bar Theme
tabBarTheme: TabBarTheme(
tabBarTheme: TabBarThemeData(
unselectedLabelColor: Color(0xff495057),
labelColor: AppTheme.primaryColor,
indicatorSize: TabBarIndicatorSize.label,
@ -123,8 +132,11 @@ class AppTheme {
checkColor: WidgetStateProperty.all(Color(0xffffffff)),
fillColor: WidgetStateProperty.all(AppTheme.primaryColor),
),
switchTheme:
SwitchThemeData(thumbColor: WidgetStateProperty.resolveWith((states) => states.contains(WidgetState.selected) ? AppTheme.primaryColor : Colors.white)),
switchTheme: SwitchThemeData(
thumbColor: WidgetStateProperty.resolveWith((states) =>
states.contains(WidgetState.selected)
? AppTheme.primaryColor
: Colors.white)),
/// Other Colors
splashColor: Colors.white.withAlpha(100),
@ -132,8 +144,9 @@ class AppTheme {
highlightColor: Color(0xffeeeeee),
);
/// -------------------------- Dark Theme -------------------------------------------- ///
static final ThemeData darkTheme = ThemeData.dark(useMaterial3: false).copyWith(
/// -------------------------- Dark Theme  -------------------------------------------- ///
static final ThemeData darkTheme =
ThemeData.dark(useMaterial3: false).copyWith(
/// Brightness
/// Scaffold and Background color
@ -146,7 +159,8 @@ class AppTheme {
appBarTheme: AppBarTheme(backgroundColor: Color(0xff262729)),
/// Card Theme
cardTheme: CardTheme(color: Color(0xff1b1b1c)),
// FIX: Use CardThemeData
cardTheme: CardThemeData(color: Color(0xff1b1b1c)),
cardColor: Color(0xff1b1b1c),
/// Colorscheme
@ -175,10 +189,13 @@ class AppTheme {
foregroundColor: Colors.white),
/// Bottom AppBar Theme
bottomAppBarTheme: BottomAppBarTheme(color: Color(0xff464c52), elevation: 2),
// FIX: Use BottomAppBarThemeData
bottomAppBarTheme:
BottomAppBarThemeData(color: Color(0xff464c52), elevation: 2),
/// Tab bar Theme
tabBarTheme: TabBarTheme(
// FIX: Use TabBarThemeData
tabBarTheme: TabBarThemeData(
unselectedLabelColor: Color(0xff495057),
labelColor: AppTheme.primaryColor,
indicatorSize: TabBarIndicatorSize.label,
@ -230,7 +247,8 @@ class AppStyle {
containerRadius: AppStyle.containerRadius.medium,
cardRadius: AppStyle.cardRadius.medium,
buttonRadius: AppStyle.buttonRadius.medium,
defaultBreadCrumbItem: MyBreadcrumbItem(name: 'On Field Work', route: '/client/dashboard'),
defaultBreadCrumbItem:
MyBreadcrumbItem(name: 'OnFieldWork.com', route: '/client/dashboard'),
));
bool isMobile = true;
try {
@ -241,12 +259,16 @@ class AppStyle {
My.setFlexSpacing(isMobile ? 16 : 24);
}
/// -------------------------- Styles -------------------------------------------- ///
/// -------------------------- Styles  -------------------------------------------- ///
static MaterialRadius buttonRadius = MaterialRadius(small: 2, medium: 4, large: 8);
static MaterialRadius cardRadius = MaterialRadius(xs: 2, small: 4, medium: 4, large: 8);
static MaterialRadius containerRadius = MaterialRadius(xs: 2, small: 4, medium: 4, large: 8);
static MaterialRadius imageRadius = MaterialRadius(xs: 2, small: 4, medium: 4, large: 8);
static MaterialRadius buttonRadius =
MaterialRadius(small: 2, medium: 4, large: 8);
static MaterialRadius cardRadius =
MaterialRadius(xs: 2, small: 4, medium: 4, large: 8);
static MaterialRadius containerRadius =
MaterialRadius(xs: 2, small: 4, medium: 4, large: 8);
static MaterialRadius imageRadius =
MaterialRadius(xs: 2, small: 4, medium: 4, large: 8);
}
class AppColors {
@ -262,13 +284,16 @@ class AppColors {
static ColorGroup orange = ColorGroup(Color(0xffFFCEC2), Color(0xffFF3B0A));
static ColorGroup skyBlue = ColorGroup(Color(0xffC2F0FF), Color(0xff0099CC));
static ColorGroup lavender = ColorGroup(Color(0xffEAE2F3), Color(0xff7748AD));
static ColorGroup queenPink = ColorGroup(Color(0xffE8D9DC), Color(0xff804D57));
static ColorGroup blueViolet = ColorGroup(Color(0xffC5C6E7), Color(0xff3B3E91));
static ColorGroup queenPink =
ColorGroup(Color(0xffE8D9DC), Color(0xff804D57));
static ColorGroup blueViolet =
ColorGroup(Color(0xffC5C6E7), Color(0xff3B3E91));
static ColorGroup rosePink = ColorGroup(Color(0xffFCB1E0), Color(0xffEC0999));
static ColorGroup rubinRed = ColorGroup(Color(0x98f6a8bd), Color(0xffd03760));
static ColorGroup favorite = rubinRed;
static ColorGroup redOrange = ColorGroup(Color(0xffFFAD99), Color(0xffF53100));
static ColorGroup redOrange =
ColorGroup(Color(0xffFFAD99), Color(0xffF53100));
static Color notificationSuccessBGColor = Color(0xff117E68);
static Color notificationSuccessTextColor = Color(0xffffffff);
@ -278,7 +303,16 @@ class AppColors {
static Color notificationErrorTextColor = Color(0xffFF3B0A);
static Color notificationErrorActionColor = Color(0xff006784);
static List<ColorGroup> list = [redOrange, violet, blue, green, orange, skyBlue, lavender, blueViolet];
static List<ColorGroup> list = [
redOrange,
violet,
blue,
green,
orange,
skyBlue,
lavender,
blueViolet
];
static ColorGroup get random => list[Random().nextInt(list.length)];
@ -287,7 +321,13 @@ class AppColors {
}
static Color getColorByRating(int rating) {
var colors = {1: Color(0xfff0323c), 2: Color(0xcdf0323c), 3: star, 4: Color(0xcd3cd278), 5: Color(0xff3cd278)};
var colors = {
1: Color(0xfff0323c),
2: Color(0xcdf0323c),
3: star,
4: Color(0xcd3cd278),
5: Color(0xff3cd278)
};
return colors[rating] ?? colors[1]!;
}

View File

@ -11,8 +11,8 @@ import 'package:on_field_work/helpers/utils/permission_constants.dart';
import 'package:on_field_work/helpers/widgets/avatar.dart';
import 'package:on_field_work/helpers/widgets/dashbaord/expense_breakdown_chart.dart';
import 'package:on_field_work/helpers/widgets/dashbaord/expense_by_status_widget.dart';
import 'package:on_field_work/helpers/widgets/dashbaord/collection_dashboard_card.dart';
import 'package:on_field_work/helpers/widgets/dashbaord/purchase_invoice_dashboard.dart';
// import 'package:on_field_work/helpers/widgets/dashbaord/collection_dashboard_card.dart'; // Unused
// import 'package:on_field_work/helpers/widgets/dashbaord/purchase_invoice_dashboard.dart'; // Unused
import 'package:on_field_work/helpers/widgets/dashbaord/monthly_expense_dashboard_chart.dart';
import 'package:on_field_work/helpers/widgets/my_custom_skeleton.dart';
import 'package:on_field_work/helpers/widgets/my_spacing.dart';
@ -80,7 +80,6 @@ class _DashboardScreenState extends State<DashboardScreen> with UIMixin {
Widget _sectionTitle(String title) {
return Padding(
padding: const EdgeInsets.only(left: 4, bottom: 8),
// OPTIMIZATION: Use MyText for consistent styling
child: MyText.titleMedium(
title,
fontWeight: 700,
@ -127,12 +126,13 @@ class _DashboardScreenState extends State<DashboardScreen> with UIMixin {
children: [
Row(
children: [
Icon(Icons.info_outline, size: 30, color: Colors.white),
const Icon(Icons.info_outline,
size: 30, color: Colors.white),
MySpacing.width(10),
Expanded(
const Expanded(
child: Text(
"No attendance data available yet.",
style: const TextStyle(
style: TextStyle(
color: Colors.white,
fontSize: 13,
fontWeight: FontWeight.w500,
@ -142,9 +142,9 @@ class _DashboardScreenState extends State<DashboardScreen> with UIMixin {
],
),
MySpacing.height(12),
Text(
const Text(
"You are not added to this project or attendance data is not available.",
style: const TextStyle(color: Colors.white70, fontSize: 13),
style: TextStyle(color: Colors.white70, fontSize: 13),
),
],
),
@ -215,7 +215,7 @@ class _DashboardScreenState extends State<DashboardScreen> with UIMixin {
),
],
),
MySpacing.height(12), // OPTIMIZED
MySpacing.height(12),
Text(
infoText,
style: const TextStyle(
@ -223,7 +223,7 @@ class _DashboardScreenState extends State<DashboardScreen> with UIMixin {
fontSize: 13,
),
),
MySpacing.height(12), // OPTIMIZED
MySpacing.height(12),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
@ -262,7 +262,6 @@ class _DashboardScreenState extends State<DashboardScreen> with UIMixin {
final bool projectSelected = projectController.selectedProject != null;
// These are String constants from permission_constants.dart (kept outside of Obx)
const List<String> cardOrder = [
MenuItems.attendance,
MenuItems.employees,
@ -273,7 +272,6 @@ class _DashboardScreenState extends State<DashboardScreen> with UIMixin {
MenuItems.infraProjects,
];
// OPTIMIZATION: Using a static map for meta data
final Map<String, _DashboardCardMeta> meta = {
MenuItems.attendance:
_DashboardCardMeta(LucideIcons.scan_face, contentTheme.success),
@ -291,7 +289,6 @@ class _DashboardScreenState extends State<DashboardScreen> with UIMixin {
_DashboardCardMeta(LucideIcons.building_2, contentTheme.primary),
};
// OPTIMIZATION: Use map for faster lookup, then filter the preferred order
final Map<String, dynamic> allowed = {
for (final m in menuController.menuItems)
if (m.available && meta.containsKey(m.id)) m.id: m,
@ -308,8 +305,7 @@ class _DashboardScreenState extends State<DashboardScreen> with UIMixin {
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
_sectionTitle(
'Modules'), // OPTIMIZATION: Reused section title helper
_sectionTitle('Modules'),
if (!projectSelected)
Container(
padding: const EdgeInsets.symmetric(
@ -334,7 +330,7 @@ class _DashboardScreenState extends State<DashboardScreen> with UIMixin {
),
GridView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
physics: const NeverScrollableScrollPhysics(), // Important!
padding: const EdgeInsets.symmetric(vertical: 2, horizontal: 2),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
@ -394,7 +390,7 @@ class _DashboardScreenState extends State<DashboardScreen> with UIMixin {
color:
isEnabled ? cardMeta.color : Colors.grey.shade300,
),
MySpacing.height(6), // OPTIMIZED
MySpacing.height(6),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 2),
child: Text(
@ -521,7 +517,6 @@ class _DashboardScreenState extends State<DashboardScreen> with UIMixin {
child: Column(
children: [
const TextField(
// OPTIMIZED: Added const
decoration: InputDecoration(
hintText: 'Search project...',
isDense: true,
@ -557,8 +552,9 @@ class _DashboardScreenState extends State<DashboardScreen> with UIMixin {
),
);
}
// ---------------------------------------------------------------------------
// Build
// Build (MODIFIED FOR FIXED HEADER)
// ---------------------------------------------------------------------------
@override
@ -566,34 +562,39 @@ class _DashboardScreenState extends State<DashboardScreen> with UIMixin {
return Scaffold(
backgroundColor: const Color(0xfff5f6fa),
body: Layout(
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_projectSelector(),
MySpacing.height(20),
_quickActions(),
MySpacing.height(20),
_dashboardModules(),
MySpacing.height(20),
_sectionTitle('Reports & Analytics'),
const CompactPurchaseInvoiceDashboard(),
MySpacing.height(20),
CollectionsHealthWidget(),
MySpacing.height(20),
_cardWrapper(
child: ExpenseTypeReportChart(),
),
_cardWrapper(
child: ExpenseByStatusWidget(
controller: dashboardController,
Expanded(
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_quickActions(),
MySpacing.height(20),
_dashboardModules(),
MySpacing.height(20),
_sectionTitle('Reports & Analytics'),
_cardWrapper(
child: ExpenseTypeReportChart(),
),
_cardWrapper(
child: ExpenseByStatusWidget(
controller: dashboardController,
),
),
_cardWrapper(
child: MonthlyExpenseDashboardChart(),
),
MySpacing.height(20),
],
),
),
),
_cardWrapper(
child: MonthlyExpenseDashboardChart(),
),
MySpacing.height(20),
],
),
),

View File

@ -24,7 +24,6 @@ class Layout extends StatefulWidget {
class _LayoutState extends State<Layout> with UIMixin {
final LayoutController controller = LayoutController();
final EmployeeInfo? employeeInfo = LocalStorage.getEmployeeInfo();
final bool isBetaEnvironment = ApiEndpoints.baseUrl.contains("stage");
bool hasMpin = true;
@ -46,72 +45,69 @@ class _LayoutState extends State<Layout> with UIMixin {
@override
Widget build(BuildContext context) {
return MyResponsive(builder: (context, _, screenMT) {
return GetBuilder(
return GetBuilder<LayoutController>(
init: controller,
builder: (_) {
return (screenMT.isMobile || screenMT.isTablet)
? _buildScaffold(context, isMobile: true)
: _buildScaffold(context);
return _buildScaffold(context);
},
);
});
}
Widget _buildScaffold(BuildContext context, {bool isMobile = false}) {
Widget _buildScaffold(BuildContext context) {
final primaryColor = contentTheme.primary;
return Scaffold(
key: controller.scaffoldKey,
endDrawer: const UserProfileBar(),
floatingActionButton: widget.floatingActionButton,
body: Column(
children: [
// Solid primary background area
Container(
key: controller.scaffoldKey,
endDrawer: const UserProfileBar(),
floatingActionButton: widget.floatingActionButton,
body: Column(
children: [
Container(
width: double.infinity,
color: primaryColor,
child: _buildHeaderContent(),
),
Expanded(
child: Container(
width: double.infinity,
color: primaryColor,
child: _buildHeaderContent(isMobile),
),
Expanded(
child: Container(
width: double.infinity,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
primaryColor,
primaryColor.withOpacity(0.7),
primaryColor.withOpacity(0.0),
],
stops: const [0.0, 0.1, 0.3],
),
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
primaryColor,
primaryColor.withOpacity(0.7),
primaryColor.withOpacity(0.0),
],
stops: const [0.0, 0.1, 0.3],
),
child: SafeArea(
top: false,
child: GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () {},
child: SingleChildScrollView(
key: controller.scrollKey,
padding: EdgeInsets.zero,
child: widget.child,
),
),
),
child: SafeArea(
top: false,
child: GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () =>
FocusScope.of(context).unfocus(),
child: widget.child ??
const SizedBox.shrink(),
),
),
),
],
));
),
],
),
);
}
Widget _buildHeaderContent(bool isMobile) {
Widget _buildHeaderContent() {
final selectedTenant = AuthService.currentTenant;
final bool isBeta = ApiEndpoints.baseUrl.contains("stage");
return Padding(
padding: const EdgeInsets.fromLTRB(10, 45, 10, 0),
child: Container(
margin: const EdgeInsets.only(bottom: 18),
margin: const EdgeInsets.only(bottom: 10),
width: double.infinity,
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
@ -139,7 +135,7 @@ class _LayoutState extends State<Layout> with UIMixin {
),
// Beta badge
if (ApiEndpoints.baseUrl.contains("stage"))
if (isBeta)
Positioned(
bottom: 0,
left: 0,

View File

@ -46,15 +46,15 @@ dependencies:
carousel_slider: ^5.0.0
reorderable_grid: ^1.0.10
loading_animation_widget: ^1.3.0
intl: ^0.19.0
syncfusion_flutter_core: ^29.1.40
syncfusion_flutter_sliders: ^29.1.40
intl: ^0.20.2
syncfusion_flutter_core: ^31.2.18
syncfusion_flutter_sliders: ^31.2.18
file_picker: ^10.3.2
timelines_plus: ^1.0.4
syncfusion_flutter_charts: ^29.1.40
syncfusion_flutter_charts: ^31.2.18
appflowy_board: ^0.1.2
syncfusion_flutter_calendar: ^29.1.40
syncfusion_flutter_maps: ^29.1.40
syncfusion_flutter_calendar: ^31.2.18
syncfusion_flutter_maps: ^31.2.18
http: ^1.6.0
geolocator: ^14.0.2
permission_handler: ^12.0.1
@ -71,13 +71,13 @@ dependencies:
font_awesome_flutter: ^10.8.0
flutter_html: ^3.0.0
tab_indicator_styler: ^2.0.0
connectivity_plus: ^6.1.4
connectivity_plus: ^7.0.0
geocoding: ^4.0.0
firebase_core: ^4.0.0
firebase_messaging: ^16.0.0
googleapis_auth: ^2.0.0
device_info_plus: ^11.3.0
flutter_local_notifications: 19.4.0
device_info_plus: ^12.3.0
flutter_local_notifications: ^19.5.0
equatable: ^2.0.7
mime: ^2.0.0
timeago: ^3.7.1
@ -97,7 +97,7 @@ dev_dependencies:
# activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint
# rules and activating additional ones.
flutter_lints: ^5.0.0
flutter_lints: ^6.0.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
@ -150,6 +150,3 @@ flutter:
#
# For details regarding fonts from package dependencies,
# see https://flutter.dev/to/font-from-package
dependency_overrides:
http: ^1.6.0