Enhance UI and Navigation
- Added navigation to the dashboard after applying the theme in ThemeController. - Introduced a new PillTabBar widget for a modern tab design across multiple screens. - Updated dashboard screen to improve button actions and UI consistency. - Refactored contact detail screen to streamline layout and enhance gradient effects. - Implemented PillTabBar in directory main screen, expense screen, and payment request screen for consistent tab navigation. - Improved layout structure in user document screen and employee profile screen for better user experience. - Enhanced service project details screen with a modern tab bar implementation.
This commit is contained in:
parent
259f2aa928
commit
65fbef3441
@ -63,6 +63,9 @@ class ThemeController extends GetxController {
|
||||
|
||||
await Future.delayed(const Duration(milliseconds: 600));
|
||||
showApplied.value = false;
|
||||
|
||||
// Navigate to dashboard after applying theme
|
||||
Get.offAllNamed('/dashboard');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
61
lib/helpers/widgets/pill_tab_bar.dart
Normal file
61
lib/helpers/widgets/pill_tab_bar.dart
Normal file
@ -0,0 +1,61 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class PillTabBar extends StatelessWidget {
|
||||
final TabController controller;
|
||||
final List<String> tabs;
|
||||
final Color selectedColor;
|
||||
final Color unselectedColor;
|
||||
final Color indicatorColor;
|
||||
final double height;
|
||||
|
||||
const PillTabBar({
|
||||
Key? key,
|
||||
required this.controller,
|
||||
required this.tabs,
|
||||
this.selectedColor = Colors.blue,
|
||||
this.unselectedColor = Colors.grey,
|
||||
this.indicatorColor = Colors.blueAccent,
|
||||
this.height = 48,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
child: Container(
|
||||
height: height,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(height / 2),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.grey.withOpacity(0.15),
|
||||
blurRadius: 4,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: TabBar(
|
||||
controller: controller,
|
||||
indicator: BoxDecoration(
|
||||
color: indicatorColor.withOpacity(0.2),
|
||||
borderRadius: BorderRadius.circular(height / 2),
|
||||
),
|
||||
indicatorSize: TabBarIndicatorSize.tab,
|
||||
indicatorPadding: const EdgeInsets.symmetric(horizontal: 4, vertical: 4),
|
||||
labelColor: selectedColor,
|
||||
unselectedLabelColor: unselectedColor,
|
||||
labelStyle: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 15,
|
||||
),
|
||||
unselectedLabelStyle: const TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 15,
|
||||
),
|
||||
tabs: tabs.map((text) => Tab(text: text)).toList(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -81,30 +81,31 @@ class _DashboardScreenState extends State<DashboardScreen> with UIMixin {
|
||||
);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// CONDITIONAL QUICK ACTION CARD
|
||||
//---------------------------------------------------------------------------
|
||||
Widget _conditionalQuickActionCard() {
|
||||
// STATIC CONDITION
|
||||
String status = "O"; // <-- change if needed
|
||||
String status = "1"; // <-- change as needed
|
||||
bool isCheckedIn = status == "O";
|
||||
|
||||
// Button color remains the same
|
||||
Color buttonColor =
|
||||
isCheckedIn ? Colors.red.shade700 : Colors.green.shade700;
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
gradient: LinearGradient(
|
||||
colors: isCheckedIn
|
||||
? [Colors.red.shade200, Colors.red.shade400]
|
||||
: [Colors.green.shade200, Colors.green.shade400],
|
||||
colors: [
|
||||
contentTheme.primary.withOpacity(0.3), // lighter/faded
|
||||
contentTheme.primary.withOpacity(0.6), // slightly stronger
|
||||
],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black12.withOpacity(0.1),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 4),
|
||||
color: Colors.black12.withOpacity(0.05),
|
||||
blurRadius: 6,
|
||||
offset: const Offset(0, 3),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -139,32 +140,24 @@ class _DashboardScreenState extends State<DashboardScreen> with UIMixin {
|
||||
style: const TextStyle(color: Colors.white70, fontSize: 13),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
// Action Buttons
|
||||
// Action Button (solid color)
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
if (!isCheckedIn)
|
||||
ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
// Check-In action
|
||||
},
|
||||
label: const Text("Check-In"),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.green.shade700,
|
||||
foregroundColor: Colors.white,
|
||||
),
|
||||
ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
// Check-In / Check-Out action
|
||||
},
|
||||
icon: Icon(
|
||||
isCheckedIn ? LucideIcons.log_out : LucideIcons.log_in,
|
||||
size: 16,
|
||||
),
|
||||
if (isCheckedIn)
|
||||
ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
// Check-Out action
|
||||
},
|
||||
label: const Text("Check-Out"),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.red.shade700,
|
||||
foregroundColor: Colors.white,
|
||||
),
|
||||
label: Text(isCheckedIn ? "Check-Out" : "Check-In"),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: buttonColor,
|
||||
foregroundColor: Colors.white,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
@ -180,8 +173,8 @@ class _DashboardScreenState extends State<DashboardScreen> with UIMixin {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_sectionTitle("Quick Action"), // Change title to singular
|
||||
_conditionalQuickActionCard(), // Use the new conditional card
|
||||
_sectionTitle("Quick Action"),
|
||||
_conditionalQuickActionCard(),
|
||||
],
|
||||
);
|
||||
}
|
||||
@ -419,7 +412,7 @@ class _DashboardScreenState extends State<DashboardScreen> with UIMixin {
|
||||
children: [
|
||||
Icon(
|
||||
cardMeta.icon,
|
||||
size: 20, // **smaller icon**
|
||||
size: 20,
|
||||
color:
|
||||
isEnabled ? cardMeta.color : Colors.grey.shade400,
|
||||
),
|
||||
@ -428,7 +421,7 @@ class _DashboardScreenState extends State<DashboardScreen> with UIMixin {
|
||||
item.name,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 9.5, // **reduced text size**
|
||||
fontSize: 9.5,
|
||||
fontWeight: FontWeight.w600,
|
||||
color:
|
||||
isEnabled ? Colors.black87 : Colors.grey.shade600,
|
||||
@ -457,7 +450,7 @@ class _DashboardScreenState extends State<DashboardScreen> with UIMixin {
|
||||
backgroundColor: const Color(0xfff5f6fa),
|
||||
body: Layout(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 14),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
|
||||
@ -65,61 +65,47 @@ class _ContactDetailScreenState extends State<ContactDetailScreen>
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: const Color(0xFFF5F5F5),
|
||||
body: Stack(
|
||||
children: [
|
||||
// GRADIENT BEHIND APPBAR & TABBAR
|
||||
Positioned.fill(
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
height: 120,
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
appBarColor,
|
||||
appBarColor.withOpacity(0.0),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// ✔ AppBar is outside SafeArea (correct)
|
||||
appBar: CustomAppBar(
|
||||
title: 'Contact Profile',
|
||||
backgroundColor: appBarColor,
|
||||
onBackPressed: () => Get.offAllNamed('/dashboard/directory-main-page'),
|
||||
),
|
||||
|
||||
// ✔ Only the content is wrapped inside SafeArea
|
||||
body: SafeArea(
|
||||
child: Column(
|
||||
children: [
|
||||
// ************ GRADIENT + SUBHEADER + TABBAR ************
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
contentTheme.primary,
|
||||
contentTheme.primary.withOpacity(0),
|
||||
],
|
||||
),
|
||||
Expanded(child: Container(color: Colors.grey[100])),
|
||||
],
|
||||
),
|
||||
child: Obx(() => _buildSubHeader(contactRx.value)),
|
||||
),
|
||||
),
|
||||
|
||||
// MAIN CONTENT
|
||||
SafeArea(
|
||||
top: true,
|
||||
bottom: true,
|
||||
child: Column(
|
||||
children: [
|
||||
// APPBAR
|
||||
CustomAppBar(
|
||||
title: 'Contact Profile',
|
||||
backgroundColor: Colors.transparent,
|
||||
onBackPressed: () =>
|
||||
Get.offAllNamed('/dashboard/directory-main-page'),
|
||||
),
|
||||
|
||||
// SUBHEADER + TABBAR
|
||||
Obx(() => _buildSubHeader(contactRx.value)),
|
||||
|
||||
// TABBAR VIEW
|
||||
Expanded(
|
||||
child: TabBarView(
|
||||
controller: _tabController,
|
||||
children: [
|
||||
Obx(() => _buildDetailsTab(contactRx.value)),
|
||||
_buildCommentsTab(),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
// ************ TAB CONTENT ************
|
||||
Expanded(
|
||||
child: TabBarView(
|
||||
controller: _tabController,
|
||||
children: [
|
||||
Obx(() => _buildDetailsTab(contactRx.value)),
|
||||
_buildCommentsTab(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -129,39 +115,70 @@ class _ContactDetailScreenState extends State<ContactDetailScreen>
|
||||
final lastName =
|
||||
contact.name.split(" ").length > 1 ? contact.name.split(" ").last : "";
|
||||
|
||||
final Color primaryColor = contentTheme.primary;
|
||||
|
||||
return Container(
|
||||
color: Colors.transparent,
|
||||
child: Padding(
|
||||
padding: MySpacing.xy(16, 12),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(children: [
|
||||
Avatar(firstName: firstName, lastName: lastName, size: 35),
|
||||
MySpacing.width(12),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
MyText.titleSmall(contact.name,
|
||||
fontWeight: 600, color: Colors.black),
|
||||
MySpacing.height(2),
|
||||
MyText.bodySmall(contact.organization,
|
||||
fontWeight: 500, color: Colors.grey[700]),
|
||||
],
|
||||
),
|
||||
]),
|
||||
TabBar(
|
||||
padding: MySpacing.xy(16, 12),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(children: [
|
||||
Avatar(firstName: firstName, lastName: lastName, size: 35),
|
||||
MySpacing.width(12),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
MyText.titleSmall(contact.name,
|
||||
fontWeight: 600, color: Colors.black),
|
||||
MySpacing.height(2),
|
||||
MyText.bodySmall(contact.organization,
|
||||
fontWeight: 500, color: Colors.grey[700]),
|
||||
],
|
||||
),
|
||||
]),
|
||||
MySpacing.height(12),
|
||||
// === MODERN PILL-SHAPED TABBAR ===
|
||||
Container(
|
||||
height: 48,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.grey.withOpacity(0.15),
|
||||
blurRadius: 4,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: TabBar(
|
||||
controller: _tabController,
|
||||
labelColor: Colors.black,
|
||||
unselectedLabelColor: Colors.grey,
|
||||
indicatorColor: contentTheme.primary,
|
||||
indicator: BoxDecoration(
|
||||
color: primaryColor.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
),
|
||||
indicatorSize: TabBarIndicatorSize.tab,
|
||||
indicatorPadding:
|
||||
const EdgeInsets.symmetric(horizontal: 4.0, vertical: 4.0),
|
||||
labelColor: primaryColor,
|
||||
unselectedLabelColor: Colors.grey.shade600,
|
||||
labelStyle: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 15,
|
||||
),
|
||||
unselectedLabelStyle: const TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 15,
|
||||
),
|
||||
tabs: const [
|
||||
Tab(text: "Details"),
|
||||
Tab(text: "Notes"),
|
||||
],
|
||||
dividerColor: Colors.transparent,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -3,12 +3,13 @@ import 'package:get/get.dart';
|
||||
|
||||
import 'package:on_field_work/controller/directory/directory_controller.dart';
|
||||
import 'package:on_field_work/controller/directory/notes_controller.dart';
|
||||
import 'package:on_field_work/controller/project_controller.dart';
|
||||
import 'package:on_field_work/helpers/widgets/my_spacing.dart';
|
||||
import 'package:on_field_work/helpers/widgets/my_text.dart';
|
||||
|
||||
import 'package:on_field_work/helpers/widgets/custom_app_bar.dart';
|
||||
|
||||
import 'package:on_field_work/view/directory/directory_view.dart';
|
||||
import 'package:on_field_work/view/directory/notes_view.dart';
|
||||
import 'package:on_field_work/helpers/utils/mixins/ui_mixin.dart';
|
||||
import 'package:on_field_work/helpers/widgets/pill_tab_bar.dart';
|
||||
|
||||
class DirectoryMainScreen extends StatefulWidget {
|
||||
const DirectoryMainScreen({super.key});
|
||||
@ -18,7 +19,7 @@ class DirectoryMainScreen extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _DirectoryMainScreenState extends State<DirectoryMainScreen>
|
||||
with SingleTickerProviderStateMixin {
|
||||
with SingleTickerProviderStateMixin, UIMixin {
|
||||
late TabController _tabController;
|
||||
|
||||
final DirectoryController controller = Get.put(DirectoryController());
|
||||
@ -38,97 +39,46 @@ class _DirectoryMainScreenState extends State<DirectoryMainScreen>
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return OrientationBuilder(
|
||||
builder: (context, orientation) {
|
||||
final bool isLandscape = orientation == Orientation.landscape;
|
||||
final Color appBarColor = contentTheme.primary;
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: const Color(0xFFF5F5F5),
|
||||
appBar: PreferredSize(
|
||||
preferredSize: Size.fromHeight(
|
||||
isLandscape ? 55 : 72, // Responsive height
|
||||
),
|
||||
child: SafeArea(
|
||||
bottom: false,
|
||||
child: AppBar(
|
||||
backgroundColor: const Color(0xFFF5F5F5),
|
||||
elevation: 0.5,
|
||||
automaticallyImplyLeading: false,
|
||||
titleSpacing: 0,
|
||||
title: Padding(
|
||||
padding: MySpacing.xy(16, 0),
|
||||
child: Row(
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.arrow_back_ios_new,
|
||||
color: Colors.black, size: 20),
|
||||
onPressed: () => Get.offNamed('/dashboard'),
|
||||
),
|
||||
MySpacing.width(8),
|
||||
|
||||
/// FIX: Flexible to prevent overflow in landscape
|
||||
Flexible(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
MyText.titleLarge(
|
||||
'Directory',
|
||||
fontWeight: 700,
|
||||
color: Colors.black,
|
||||
),
|
||||
MySpacing.height(2),
|
||||
GetBuilder<ProjectController>(
|
||||
builder: (projectController) {
|
||||
final projectName =
|
||||
projectController.selectedProject?.name ??
|
||||
'Select Project';
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
const Icon(Icons.work_outline,
|
||||
size: 14, color: Colors.grey),
|
||||
MySpacing.width(4),
|
||||
Expanded(
|
||||
child: MyText.bodySmall(
|
||||
projectName,
|
||||
fontWeight: 600,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
color: Colors.grey[700],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
return Scaffold(
|
||||
backgroundColor: const Color(0xFFF1F1F1),
|
||||
appBar: CustomAppBar(
|
||||
title: "Directory",
|
||||
onBackPressed: () => Get.offNamed('/dashboard'),
|
||||
backgroundColor: appBarColor,
|
||||
),
|
||||
body: Stack(
|
||||
children: [
|
||||
// === TOP GRADIENT ===
|
||||
Container(
|
||||
height: 50,
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
appBarColor,
|
||||
appBarColor.withOpacity(0.0),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
/// MAIN CONTENT
|
||||
body: SafeArea(
|
||||
SafeArea(
|
||||
top: false,
|
||||
bottom: true,
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
color: Colors.white,
|
||||
child: TabBar(
|
||||
controller: _tabController,
|
||||
labelColor: Colors.black,
|
||||
unselectedLabelColor: Colors.grey,
|
||||
indicatorColor: Colors.red,
|
||||
tabs: const [
|
||||
Tab(text: "Directory"),
|
||||
Tab(text: "Notes"),
|
||||
],
|
||||
),
|
||||
PillTabBar(
|
||||
controller: _tabController,
|
||||
tabs: const ["Directory", "Notes"],
|
||||
selectedColor: contentTheme.primary,
|
||||
unselectedColor: Colors.grey.shade600,
|
||||
indicatorColor: contentTheme.primary,
|
||||
),
|
||||
|
||||
// === TABBAR VIEW ===
|
||||
Expanded(
|
||||
child: TabBarView(
|
||||
controller: _tabController,
|
||||
@ -141,8 +91,8 @@ class _DirectoryMainScreenState extends State<DirectoryMainScreen>
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -115,7 +115,6 @@ class _UserDocumentsPageState extends State<UserDocumentsPage>
|
||||
void dispose() {
|
||||
_scrollController.dispose();
|
||||
_fabAnimationController.dispose();
|
||||
docController.searchController.dispose();
|
||||
docController.documents.clear();
|
||||
super.dispose();
|
||||
}
|
||||
@ -137,7 +136,7 @@ class _UserDocumentsPageState extends State<UserDocumentsPage>
|
||||
],
|
||||
),
|
||||
child: TextField(
|
||||
controller: docController.searchController,
|
||||
controller: docController.searchController, // keep GetX controller
|
||||
onChanged: (value) {
|
||||
docController.searchQuery.value = value;
|
||||
docController.fetchDocuments(
|
||||
@ -804,103 +803,93 @@ class _UserDocumentsPageState extends State<UserDocumentsPage>
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBody() {
|
||||
return Obx(() {
|
||||
// Check permissions
|
||||
if (permissionController.permissions.isEmpty) {
|
||||
return _buildLoadingIndicator();
|
||||
}
|
||||
Widget _buildBody() {
|
||||
// Non-reactive widgets
|
||||
final searchBar = _buildSearchBar();
|
||||
final filterChips = _buildFilterChips();
|
||||
final statusBanner = _buildStatusBanner();
|
||||
|
||||
if (!permissionController.hasPermission(Permissions.viewDocument)) {
|
||||
return _buildPermissionDenied();
|
||||
}
|
||||
return Column(
|
||||
children: [
|
||||
searchBar,
|
||||
filterChips,
|
||||
statusBanner,
|
||||
|
||||
// Show skeleton loader
|
||||
if (docController.isLoading.value && docController.documents.isEmpty) {
|
||||
return SingleChildScrollView(
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
child: SkeletonLoaders.documentSkeletonLoader(),
|
||||
);
|
||||
}
|
||||
// Only the list is reactive
|
||||
Expanded(
|
||||
child: Obx(() {
|
||||
if (!permissionController.hasPermission(Permissions.viewDocument)) {
|
||||
return _buildPermissionDenied();
|
||||
}
|
||||
|
||||
final docs = docController.documents;
|
||||
final docs = docController.documents;
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
_buildSearchBar(),
|
||||
_buildFilterChips(),
|
||||
_buildStatusBanner(),
|
||||
Expanded(
|
||||
child: MyRefreshIndicator(
|
||||
onRefresh: () async {
|
||||
final combinedFilter = {
|
||||
'uploadedByIds': docController.selectedUploadedBy.toList(),
|
||||
'documentCategoryIds':
|
||||
docController.selectedCategory.toList(),
|
||||
'documentTypeIds': docController.selectedType.toList(),
|
||||
'documentTagIds': docController.selectedTag.toList(),
|
||||
};
|
||||
// Skeleton loader
|
||||
if (docController.isLoading.value && docs.isEmpty) {
|
||||
return SkeletonLoaders.documentSkeletonLoader();
|
||||
}
|
||||
|
||||
await docController.fetchDocuments(
|
||||
entityTypeId: entityTypeId,
|
||||
entityId: resolvedEntityId,
|
||||
filter: jsonEncode(combinedFilter),
|
||||
reset: true,
|
||||
);
|
||||
// Empty state
|
||||
if (!docController.isLoading.value && docs.isEmpty) {
|
||||
return _buildEmptyState();
|
||||
}
|
||||
|
||||
// List of documents
|
||||
return MyRefreshIndicator(
|
||||
onRefresh: () async {
|
||||
final combinedFilter = {
|
||||
'uploadedByIds': docController.selectedUploadedBy.toList(),
|
||||
'documentCategoryIds': docController.selectedCategory.toList(),
|
||||
'documentTypeIds': docController.selectedType.toList(),
|
||||
'documentTagIds': docController.selectedTag.toList(),
|
||||
};
|
||||
|
||||
await docController.fetchDocuments(
|
||||
entityTypeId: entityTypeId,
|
||||
entityId: resolvedEntityId,
|
||||
filter: jsonEncode(combinedFilter),
|
||||
reset: true,
|
||||
);
|
||||
},
|
||||
child: ListView.builder(
|
||||
controller: _scrollController,
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
padding: const EdgeInsets.only(bottom: 100, top: 8),
|
||||
itemCount: docs.length + 1,
|
||||
itemBuilder: (context, index) {
|
||||
if (index == docs.length) {
|
||||
return Obx(() {
|
||||
if (docController.isLoading.value) {
|
||||
return _buildLoadingIndicator();
|
||||
}
|
||||
if (!docController.hasMore.value && docs.isNotEmpty) {
|
||||
return _buildNoMoreIndicator();
|
||||
}
|
||||
return const SizedBox.shrink();
|
||||
});
|
||||
}
|
||||
|
||||
final doc = docs[index];
|
||||
final currentDate = doc.uploadedAt != null
|
||||
? DateFormat("dd MMM yyyy").format(doc.uploadedAt!.toLocal())
|
||||
: '';
|
||||
final prevDate = index > 0
|
||||
? (docs[index - 1].uploadedAt != null
|
||||
? DateFormat("dd MMM yyyy")
|
||||
.format(docs[index - 1].uploadedAt!.toLocal())
|
||||
: '')
|
||||
: null;
|
||||
final showDateHeader = currentDate != prevDate;
|
||||
|
||||
return _buildDocumentCard(doc, showDateHeader);
|
||||
},
|
||||
child: docs.isEmpty
|
||||
? ListView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
children: [
|
||||
SizedBox(
|
||||
height: MediaQuery.of(context).size.height * 0.5,
|
||||
child: _buildEmptyState(),
|
||||
),
|
||||
],
|
||||
)
|
||||
: ListView.builder(
|
||||
controller: _scrollController,
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
padding: const EdgeInsets.only(bottom: 100, top: 8),
|
||||
itemCount: docs.length + 1,
|
||||
itemBuilder: (context, index) {
|
||||
if (index == docs.length) {
|
||||
return Obx(() {
|
||||
if (docController.isLoading.value) {
|
||||
return _buildLoadingIndicator();
|
||||
}
|
||||
if (!docController.hasMore.value &&
|
||||
docs.isNotEmpty) {
|
||||
return _buildNoMoreIndicator();
|
||||
}
|
||||
return const SizedBox.shrink();
|
||||
});
|
||||
}
|
||||
|
||||
final doc = docs[index];
|
||||
final currentDate = doc.uploadedAt != null
|
||||
? DateFormat("dd MMM yyyy")
|
||||
.format(doc.uploadedAt!.toLocal())
|
||||
: '';
|
||||
|
||||
final prevDate = index > 0
|
||||
? (docs[index - 1].uploadedAt != null
|
||||
? DateFormat("dd MMM yyyy").format(
|
||||
docs[index - 1].uploadedAt!.toLocal())
|
||||
: '')
|
||||
: null;
|
||||
|
||||
final showDateHeader = currentDate != prevDate;
|
||||
|
||||
return _buildDocumentCard(doc, showDateHeader);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
||||
}),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFAB() {
|
||||
return Obx(() {
|
||||
|
||||
@ -16,11 +16,14 @@ class EmployeeProfilePage extends StatefulWidget {
|
||||
|
||||
class _EmployeeProfilePageState extends State<EmployeeProfilePage>
|
||||
with SingleTickerProviderStateMixin, UIMixin {
|
||||
// We no longer need to listen to the TabController for setState,
|
||||
// as the TabBar handles its own state updates via the controller.
|
||||
late TabController _tabController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// Initialize TabController with 2 tabs
|
||||
_tabController = TabController(length: 2, vsync: this);
|
||||
}
|
||||
|
||||
@ -30,9 +33,13 @@ class _EmployeeProfilePageState extends State<EmployeeProfilePage>
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
// --- No need for _buildSegmentedButton function anymore ---
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Accessing theme colors for consistency
|
||||
final Color appBarColor = contentTheme.primary;
|
||||
final Color primaryColor = contentTheme.primary;
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: const Color(0xFFF1F1F1),
|
||||
@ -43,7 +50,8 @@ class _EmployeeProfilePageState extends State<EmployeeProfilePage>
|
||||
),
|
||||
body: Stack(
|
||||
children: [
|
||||
// === Gradient at the top behind AppBar + TabBar ===
|
||||
// === Gradient at the top behind AppBar + Toggle ===
|
||||
// This container ensures the background color transitions nicely
|
||||
Container(
|
||||
height: 50,
|
||||
decoration: BoxDecoration(
|
||||
@ -57,25 +65,63 @@ class _EmployeeProfilePageState extends State<EmployeeProfilePage>
|
||||
),
|
||||
),
|
||||
),
|
||||
// === Main Content Area ===
|
||||
SafeArea(
|
||||
top: false,
|
||||
bottom: true,
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
decoration: const BoxDecoration(color: Colors.transparent),
|
||||
child: TabBar(
|
||||
controller: _tabController,
|
||||
labelColor: Colors.white,
|
||||
unselectedLabelColor: Colors.white70,
|
||||
indicatorColor: Colors.white,
|
||||
indicatorWeight: 3,
|
||||
tabs: const [
|
||||
Tab(text: "Details"),
|
||||
Tab(text: "Documents"),
|
||||
],
|
||||
// 🛑 NEW: The Modern TabBar Implementation 🛑
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
|
||||
child: Container(
|
||||
height: 48, // Define a specific height for the TabBar container
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(24.0), // Rounded corners for a chip-like look
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.grey.withOpacity(0.15),
|
||||
blurRadius: 4,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: TabBar(
|
||||
controller: _tabController,
|
||||
// Style the indicator as a subtle pill/chip
|
||||
indicator: BoxDecoration(
|
||||
color: primaryColor.withOpacity(0.1), // Light background color for the selection
|
||||
borderRadius: BorderRadius.circular(24.0),
|
||||
),
|
||||
indicatorSize: TabBarIndicatorSize.tab,
|
||||
// The padding is used to slightly shrink the indicator area
|
||||
indicatorPadding: const EdgeInsets.symmetric(horizontal: 4.0, vertical: 4.0),
|
||||
|
||||
// Text styling
|
||||
labelColor: primaryColor, // Selected text color is primary
|
||||
unselectedLabelColor: Colors.grey.shade600, // Unselected text color is darker grey
|
||||
labelStyle: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 15,
|
||||
),
|
||||
unselectedLabelStyle: const TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 15,
|
||||
),
|
||||
|
||||
// Tabs (No custom widget needed, just use the built-in Tab)
|
||||
tabs: const [
|
||||
Tab(text: "Details"),
|
||||
Tab(text: "Documents"),
|
||||
],
|
||||
// Setting this to zero removes the default underline
|
||||
dividerColor: Colors.transparent,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// 🛑 TabBarView (The Content) 🛑
|
||||
Expanded(
|
||||
child: TabBarView(
|
||||
controller: _tabController,
|
||||
|
||||
@ -13,6 +13,7 @@ import 'package:on_field_work/helpers/utils/permission_constants.dart';
|
||||
import 'package:on_field_work/helpers/widgets/my_refresh_indicator.dart';
|
||||
import 'package:on_field_work/helpers/utils/mixins/ui_mixin.dart';
|
||||
import 'package:on_field_work/helpers/widgets/custom_app_bar.dart';
|
||||
import 'package:on_field_work/helpers/widgets/pill_tab_bar.dart';
|
||||
|
||||
class ExpenseMainScreen extends StatefulWidget {
|
||||
const ExpenseMainScreen({super.key});
|
||||
@ -117,8 +118,7 @@ class _ExpenseMainScreenState extends State<ExpenseMainScreen>
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child:
|
||||
Container(color: Colors.grey[100]),
|
||||
child: Container(color: Colors.grey[100]),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -130,20 +130,12 @@ class _ExpenseMainScreenState extends State<ExpenseMainScreen>
|
||||
bottom: true,
|
||||
child: Column(
|
||||
children: [
|
||||
// TAB BAR WITH TRANSPARENT BACKGROUND
|
||||
Container(
|
||||
decoration: const BoxDecoration(color: Colors.transparent),
|
||||
child: TabBar(
|
||||
controller: _tabController,
|
||||
labelColor: Colors.white,
|
||||
unselectedLabelColor: Colors.white70,
|
||||
indicatorColor: Colors.white,
|
||||
indicatorWeight: 3,
|
||||
tabs: const [
|
||||
Tab(text: "Current Month"),
|
||||
Tab(text: "History"),
|
||||
],
|
||||
),
|
||||
PillTabBar(
|
||||
controller: _tabController,
|
||||
tabs: const ["Current Month", "History"],
|
||||
selectedColor: contentTheme.primary,
|
||||
unselectedColor: Colors.grey.shade600,
|
||||
indicatorColor: contentTheme.primary,
|
||||
),
|
||||
|
||||
// CONTENT AREA
|
||||
|
||||
@ -14,6 +14,7 @@ import 'package:on_field_work/controller/permission_controller.dart';
|
||||
import 'package:on_field_work/helpers/utils/permission_constants.dart';
|
||||
import 'package:on_field_work/helpers/widgets/my_refresh_indicator.dart';
|
||||
import 'package:on_field_work/helpers/widgets/custom_app_bar.dart';
|
||||
import 'package:on_field_work/helpers/widgets/pill_tab_bar.dart';
|
||||
|
||||
class PaymentRequestMainScreen extends StatefulWidget {
|
||||
const PaymentRequestMainScreen({super.key});
|
||||
@ -126,8 +127,7 @@ class _PaymentRequestMainScreenState extends State<PaymentRequestMainScreen>
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child:
|
||||
Container(color: Colors.grey[100]),
|
||||
child: Container(color: Colors.grey[100]),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -139,19 +139,12 @@ class _PaymentRequestMainScreenState extends State<PaymentRequestMainScreen>
|
||||
bottom: true,
|
||||
child: Column(
|
||||
children: [
|
||||
// TAB BAR WITH TRANSPARENT BACKGROUND
|
||||
Container(
|
||||
decoration: const BoxDecoration(color: Colors.transparent),
|
||||
child: TabBar(
|
||||
controller: _tabController,
|
||||
labelColor: Colors.white,
|
||||
unselectedLabelColor: Colors.white70,
|
||||
indicatorColor: Colors.white,
|
||||
tabs: const [
|
||||
Tab(text: "Current Month"),
|
||||
Tab(text: "History"),
|
||||
],
|
||||
),
|
||||
PillTabBar(
|
||||
controller: _tabController,
|
||||
tabs: const ["Current Month", "History"],
|
||||
selectedColor: contentTheme.primary,
|
||||
unselectedColor: Colors.grey.shade600,
|
||||
indicatorColor: contentTheme.primary,
|
||||
),
|
||||
|
||||
// CONTENT AREA
|
||||
|
||||
@ -9,6 +9,7 @@ import 'package:on_field_work/helpers/services/api_endpoints.dart';
|
||||
import 'package:on_field_work/images.dart';
|
||||
import 'package:on_field_work/view/layouts/user_profile_right_bar.dart';
|
||||
import 'package:on_field_work/helpers/services/tenant_service.dart';
|
||||
import 'package:on_field_work/helpers/utils/mixins/ui_mixin.dart';
|
||||
|
||||
class Layout extends StatefulWidget {
|
||||
final Widget? child;
|
||||
@ -20,7 +21,7 @@ class Layout extends StatefulWidget {
|
||||
State<Layout> createState() => _LayoutState();
|
||||
}
|
||||
|
||||
class _LayoutState extends State<Layout> {
|
||||
class _LayoutState extends State<Layout> with UIMixin {
|
||||
final LayoutController controller = LayoutController();
|
||||
final EmployeeInfo? employeeInfo = LocalStorage.getEmployeeInfo();
|
||||
final bool isBetaEnvironment = ApiEndpoints.baseUrl.contains("stage");
|
||||
@ -57,142 +58,155 @@ class _LayoutState extends State<Layout> {
|
||||
}
|
||||
|
||||
Widget _buildScaffold(BuildContext context, {bool isMobile = false}) {
|
||||
final primaryColor = contentTheme.primary;
|
||||
|
||||
return Scaffold(
|
||||
key: controller.scaffoldKey,
|
||||
endDrawer: const UserProfileBar(),
|
||||
floatingActionButton: widget.floatingActionButton,
|
||||
body: SafeArea(
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: () {},
|
||||
child: Column(
|
||||
children: [
|
||||
_buildHeader(context, isMobile),
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
key: controller.scrollKey,
|
||||
// Removed redundant vertical padding here. DashboardScreen's
|
||||
// SingleChildScrollView now handles all internal padding.
|
||||
padding: EdgeInsets.symmetric(horizontal: 0, vertical: 0),
|
||||
child: widget.child,
|
||||
),
|
||||
),
|
||||
body: 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],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Header Section (Project selection removed)
|
||||
Widget _buildHeader(BuildContext context, bool isMobile) {
|
||||
final selectedTenant = TenantService.currentTenant;
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 0),
|
||||
child: Card(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
),
|
||||
margin: EdgeInsets.zero,
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: Row(
|
||||
children: [
|
||||
ClipRRect(
|
||||
child: Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
Image.asset(
|
||||
Images.logoDark,
|
||||
height: 50,
|
||||
width: 50,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
if (isBetaEnvironment)
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 4, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.deepPurple,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
border: Border.all(color: Colors.white, width: 1.2),
|
||||
),
|
||||
child: const Text(
|
||||
'B',
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
|
||||
/// Dashboard title + current organization
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
MyText.bodyLarge(
|
||||
"Dashboard",
|
||||
fontWeight: 700,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
// MyText.bodyMedium(
|
||||
// "Hi, ${employeeInfo?.firstName ?? ''}",
|
||||
// color: Colors.black54,
|
||||
// ),
|
||||
if (selectedTenant != null)
|
||||
MyText.bodySmall(
|
||||
"Organization: ${selectedTenant.name}",
|
||||
color: Colors.black54,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
/// Menu Button
|
||||
Stack(
|
||||
clipBehavior: Clip.none,
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.menu),
|
||||
onPressed: () =>
|
||||
controller.scaffoldKey.currentState?.openEndDrawer(),
|
||||
child: Column(
|
||||
children: [
|
||||
_buildHeaderContent(isMobile),
|
||||
Expanded(
|
||||
child: SafeArea(
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: () {},
|
||||
child: SingleChildScrollView(
|
||||
key: controller.scrollKey,
|
||||
padding: EdgeInsets.zero,
|
||||
child: widget.child,
|
||||
),
|
||||
if (!hasMpin)
|
||||
Positioned(
|
||||
right: 10,
|
||||
top: 10,
|
||||
child: Container(
|
||||
width: 14,
|
||||
height: 14,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.redAccent,
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(color: Colors.white, width: 2),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHeaderContent(bool isMobile) {
|
||||
final selectedTenant = TenantService.currentTenant;
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(10, 45, 10, 0),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.1),
|
||||
blurRadius: 6,
|
||||
offset: const Offset(0, 3),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
// Logo inside white background card
|
||||
Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
Image.asset(
|
||||
Images.logoDark,
|
||||
height: 50,
|
||||
width: 50,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
if (ApiEndpoints.baseUrl.contains("stage"))
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 4, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.deepPurple,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
border: Border.all(color: Colors.white, width: 1.2),
|
||||
),
|
||||
child: const Text(
|
||||
'B',
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
MyText.bodyLarge(
|
||||
"Dashboard",
|
||||
fontWeight: 700,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
color: Colors.black87,
|
||||
),
|
||||
if (selectedTenant != null)
|
||||
MyText.bodySmall(
|
||||
"Organization: ${selectedTenant.name}",
|
||||
color: Colors.black54,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Stack(
|
||||
clipBehavior: Clip.none,
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.menu, color: Colors.black87),
|
||||
onPressed: () =>
|
||||
controller.scaffoldKey.currentState?.openEndDrawer(),
|
||||
),
|
||||
if (!hasMpin)
|
||||
Positioned(
|
||||
right: 10,
|
||||
top: 10,
|
||||
child: Container(
|
||||
width: 14,
|
||||
height: 14,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.redAccent,
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(color: Colors.white, width: 2),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -13,6 +13,7 @@ import 'package:on_field_work/model/service_project/service_project_allocation_b
|
||||
import 'package:on_field_work/model/employees/employee_model.dart';
|
||||
import 'package:on_field_work/view/service_project/jobs_tab.dart';
|
||||
import 'package:on_field_work/helpers/widgets/my_refresh_indicator.dart';
|
||||
import 'package:on_field_work/helpers/widgets/pill_tab_bar.dart';
|
||||
|
||||
class ServiceProjectDetailsScreen extends StatefulWidget {
|
||||
final String projectId;
|
||||
@ -460,27 +461,13 @@ class _ServiceProjectDetailsScreenState
|
||||
bottom: true,
|
||||
child: Column(
|
||||
children: [
|
||||
// === TAB BAR WITH TRANSPARENT BACKGROUND ===
|
||||
Container(
|
||||
decoration: const BoxDecoration(color: Colors.transparent),
|
||||
child: TabBar(
|
||||
controller: _tabController,
|
||||
labelColor: Colors.white,
|
||||
unselectedLabelColor: Colors.white70,
|
||||
indicatorColor: Colors.white,
|
||||
indicatorWeight: 3,
|
||||
tabs: [
|
||||
Tab(
|
||||
child: MyText.bodyMedium("Profile",
|
||||
color: Colors.white)),
|
||||
Tab(
|
||||
child:
|
||||
MyText.bodyMedium("Jobs", color: Colors.white)),
|
||||
Tab(
|
||||
child:
|
||||
MyText.bodyMedium("Teams", color: Colors.white)),
|
||||
],
|
||||
),
|
||||
PillTabBar(
|
||||
controller: _tabController,
|
||||
tabs: const ["Profile", "Jobs", "Teams"],
|
||||
selectedColor: contentTheme.primary,
|
||||
unselectedColor: Colors.grey.shade600,
|
||||
indicatorColor: contentTheme.primary.withOpacity(0.1),
|
||||
height: 48,
|
||||
),
|
||||
|
||||
// === TABBAR VIEW ===
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user