import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:go_router/go_router.dart';
import '../../../../app_core/utils/app_style.dart';
class OverlayAction {
final String title;
final Function() onTap;
OverlayAction({required this.title, required this.onTap});
}
class OverlayPortalWidget extends StatefulWidget {
final Widget child;
final double offsetY;
final List<OverlayAction> actions;
const OverlayPortalWidget({
super.key,
required this.child,
this.offsetY = 8.0,
required this.actions,
});
@override
State<OverlayPortalWidget> createState() => _OverlayPortalWidgetState();
}
class _OverlayPortalWidgetState extends State<OverlayPortalWidget>
with SingleTickerProviderStateMixin {
final OverlayPortalController _tooltipController = OverlayPortalController();
late AnimationController _animationController;
late Animation<double> _fadeAnimation;
final GlobalKey _childKey = GlobalKey();
final GlobalKey _tooltipKey = GlobalKey();
Offset _overlayPosition = Offset.zero;
bool _showAbove = false;
late VoidCallback _routerListener;
@override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 200),
reverseDuration: const Duration(milliseconds: 150),
);
_fadeAnimation = CurvedAnimation(
parent: _animationController,
curve: Curves.easeOut,
reverseCurve: Curves.easeIn,
);
// Добавляем слушатель маршрута в GoRouter
_routerListener = () {
if (_tooltipController.isShowing) {
_tooltipController.hide();
}
};
GoRouter.of(context).routerDelegate.addListener(_routerListener);
}
@override
void dispose() {
_tooltipController.hide(); // Гарантированное закрытие тултипа
_animationController.dispose();
GoRouter.of(context).routerDelegate.removeListener(_routerListener); // Удаление слушателя
super.dispose();
}
void _toggleOverlay() {
if (_tooltipController.isShowing) {
_animationController.reverse().then((_) => _tooltipController.hide());
} else {
_calculateOverlayPosition();
_tooltipController.show();
_animationController.forward();
}
}
/// Вычисляем позицию тултипа, чтобы он не выходил за границы экрана
void _calculateOverlayPosition() {
final RenderBox renderBox =
_childKey.currentContext?.findRenderObject() as RenderBox;
final Offset localOffset = renderBox.localToGlobal(Offset.zero);
final Size screenSize = MediaQuery.of(context).size;
WidgetsBinding.instance.addPostFrameCallback((_) {
final RenderBox? tooltipBox =
_tooltipKey.currentContext?.findRenderObject() as RenderBox?;
final double tooltipHeight = tooltipBox?.size.height ?? 50.h;
final double tooltipWidth = tooltipBox?.size.width ?? 200.w;
double newX = localOffset.dx;
double newY = localOffset.dy + renderBox.size.height + widget.offsetY;
// Проверка на выход за правый край экрана
if (newX + tooltipWidth > screenSize.width) {
newX = screenSize.width - tooltipWidth - 30;
}
// Проверка на выход за левый край экрана
if (newX < 0) {
newX = 8; // Минимальный отступ от экрана
}
// Если тултип выходит за нижний край — показываем сверху
bool showAbove = false;
if (newY + tooltipHeight > screenSize.height) {
newY = localOffset.dy - tooltipHeight - widget.offsetY;
showAbove = true;
}
setState(() {
_overlayPosition = Offset(newX, newY);
_showAbove = showAbove;
});
});
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: _toggleOverlay,
child: OverlayPortal(
controller: _tooltipController,
overlayChildBuilder: (context) {
return Positioned(
left: _overlayPosition.dx,
top: _overlayPosition.dy,
child: FadeTransition(
opacity: _fadeAnimation,
child: Material(
key: _tooltipKey,
color: Colors.transparent,
child: _listActions(),
),
),
);
},
child: Container(
key: _childKey,
child: AbsorbPointer(child: widget.child),
),
),
);
}
Widget _listActions() {
return Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12.r),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 5,
spreadRadius: 2,
),
],
),
clipBehavior: Clip.antiAlias,
child: Column(
children: widget.actions.map(_action).toList(),
),
);
}
Widget _action(OverlayAction action) {
return GestureDetector(
onTap: () {
_tooltipController.hide();
action.onTap.call();
},
child: Container(
width: 150.w,
color: Colors.white,
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 10.h),
child: Row(
children: [
FittedBox(
child: Text(
action.title.tr(),
maxLines: 1,
textAlign: TextAlign.start,
style: Theme.of(context).textTheme.titleSmall?.copyWith(
color: AppStyle.dark,
fontSize: 12.sp,
),
),
),
],
),
),
);
}
}