enhacement of UI for mobile screen responsiveness

This commit is contained in:
Manish 2025-11-25 12:17:45 +05:30
parent 3e8bd1c41d
commit 18fbfaa42d
8 changed files with 513 additions and 484 deletions

View File

@ -95,7 +95,7 @@ class _SearchAndFilterState extends State<SearchAndFilter> with UIMixin {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Padding( return Padding(
padding: MySpacing.fromLTRB(12, 10, 12, 0), padding: MySpacing.fromLTRB(12, 10, 12, 8),
child: Row( child: Row(
children: [ children: [
Expanded( Expanded(
@ -179,13 +179,6 @@ class ToggleButtonsRow extends StatelessWidget {
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFFF0F0F0), color: const Color(0xFFF0F0F0),
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 4,
offset: const Offset(0, 2),
),
],
), ),
child: Row( child: Row(
children: [ children: [
@ -286,8 +279,10 @@ class ExpenseList extends StatelessWidget {
return Center(child: MyText.bodyMedium('No expenses found.')); return Center(child: MyText.bodyMedium('No expenses found.'));
} }
return ListView.separated( return SafeArea(
padding: const EdgeInsets.fromLTRB(12, 12, 12, 80), bottom: true,
child: ListView.separated(
padding: const EdgeInsets.fromLTRB(12, 12, 12, 100),
itemCount: expenseList.length, itemCount: expenseList.length,
separatorBuilder: (_, __) => separatorBuilder: (_, __) =>
Divider(color: Colors.grey.shade300, height: 20), Divider(color: Colors.grey.shade300, height: 20),
@ -303,9 +298,7 @@ class ExpenseList extends StatelessWidget {
child: InkWell( child: InkWell(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
onTap: () async { onTap: () async {
await Get.to( await Get.to(() => ExpenseDetailScreen(expenseId: expense.id));
() => ExpenseDetailScreen(expenseId: expense.id),
);
}, },
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8), padding: const EdgeInsets.symmetric(vertical: 8),
@ -321,7 +314,8 @@ class ExpenseList extends StatelessWidget {
children: [ children: [
MyText.bodyMedium('${expense.formattedAmount}', MyText.bodyMedium('${expense.formattedAmount}',
fontWeight: 600), fontWeight: 600),
if (expense.status.name.toLowerCase() == 'draft') ...[ if (expense.status.name.toLowerCase() ==
'draft') ...[
const SizedBox(width: 8), const SizedBox(width: 8),
GestureDetector( GestureDetector(
onTap: () => onTap: () =>
@ -362,6 +356,7 @@ class ExpenseList extends StatelessWidget {
), ),
); );
}, },
),
); );
} }
} }

View File

@ -354,38 +354,27 @@ class _AssignTaskBottomSheetState extends State<AssignTaskBottomSheet> {
final result = await showModalBottomSheet<List<EmployeeModel>>( final result = await showModalBottomSheet<List<EmployeeModel>>(
context: context, context: context,
isScrollControlled: true, isScrollControlled: true,
backgroundColor: Colors.white,
barrierColor: Colors.white,
useSafeArea: true,
shape: const RoundedRectangleBorder( shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(20)), borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
), ),
builder: (context) { builder: (_) => SizedBox(
return DraggableScrollableSheet( height: MediaQuery.of(context).size.height * 0.90,
expand: false,
initialChildSize: 0.85,
minChildSize: 0.6,
maxChildSize: 1.0,
builder: (_, scrollController) {
return Container(
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
),
child: MultipleSelectRoleBottomSheet( child: MultipleSelectRoleBottomSheet(
projectId: selectedProjectId!, projectId: selectedProjectId!,
organizationId: selectedOrganization?.id, organizationId: selectedOrganization?.id,
serviceId: selectedService?.id, serviceId: selectedService?.id,
roleId: selectedRoleId, roleId: selectedRoleId,
initiallySelected: controller.selectedEmployees.toList(), initiallySelected: controller.selectedEmployees.toList(),
scrollController: scrollController, scrollController: ScrollController(),
),
), ),
);
},
);
},
); );
if (result != null) { if (result != null) {
controller.selectedEmployees controller.selectedEmployees.assignAll(result);
.assignAll(result); // RxList updates UI automatically
} }
} }

View File

@ -38,10 +38,18 @@ class _DirectoryMainScreenState extends State<DirectoryMainScreen>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return OrientationBuilder(
builder: (context, orientation) {
final bool isLandscape = orientation == Orientation.landscape;
return Scaffold( return Scaffold(
backgroundColor: const Color(0xFFF5F5F5), backgroundColor: const Color(0xFFF5F5F5),
appBar: PreferredSize( appBar: PreferredSize(
preferredSize: const Size.fromHeight(72), preferredSize: Size.fromHeight(
isLandscape ? 55 : 72, // Responsive height
),
child: SafeArea(
bottom: false,
child: AppBar( child: AppBar(
backgroundColor: const Color(0xFFF5F5F5), backgroundColor: const Color(0xFFF5F5F5),
elevation: 0.5, elevation: 0.5,
@ -50,7 +58,6 @@ class _DirectoryMainScreenState extends State<DirectoryMainScreen>
title: Padding( title: Padding(
padding: MySpacing.xy(16, 0), padding: MySpacing.xy(16, 0),
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
IconButton( IconButton(
icon: const Icon(Icons.arrow_back_ios_new, icon: const Icon(Icons.arrow_back_ios_new,
@ -58,7 +65,9 @@ class _DirectoryMainScreenState extends State<DirectoryMainScreen>
onPressed: () => Get.offNamed('/dashboard'), onPressed: () => Get.offNamed('/dashboard'),
), ),
MySpacing.width(8), MySpacing.width(8),
Expanded(
/// FIX: Flexible to prevent overflow in landscape
Flexible(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@ -74,6 +83,7 @@ class _DirectoryMainScreenState extends State<DirectoryMainScreen>
final projectName = final projectName =
projectController.selectedProject?.name ?? projectController.selectedProject?.name ??
'Select Project'; 'Select Project';
return Row( return Row(
children: [ children: [
const Icon(Icons.work_outline, const Icon(Icons.work_outline,
@ -99,9 +109,13 @@ class _DirectoryMainScreenState extends State<DirectoryMainScreen>
), ),
), ),
), ),
body: Column( ),
/// MAIN CONTENT
body: SafeArea(
bottom: true,
child: Column(
children: [ children: [
// ---------------- TabBar ----------------
Container( Container(
color: Colors.white, color: Colors.white,
child: TabBar( child: TabBar(
@ -115,8 +129,6 @@ class _DirectoryMainScreenState extends State<DirectoryMainScreen>
], ],
), ),
), ),
// ---------------- TabBarView ----------------
Expanded( Expanded(
child: TabBarView( child: TabBarView(
controller: _tabController, controller: _tabController,
@ -128,6 +140,9 @@ class _DirectoryMainScreenState extends State<DirectoryMainScreen>
), ),
], ],
), ),
),
);
},
); );
} }
} }

View File

@ -49,12 +49,14 @@ class _AdvancePaymentScreenState extends State<AdvancePaymentScreen>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
backgroundColor: const Color( backgroundColor: const Color(0xFFF5F5F5),
0xFFF5F5F5),
appBar: _buildAppBar(), appBar: _buildAppBar(),
body: GestureDetector(
// SafeArea added so nothing hides under system navigation buttons
body: SafeArea(
bottom: true,
child: GestureDetector(
onTap: () => FocusScope.of(context).unfocus(), onTap: () => FocusScope.of(context).unfocus(),
child: RefreshIndicator( child: RefreshIndicator(
onRefresh: () async { onRefresh: () async {
@ -69,9 +71,12 @@ class _AdvancePaymentScreenState extends State<AdvancePaymentScreen>
displacement: 60, displacement: 60,
child: SingleChildScrollView( child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(), physics: const AlwaysScrollableScrollPhysics(),
child: Container( child: Padding(
color: // Extra bottom padding so content does NOT go under 3-button navbar
const Color(0xFFF5F5F5), padding: EdgeInsets.only(
bottom: MediaQuery.of(context).padding.bottom + 20,
),
child: Column( child: Column(
children: [ children: [
_buildSearchBar(), _buildSearchBar(),
@ -84,6 +89,7 @@ class _AdvancePaymentScreenState extends State<AdvancePaymentScreen>
), ),
), ),
), ),
),
); );
} }
@ -322,7 +328,6 @@ class _AdvancePaymentScreenState extends State<AdvancePaymentScreen>
); );
} }
// No employee selected yet
if (controller.selectedEmployee.value == null) { if (controller.selectedEmployee.value == null) {
return const Padding( return const Padding(
padding: EdgeInsets.only(top: 100), padding: EdgeInsets.only(top: 100),
@ -330,7 +335,6 @@ class _AdvancePaymentScreenState extends State<AdvancePaymentScreen>
); );
} }
// Employee selected but no payments found
if (controller.payments.isEmpty) { if (controller.payments.isEmpty) {
return const Padding( return const Padding(
padding: EdgeInsets.only(top: 100), padding: EdgeInsets.only(top: 100),
@ -340,7 +344,6 @@ class _AdvancePaymentScreenState extends State<AdvancePaymentScreen>
); );
} }
// Payments available
return ListView.builder( return ListView.builder(
shrinkWrap: true, shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
@ -378,7 +381,7 @@ class _AdvancePaymentScreenState extends State<AdvancePaymentScreen>
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.grey[100], color: Colors.grey[100],
border: Border( border: Border(
bottom: BorderSide(color: Color(0xFFE0E0E0), width: 0.9), bottom: BorderSide(color: const Color(0xFFE0E0E0), width: 0.9),
), ),
), ),
child: Row( child: Row(

View File

@ -113,14 +113,18 @@ class _FinanceScreenState extends State<FinanceScreen>
), ),
), ),
), ),
body: FadeTransition( body: SafeArea(
top: false, // keep appbar area same
bottom: true, // avoid system bottom buttons
child: FadeTransition(
opacity: _fadeAnimation, opacity: _fadeAnimation,
child: Obx(() { child: Obx(() {
if (menuController.isLoading.value) { if (menuController.isLoading.value) {
return const Center(child: CircularProgressIndicator()); return const Center(child: CircularProgressIndicator());
} }
if (menuController.hasError.value || menuController.menuItems.isEmpty) { if (menuController.hasError.value ||
menuController.menuItems.isEmpty) {
return const Center( return const Center(
child: Text( child: Text(
"Failed to load menus. Please try again later.", "Failed to load menus. Please try again later.",
@ -129,7 +133,6 @@ class _FinanceScreenState extends State<FinanceScreen>
); );
} }
// Filter allowed Finance menus dynamically
final financeMenuIds = [ final financeMenuIds = [
MenuItems.expenseReimbursement, MenuItems.expenseReimbursement,
MenuItems.paymentRequests, MenuItems.paymentRequests,
@ -149,8 +152,18 @@ class _FinanceScreenState extends State<FinanceScreen>
); );
} }
// ---- IMPORTANT FIX: Add bottom safe padding ----
final double bottomInset =
MediaQuery.of(context).viewPadding.bottom;
return SingleChildScrollView( return SingleChildScrollView(
padding: const EdgeInsets.all(16), padding: EdgeInsets.fromLTRB(
16,
16,
16,
bottomInset +
24, // ensures charts never go under system buttons
),
child: Column( child: Column(
children: [ children: [
_buildFinanceModulesCompact(financeMenus), _buildFinanceModulesCompact(financeMenus),
@ -165,16 +178,20 @@ class _FinanceScreenState extends State<FinanceScreen>
); );
}), }),
), ),
),
); );
} }
// --- Finance Modules (Compact Dashboard-style) --- // --- Finance Modules (Compact Dashboard-style) ---
Widget _buildFinanceModulesCompact(List<MenuItem> financeMenus) { Widget _buildFinanceModulesCompact(List<MenuItem> financeMenus) {
// Map menu IDs to icon + color // Map menu IDs to icon + color
final Map<String, _FinanceCardMeta> financeCardMeta = { final Map<String, _FinanceCardMeta> financeCardMeta = {
MenuItems.expenseReimbursement: _FinanceCardMeta(LucideIcons.badge_dollar_sign, contentTheme.info), MenuItems.expenseReimbursement:
MenuItems.paymentRequests: _FinanceCardMeta(LucideIcons.receipt_text, contentTheme.primary), _FinanceCardMeta(LucideIcons.badge_dollar_sign, contentTheme.info),
MenuItems.advancePaymentStatements: _FinanceCardMeta(LucideIcons.wallet, contentTheme.warning), MenuItems.paymentRequests:
_FinanceCardMeta(LucideIcons.receipt_text, contentTheme.primary),
MenuItems.advancePaymentStatements:
_FinanceCardMeta(LucideIcons.wallet, contentTheme.warning),
}; };
// Build the stat items using API-provided mobileLink // Build the stat items using API-provided mobileLink
@ -198,20 +215,22 @@ Widget _buildFinanceModulesCompact(List<MenuItem> financeMenus) {
return LayoutBuilder(builder: (context, constraints) { return LayoutBuilder(builder: (context, constraints) {
// Determine number of columns dynamically // Determine number of columns dynamically
int crossAxisCount = (constraints.maxWidth ~/ 80).clamp(2, 4); int crossAxisCount = (constraints.maxWidth ~/ 80).clamp(2, 4);
double cardWidth = (constraints.maxWidth - (crossAxisCount - 1) * 6) / crossAxisCount; double cardWidth =
(constraints.maxWidth - (crossAxisCount - 1) * 6) / crossAxisCount;
return Wrap( return Wrap(
spacing: 6, spacing: 6,
runSpacing: 6, runSpacing: 6,
alignment: WrapAlignment.end, alignment: WrapAlignment.end,
children: stats children: stats
.map((stat) => _buildFinanceModuleCard(stat, projectSelected, cardWidth)) .map((stat) =>
_buildFinanceModuleCard(stat, projectSelected, cardWidth))
.toList(), .toList(),
); );
}); });
} }
Widget _buildFinanceModuleCard( Widget _buildFinanceModuleCard(
_FinanceStatItem stat, bool isProjectSelected, double width) { _FinanceStatItem stat, bool isProjectSelected, double width) {
return Opacity( return Opacity(
opacity: isProjectSelected ? 1.0 : 0.4, // Dim if no project selected opacity: isProjectSelected ? 1.0 : 0.4, // Dim if no project selected
@ -260,9 +279,9 @@ Widget _buildFinanceModuleCard(
), ),
), ),
); );
} }
void _onCardTap(_FinanceStatItem statItem, bool isEnabled) { void _onCardTap(_FinanceStatItem statItem, bool isEnabled) {
if (!isEnabled) { if (!isEnabled) {
Get.defaultDialog( Get.defaultDialog(
title: "No Project Selected", title: "No Project Selected",
@ -276,8 +295,8 @@ void _onCardTap(_FinanceStatItem statItem, bool isEnabled) {
// Navigate to the card's specific route // Navigate to the card's specific route
Get.toNamed(statItem.route); Get.toNamed(statItem.route);
} }
}
} }
}
class _FinanceStatItem { class _FinanceStatItem {
final IconData icon; final IconData icon;

View File

@ -99,7 +99,13 @@ class _PaymentRequestMainScreenState extends State<PaymentRequestMainScreen>
return Scaffold( return Scaffold(
backgroundColor: Colors.white, backgroundColor: Colors.white,
appBar: _buildAppBar(), appBar: _buildAppBar(),
body: Column(
// ------------------------
// FIX: SafeArea prevents content from going under 3-button navbar
// ------------------------
body: SafeArea(
bottom: true,
child: Column(
children: [ children: [
Container( Container(
color: Colors.white, color: Colors.white,
@ -135,6 +141,8 @@ class _PaymentRequestMainScreenState extends State<PaymentRequestMainScreen>
), ),
], ],
), ),
),
floatingActionButton: Obx(() { floatingActionButton: Obx(() {
if (permissionController.permissions.isEmpty) { if (permissionController.permissions.isEmpty) {
return const SizedBox.shrink(); return const SizedBox.shrink();
@ -294,7 +302,6 @@ class _PaymentRequestMainScreenState extends State<PaymentRequestMainScreen>
final list = filteredList(isHistory: isHistory); final list = filteredList(isHistory: isHistory);
// ScrollController for infinite scroll
final scrollController = ScrollController(); final scrollController = ScrollController();
scrollController.addListener(() { scrollController.addListener(() {
if (scrollController.position.pixels >= if (scrollController.position.pixels >=
@ -309,6 +316,7 @@ class _PaymentRequestMainScreenState extends State<PaymentRequestMainScreen>
child: list.isEmpty child: list.isEmpty
? ListView( ? ListView(
physics: const AlwaysScrollableScrollPhysics(), physics: const AlwaysScrollableScrollPhysics(),
padding: const EdgeInsets.only(bottom: 100),
children: [ children: [
SizedBox( SizedBox(
height: MediaQuery.of(context).size.height * 0.5, height: MediaQuery.of(context).size.height * 0.5,
@ -325,7 +333,12 @@ class _PaymentRequestMainScreenState extends State<PaymentRequestMainScreen>
) )
: ListView.separated( : ListView.separated(
controller: scrollController, controller: scrollController,
padding: const EdgeInsets.fromLTRB(12, 12, 12, 80),
// ------------------------
// FIX: ensure bottom list items stay visible above nav bar
// ------------------------
padding: const EdgeInsets.fromLTRB(12, 12, 12, 120),
itemCount: list.length + 1, itemCount: list.length + 1,
separatorBuilder: (_, __) => separatorBuilder: (_, __) =>
Divider(color: Colors.grey.shade300, height: 20), Divider(color: Colors.grey.shade300, height: 20),
@ -365,10 +378,6 @@ class _PaymentRequestMainScreenState extends State<PaymentRequestMainScreen>
Row( Row(
children: [ children: [
MyText.bodyMedium(item.expenseCategory.name, fontWeight: 600), MyText.bodyMedium(item.expenseCategory.name, fontWeight: 600),
// -------------------------------
// ADV CHIP (only if advance)
// -------------------------------
if (item.isAdvancePayment == true) ...[ if (item.isAdvancePayment == true) ...[
const SizedBox(width: 8), const SizedBox(width: 8),
Container( Container(

View File

@ -51,6 +51,7 @@ class _JobDetailsScreenState extends State<JobDetailsScreen> with UIMixin {
controller.fetchJobDetail(widget.jobId).then((_) { controller.fetchJobDetail(widget.jobId).then((_) {
final job = controller.jobDetail.value?.data; final job = controller.jobDetail.value?.data;
if (job != null) { if (job != null) {
_selectedTags.value = job.tags ?? [];
_titleController.text = job.title ?? ''; _titleController.text = job.title ?? '';
_descriptionController.text = job.description ?? ''; _descriptionController.text = job.description ?? '';
_startDateController.text = DateTimeUtils.convertUtcToLocal( _startDateController.text = DateTimeUtils.convertUtcToLocal(
@ -169,6 +170,11 @@ class _JobDetailsScreenState extends State<JobDetailsScreen> with UIMixin {
message: "Job updated successfully", message: "Job updated successfully",
type: SnackbarType.success); type: SnackbarType.success);
await controller.fetchJobDetail(widget.jobId); await controller.fetchJobDetail(widget.jobId);
final updatedJob = controller.jobDetail.value?.data;
if (updatedJob != null) {
_selectedTags.value = updatedJob.tags ?? [];
}
isEditing.value = false; isEditing.value = false;
} else { } else {
showAppSnackbar( showAppSnackbar(

View File

@ -22,11 +22,11 @@ class _ServiceProjectScreenState extends State<ServiceProjectScreen>
final TextEditingController searchController = TextEditingController(); final TextEditingController searchController = TextEditingController();
final ServiceProjectController controller = final ServiceProjectController controller =
Get.put(ServiceProjectController()); Get.put(ServiceProjectController());
@override @override
void initState() { void initState() {
super.initState(); super.initState();
// Fetch projects safely after first frame
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
controller.fetchProjects(); controller.fetchProjects();
}); });
@ -49,7 +49,6 @@ class _ServiceProjectScreenState extends State<ServiceProjectScreen>
child: InkWell( child: InkWell(
borderRadius: BorderRadius.circular(14), borderRadius: BorderRadius.circular(14),
onTap: () { onTap: () {
// Navigate to ServiceProjectDetailsScreen
Get.to(() => ServiceProjectDetailsScreen( Get.to(() => ServiceProjectDetailsScreen(
projectId: project.id, projectId: project.id,
projectName: project.name, projectName: project.name,
@ -60,7 +59,6 @@ class _ServiceProjectScreenState extends State<ServiceProjectScreen>
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
/// Project Header
Row( Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -92,20 +90,14 @@ class _ServiceProjectScreenState extends State<ServiceProjectScreen>
), ),
], ],
), ),
MySpacing.height(10), MySpacing.height(10),
/// Assigned Date
_buildDetailRow( _buildDetailRow(
Icons.date_range_outlined, Icons.date_range_outlined,
Colors.teal, Colors.teal,
"Assigned: ${DateTimeUtils.convertUtcToLocal(project.assignedDate.toIso8601String(), format: DateTimeUtils.defaultFormat)}", "Assigned: ${DateTimeUtils.convertUtcToLocal(project.assignedDate.toIso8601String(), format: DateTimeUtils.defaultFormat)}",
fontSize: 13, fontSize: 13,
), ),
MySpacing.height(8), MySpacing.height(8),
/// Client Info
if (project.client != null) if (project.client != null)
_buildDetailRow( _buildDetailRow(
Icons.account_circle_outlined, Icons.account_circle_outlined,
@ -113,20 +105,14 @@ class _ServiceProjectScreenState extends State<ServiceProjectScreen>
"Client: ${project.client!.name} (${project.client!.contactPerson})", "Client: ${project.client!.name} (${project.client!.contactPerson})",
fontSize: 13, fontSize: 13,
), ),
MySpacing.height(8), MySpacing.height(8),
/// Contact Info
_buildDetailRow( _buildDetailRow(
Icons.phone, Icons.phone,
Colors.green, Colors.green,
"Contact: ${project.contactName} (${project.contactPhone})", "Contact: ${project.contactName} (${project.contactPhone})",
fontSize: 13, fontSize: 13,
), ),
MySpacing.height(12), MySpacing.height(12),
/// Services List
if (project.services.isNotEmpty) if (project.services.isNotEmpty)
Wrap( Wrap(
spacing: 6, spacing: 6,
@ -197,14 +183,18 @@ class _ServiceProjectScreenState extends State<ServiceProjectScreen>
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
backgroundColor: const Color(0xFFF5F5F5), backgroundColor: const Color(0xFFF5F5F5),
appBar: CustomAppBar( appBar: CustomAppBar(
title: "Service Projects", title: "Service Projects",
projectName: 'All Service Projects', projectName: 'All Service Projects',
onBackPressed: () => Get.toNamed('/dashboard'), onBackPressed: () => Get.toNamed('/dashboard'),
), ),
body: Column(
// FIX 1: Entire body wrapped in SafeArea
body: SafeArea(
bottom: true,
child: Column(
children: [ children: [
/// Search bar and actions
Padding( Padding(
padding: MySpacing.xy(8, 8), padding: MySpacing.xy(8, 8),
child: Row( child: Row(
@ -253,8 +243,6 @@ class _ServiceProjectScreenState extends State<ServiceProjectScreen>
], ],
), ),
), ),
/// Project List
Expanded( Expanded(
child: Obx(() { child: Obx(() {
if (controller.isLoading.value) { if (controller.isLoading.value) {
@ -262,6 +250,7 @@ class _ServiceProjectScreenState extends State<ServiceProjectScreen>
} }
final projects = controller.filteredProjects; final projects = controller.filteredProjects;
return MyRefreshIndicator( return MyRefreshIndicator(
onRefresh: _refreshProjects, onRefresh: _refreshProjects,
backgroundColor: Colors.indigo, backgroundColor: Colors.indigo,
@ -270,8 +259,11 @@ class _ServiceProjectScreenState extends State<ServiceProjectScreen>
? _buildEmptyState() ? _buildEmptyState()
: ListView.separated( : ListView.separated(
physics: const AlwaysScrollableScrollPhysics(), physics: const AlwaysScrollableScrollPhysics(),
// FIX 2: Increased bottom padding for landscape
padding: MySpacing.only( padding: MySpacing.only(
left: 8, right: 8, top: 4, bottom: 80), left: 8, right: 8, top: 4, bottom: 120),
itemCount: projects.length, itemCount: projects.length,
separatorBuilder: (_, __) => MySpacing.height(12), separatorBuilder: (_, __) => MySpacing.height(12),
itemBuilder: (_, index) => itemBuilder: (_, index) =>
@ -282,6 +274,7 @@ class _ServiceProjectScreenState extends State<ServiceProjectScreen>
), ),
], ],
), ),
),
); );
} }
} }