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/widgets/my_text.dart';
import 'package:on_field_work/helpers/utils/mixins/ui_mixin.dart'; import 'package:on_field_work/helpers/utils/mixins/ui_mixin.dart';
class CustomAppBar extends StatelessWidget class CustomAppBar extends StatefulWidget
with UIMixin with UIMixin
implements PreferredSizeWidget { implements PreferredSizeWidget {
final String title; final String title;
final String? projectName; final String? projectName; // If passed, show static text
final VoidCallback? onBackPressed; final VoidCallback? onBackPressed;
final Color? backgroundColor; final Color? backgroundColor;
@ -24,77 +24,186 @@ class CustomAppBar extends StatelessWidget
@override @override
Size get preferredSize => const Size.fromHeight(72); 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final Color effectiveBackgroundColor = final Color effectiveBackgroundColor =
backgroundColor ?? contentTheme.primary; widget.backgroundColor ?? contentTheme.primary;
const Color onPrimaryColor = Colors.white; const Color onPrimaryColor = Colors.white;
const double horizontalPadding = 16.0;
final bool showDropdown = widget.projectName == null;
return AppBar( return AppBar(
backgroundColor: effectiveBackgroundColor, backgroundColor: effectiveBackgroundColor,
elevation: 0, elevation: 0,
automaticallyImplyLeading: false, automaticallyImplyLeading: false,
titleSpacing: 0, titleSpacing: 0,
shadowColor: Colors.transparent, shadowColor: Colors.transparent,
leading: Padding( leading: Padding(
padding: MySpacing.only(left: horizontalPadding), padding: MySpacing.only(left: 16),
child: IconButton( child: IconButton(
icon: const Icon( icon: const Icon(
Icons.arrow_back_ios_new, Icons.arrow_back_ios_new,
color: onPrimaryColor, color: onPrimaryColor,
size: 20, size: 20,
), ),
onPressed: onBackPressed ?? () => Get.back(), onPressed: widget.onBackPressed ?? () => Get.back(),
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
constraints: const BoxConstraints(), constraints: const BoxConstraints(),
), ),
), ),
title: Padding( title: Padding(
padding: MySpacing.only(right: horizontalPadding, left: 8), padding: MySpacing.only(right: 16, left: 8),
child: Row( child: Row(
children: [ children: [
Expanded( Expanded(
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
MyText.titleLarge( MyText.titleLarge(
title, widget.title,
fontWeight: 800, fontWeight: 800,
color: onPrimaryColor, color: onPrimaryColor,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
maxLines: 1, maxLines: 1,
), ),
MySpacing.height(3), MySpacing.height(3),
GetBuilder<ProjectController>( showDropdown
builder: (projectController) { ? CompositedTransformTarget(
final displayProjectName = projectName ?? link: _layerLink,
projectController.selectedProject?.name ?? child: GestureDetector(
'Select Project'; onTap: _toggleDropdown,
return Row( child: Row(
children: [ children: [
const Icon(Icons.folder_open, const Icon(Icons.folder_open,
size: 14, color: onPrimaryColor), size: 14, color: onPrimaryColor),
MySpacing.width(4), MySpacing.width(4),
Flexible( Flexible(
child: MyText.bodySmall( child: Obx(() {
displayProjectName, final projectName = projectController
fontWeight: 500, .selectedProject?.name ??
color: onPrimaryColor.withOpacity(0.8), 'Select Project';
overflow: TextOverflow.ellipsis, return MyText.bodySmall(
maxLines: 1, 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),
],
), ),
), ),
MySpacing.width(2), )
const Icon(Icons.keyboard_arrow_down, : Row(
size: 18, color: onPrimaryColor), children: [
], const Icon(Icons.folder_open,
); size: 14, color: onPrimaryColor),
}, MySpacing.width(4),
), Flexible(
child: MyText.bodySmall(
widget.projectName!,
fontWeight: 500,
color: onPrimaryColor.withOpacity(0.8),
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
),
],
),
], ],
), ),
), ),
@ -103,7 +212,7 @@ class CustomAppBar extends StatelessWidget
), ),
actions: [ actions: [
Padding( Padding(
padding: MySpacing.only(right: horizontalPadding), padding: MySpacing.only(right: 16),
child: IconButton( child: IconButton(
icon: const Icon(Icons.home, color: onPrimaryColor, size: 24), icon: const Icon(Icons.home, color: onPrimaryColor, size: 24),
onPressed: () => Get.offAllNamed('/dashboard'), onPressed: () => Get.offAllNamed('/dashboard'),
@ -112,4 +221,10 @@ class CustomAppBar extends StatelessWidget
], ],
); );
} }
@override
void dispose() {
_overlayEntry?.remove();
super.dispose();
}
} }