Refactor CustomAppBar to StatefulWidget; implement project selection dropdown and improve UI interactions

This commit is contained in:
Vaibhav Surve 2025-11-29 15:02:03 +05:30
parent ed2eb014d8
commit c9e73771b0

View File

@ -5,11 +5,11 @@ 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/utils/mixins/ui_mixin.dart';
class CustomAppBar extends StatelessWidget
class CustomAppBar extends StatefulWidget
with UIMixin
implements PreferredSizeWidget {
final String title;
final String? projectName;
final String? projectName; // If passed, show static text
final VoidCallback? onBackPressed;
final Color? backgroundColor;
@ -24,12 +24,102 @@ class CustomAppBar extends StatelessWidget
@override
Size get preferredSize => const Size.fromHeight(72);
@override
State<CustomAppBar> createState() => _CustomAppBarState();
}
class _CustomAppBarState extends State<CustomAppBar> with UIMixin {
final ProjectController projectController = Get.find();
OverlayEntry? _overlayEntry;
final LayerLink _layerLink = LayerLink();
void _toggleDropdown() {
if (_overlayEntry == null) {
_overlayEntry = _createOverlayEntry();
Overlay.of(context).insert(_overlayEntry!);
} else {
_overlayEntry?.remove();
_overlayEntry = null;
}
}
OverlayEntry _createOverlayEntry() {
final renderBox = context.findRenderObject() as RenderBox;
final size = renderBox.size;
final offset = renderBox.localToGlobal(Offset.zero);
return OverlayEntry(
builder: (context) => GestureDetector(
onTap: () {
_toggleDropdown();
},
behavior: HitTestBehavior.translucent,
child: Stack(
children: [
Positioned(
left: offset.dx + 16,
top: offset.dy + size.height,
width: size.width - 32,
child: Material(
elevation: 4,
borderRadius: BorderRadius.circular(5),
child: Container(
height: MediaQuery.of(context).size.height * 0.33,
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(5),
),
child: Column(
children: [
TextField(
decoration: InputDecoration(
hintText: "Search project...",
isDense: true,
prefixIcon: const Icon(Icons.search),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(5)),
),
),
Expanded(
child: ListView.builder(
itemCount: projectController.projects.length,
itemBuilder: (_, index) {
final project = projectController.projects[index];
return RadioListTile<String>(
dense: true,
value: project.id,
groupValue:
projectController.selectedProjectId.value,
onChanged: (v) {
if (v != null) {
projectController.updateSelectedProject(v);
_toggleDropdown();
}
},
title: Text(project.name),
);
},
),
),
],
),
),
),
),
],
),
),
);
}
@override
Widget build(BuildContext context) {
final Color effectiveBackgroundColor =
backgroundColor ?? contentTheme.primary;
widget.backgroundColor ?? contentTheme.primary;
const Color onPrimaryColor = Colors.white;
const double horizontalPadding = 16.0;
final bool showDropdown = widget.projectName == null;
return AppBar(
backgroundColor: effectiveBackgroundColor,
@ -37,63 +127,82 @@ class CustomAppBar extends StatelessWidget
automaticallyImplyLeading: false,
titleSpacing: 0,
shadowColor: Colors.transparent,
leading: Padding(
padding: MySpacing.only(left: horizontalPadding),
padding: MySpacing.only(left: 16),
child: IconButton(
icon: const Icon(
Icons.arrow_back_ios_new,
color: onPrimaryColor,
size: 20,
),
onPressed: onBackPressed ?? () => Get.back(),
onPressed: widget.onBackPressed ?? () => Get.back(),
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
),
),
title: Padding(
padding: MySpacing.only(right: horizontalPadding, left: 8),
padding: MySpacing.only(right: 16, left: 8),
child: Row(
children: [
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
MyText.titleLarge(
title,
widget.title,
fontWeight: 800,
color: onPrimaryColor,
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
MySpacing.height(3),
GetBuilder<ProjectController>(
builder: (projectController) {
final displayProjectName = projectName ??
projectController.selectedProject?.name ??
showDropdown
? CompositedTransformTarget(
link: _layerLink,
child: GestureDetector(
onTap: _toggleDropdown,
child: Row(
children: [
const Icon(Icons.folder_open,
size: 14, color: onPrimaryColor),
MySpacing.width(4),
Flexible(
child: Obx(() {
final projectName = projectController
.selectedProject?.name ??
'Select Project';
return Row(
return MyText.bodySmall(
projectName,
fontWeight: 500,
color: onPrimaryColor.withOpacity(0.8),
overflow: TextOverflow.ellipsis,
maxLines: 1,
);
}),
),
MySpacing.width(2),
const Icon(Icons.keyboard_arrow_down,
size: 18, color: onPrimaryColor),
],
),
),
)
: Row(
children: [
const Icon(Icons.folder_open,
size: 14, color: onPrimaryColor),
MySpacing.width(4),
Flexible(
child: MyText.bodySmall(
displayProjectName,
widget.projectName!,
fontWeight: 500,
color: onPrimaryColor.withOpacity(0.8),
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
),
MySpacing.width(2),
const Icon(Icons.keyboard_arrow_down,
size: 18, color: onPrimaryColor),
],
);
},
),
],
),
@ -103,7 +212,7 @@ class CustomAppBar extends StatelessWidget
),
actions: [
Padding(
padding: MySpacing.only(right: horizontalPadding),
padding: MySpacing.only(right: 16),
child: IconButton(
icon: const Icon(Icons.home, color: onPrimaryColor, size: 24),
onPressed: () => Get.offAllNamed('/dashboard'),
@ -112,4 +221,10 @@ class CustomAppBar extends StatelessWidget
],
);
}
@override
void dispose() {
_overlayEntry?.remove();
super.dispose();
}
}