TF-2666 Apply new design for edit recipient card on web

Signed-off-by: dab246 <tdvu@linagora.com>
This commit is contained in:
dab246
2025-07-24 16:59:15 +07:00
committed by Dat H. Pham
parent 7ca3950c34
commit 6653551609
15 changed files with 420 additions and 87 deletions
@@ -0,0 +1,121 @@
import 'package:flutter/material.dart';
import 'package:pointer_interceptor/pointer_interceptor.dart';
import 'package:tmail_ui_user/features/base/widget/smart_interaction_widget.dart';
class CardWithSmartInteractionOverlayView extends StatefulWidget {
final Widget child;
final double overlayWidth;
final Widget Function(VoidCallback onClose) menuBuilder;
const CardWithSmartInteractionOverlayView({
super.key,
required this.child,
required this.menuBuilder,
this.overlayWidth = 361,
});
@override
State<CardWithSmartInteractionOverlayView> createState() => _CardWithSmartInteractionOverlayViewState();
}
class _CardWithSmartInteractionOverlayViewState
extends State<CardWithSmartInteractionOverlayView>
with SingleTickerProviderStateMixin {
final LayerLink _layerLink = LayerLink();
OverlayEntry? _overlayEntry;
late AnimationController _controller;
bool _isDisposed = false;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 200),
reverseDuration: const Duration(milliseconds: 100),
vsync: this,
);
}
void _showPopup() {
_overlayEntry = OverlayEntry(
builder: (context) => Stack(
children: [
Positioned.fill(
child: PointerInterceptor(
child: GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: _removePopup,
),
),
),
Positioned(
width: widget.overlayWidth,
child: CompositedTransformFollower(
link: _layerLink,
offset: const Offset(0, 40),
showWhenUnlinked: false,
child: FadeTransition(
opacity: CurvedAnimation(
parent: _controller,
curve: Curves.easeOut,
),
child: ScaleTransition(
scale: CurvedAnimation(
parent: _controller,
curve: Curves.easeOutBack,
),
alignment: Alignment.topLeft,
child: widget.menuBuilder(_removePopup),
),
),
),
),
],
),
);
Overlay.maybeOf(context)?.insert(_overlayEntry!);
_controller.forward();
}
Future<void> _removePopup() async {
if (_overlayEntry == null) return;
if (!_isDisposed && _controller.isAnimating == false) {
await _controller.reverse();
}
_overlayEntry?.remove();
_overlayEntry = null;
}
@override
void dispose() {
_isDisposed = true;
_controller.dispose();
_overlayEntry?.remove();
_overlayEntry = null;
super.dispose();
}
@override
Widget build(BuildContext context) {
return CompositedTransformTarget(
link: _layerLink,
child: SmartInteractionWidget(
onRightMouseClickAction: ({RelativeRect? position}) => _togglePopup(),
onDoubleClickAction: ({RelativeRect? position}) => _togglePopup(),
onLongPressAction: _togglePopup,
child: widget.child,
),
);
}
void _togglePopup() {
if (_overlayEntry == null) {
_showPopup();
} else {
_removePopup();
}
}
}
@@ -5,12 +5,13 @@ import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart' hide OverlayEntry;
import 'package:universal_html/html.dart' as html;
typedef OnRightMouseClickAction = void Function(RelativeRect position);
typedef OnDoubleClickAction = void Function(RelativeRect position);
typedef OnRightMouseClickAction = void Function({RelativeRect? position});
typedef OnDoubleClickAction = void Function({RelativeRect? position});
typedef OnLongPressAction = void Function();
class SmartInteractionWidget extends StatefulWidget {
final Widget child;
final bool usePosition;
final OnRightMouseClickAction onRightMouseClickAction;
final OnDoubleClickAction onDoubleClickAction;
final OnLongPressAction onLongPressAction;
@@ -21,6 +22,7 @@ class SmartInteractionWidget extends StatefulWidget {
required this.onRightMouseClickAction,
required this.onDoubleClickAction,
required this.onLongPressAction,
this.usePosition = false,
});
@override
@@ -73,8 +75,12 @@ class _SmartInteractionWidgetState extends State<SmartInteractionWidget> {
if (!mounted) return;
final relativeRect = _getRelativeRectFromOffset(context, localPosition);
widget.onRightMouseClickAction(relativeRect);
if (widget.usePosition) {
final relativeRect = _getRelativeRectFromOffset(context, localPosition);
widget.onRightMouseClickAction(position: relativeRect);
} else {
widget.onRightMouseClickAction();
}
}
}
@@ -113,12 +119,14 @@ class _SmartInteractionWidgetState extends State<SmartInteractionWidget> {
}
void _handleDoubleTap() {
if (_lastPointerEventOffset != null) {
if (widget.usePosition && _lastPointerEventOffset != null) {
final relativeRect = _getRelativeRectFromOffset(
context,
_lastPointerEventOffset!,
);
widget.onDoubleClickAction(relativeRect);
widget.onDoubleClickAction(position: relativeRect);
} else {
widget.onDoubleClickAction();
}
}
@@ -127,19 +135,28 @@ class _SmartInteractionWidgetState extends State<SmartInteractionWidget> {
@override
Widget build(BuildContext context) {
if (PlatformInfo.isWeb) {
return Listener(
key: _childKey,
onPointerDown: (event) {
if (event.kind == PointerDeviceKind.mouse ||
event.kind == PointerDeviceKind.touch) {
_lastPointerEventOffset = event.position;
}
},
child: GestureDetector(
if (widget.usePosition) {
return Listener(
key: _childKey,
onPointerDown: (event) {
if (event.kind == PointerDeviceKind.mouse ||
event.kind == PointerDeviceKind.touch) {
_lastPointerEventOffset = event.position;
}
},
child: GestureDetector(
onDoubleTap: _handleDoubleTap,
child: widget.child,
),
);
} else {
return GestureDetector(
key: _childKey,
behavior: HitTestBehavior.opaque,
onDoubleTap: _handleDoubleTap,
child: widget.child,
),
);
);
}
} else {
return GestureDetector(
onLongPress: _handleLongPress,