enhacement of UI for mobile screen responsiveness
This commit is contained in:
parent
3e8bd1c41d
commit
18fbfaa42d
@ -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 {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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>
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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(
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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(
|
||||||
|
|||||||
@ -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(
|
||||||
|
|||||||
@ -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>
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user