feat: 改进聊天弹窗和遮罩层交互,优化状态管理和绘制逻辑
This commit is contained in:
@@ -26,9 +26,9 @@ class ChatPopup extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _ChatPopupState extends State<ChatPopup> with SingleTickerProviderStateMixin {
|
||||
OverlayEntry? _overlayEntry;
|
||||
Offset _iconPosition = const Offset(10, 120);
|
||||
final double _iconSizeDefault = 80.0;
|
||||
bool _isShowingPopup = false;
|
||||
late AnimationController _partScreenAnimationController;
|
||||
|
||||
@override
|
||||
@@ -42,71 +42,47 @@ class _ChatPopupState extends State<ChatPopup> with SingleTickerProviderStateMix
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_removeOverlay();
|
||||
_partScreenAnimationController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _openFullScreen() async {
|
||||
final messageService = context.read<MessageService>();
|
||||
_removeOverlay();
|
||||
|
||||
await Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => ChangeNotifierProvider.value(
|
||||
value: messageService, // 传递同一个单例实例
|
||||
child: const FullScreenPage(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _insertOverlay() {
|
||||
if (_overlayEntry != null) return;
|
||||
_overlayEntry = OverlayEntry(
|
||||
builder: (context) => _buildOverlayContent(),
|
||||
);
|
||||
Overlay.of(context).insert(_overlayEntry!);
|
||||
if (_isShowingPopup) return;
|
||||
setState(() {
|
||||
_isShowingPopup = true;
|
||||
});
|
||||
_partScreenAnimationController.forward();
|
||||
}
|
||||
|
||||
void _removeOverlay() {
|
||||
if (_overlayEntry == null) return;
|
||||
if (!_isShowingPopup) return;
|
||||
_partScreenAnimationController.reverse().then((_) {
|
||||
_overlayEntry?.remove();
|
||||
_overlayEntry = null;
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isShowingPopup = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Widget _buildOverlayContent() {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ChangeNotifierProvider.value(
|
||||
value: MessageService.instance,
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final position = _calculatePosition(constraints);
|
||||
final double minHeight = constraints.maxHeight * 0.16;
|
||||
final double maxHeight = constraints.maxHeight * 0.4;
|
||||
final double chatWidth = constraints.maxWidth * 0.94;
|
||||
return Stack(
|
||||
children: [
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
return Stack(
|
||||
children: [
|
||||
// 1. 底层:弹窗内容(只有在显示时才渲染)
|
||||
if (_isShowingPopup) ...[
|
||||
_buildPopupBackground(),
|
||||
Positioned(
|
||||
left: position.left,
|
||||
right: position.right,
|
||||
bottom: position.bottom,
|
||||
child: ChatWindowContent(
|
||||
animationController: _partScreenAnimationController,
|
||||
minHeight: minHeight,
|
||||
maxHeight: maxHeight,
|
||||
chatWidth: chatWidth,
|
||||
),
|
||||
),
|
||||
_buildPopupContent(constraints),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
// 2. 顶层:FloatingIcon(始终在最上层,事件不被遮挡)
|
||||
_buildFloatingIcon(),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -120,23 +96,125 @@ class _ChatPopupState extends State<ChatPopup> with SingleTickerProviderStateMix
|
||||
messageService.abortReply();
|
||||
messageService.initializeEmpty();
|
||||
},
|
||||
child: Container(
|
||||
color: Colors.black.withOpacity(0.1),
|
||||
),
|
||||
child: SizedBox.expand()
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPopupContent(BoxConstraints constraints) {
|
||||
final position = _calculatePosition(constraints);
|
||||
final double minHeight = constraints.maxHeight * 0.16;
|
||||
final double maxHeight = constraints.maxHeight * 0.4;
|
||||
final double chatWidth = constraints.maxWidth * 0.94;
|
||||
|
||||
return Positioned(
|
||||
left: position.left,
|
||||
right: position.right,
|
||||
bottom: position.bottom,
|
||||
child: ChatWindowContent(
|
||||
animationController: _partScreenAnimationController,
|
||||
minHeight: minHeight,
|
||||
maxHeight: maxHeight,
|
||||
chatWidth: chatWidth,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFloatingIcon() {
|
||||
// 如果外部传入了 child,优先使用它的属性
|
||||
if (widget.child != null) {
|
||||
return ChatFloatingIcon(
|
||||
iconSize: widget.child!.iconSize ?? widget.iconSize ?? _iconSizeDefault,
|
||||
icon: widget.child!.icon ?? widget.icon,
|
||||
onTap: () async {
|
||||
debugPrint('🖱️ FloatingIcon onTap triggered! (using child properties)');
|
||||
// 先执行外部传入的 onTap(如果有)
|
||||
if (widget.child!.onTap != null) {
|
||||
widget.child!.onTap!();
|
||||
} else {
|
||||
// 默认行为:关闭弹窗并打开全屏
|
||||
final messageService = context.read<MessageService>();
|
||||
_removeOverlay();
|
||||
|
||||
await Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => ChangeNotifierProvider.value(
|
||||
value: messageService,
|
||||
child: const FullScreenPage(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
onLongPress: () async {
|
||||
debugPrint('⏳ FloatingIcon onLongPress triggered! (using child properties)');
|
||||
// 先执行外部传入的 onLongPress(如果有)
|
||||
if (widget.child!.onLongPress != null) {
|
||||
widget.child!.onLongPress!();
|
||||
}
|
||||
// 执行内部业务逻辑
|
||||
final messageService = MessageService.instance;
|
||||
_insertOverlay();
|
||||
await messageService.startVoiceInput();
|
||||
},
|
||||
onLongPressUp: () async {
|
||||
debugPrint('⏫ FloatingIcon onLongPressUp triggered! (using child properties)');
|
||||
// 先执行外部传入的 onLongPressUp(如果有)
|
||||
if (widget.child!.onLongPressUp != null) {
|
||||
widget.child!.onLongPressUp!();
|
||||
}
|
||||
// 执行内部业务逻辑
|
||||
final messageService = MessageService.instance;
|
||||
await messageService.stopAndProcessVoiceInput();
|
||||
final hasMessage = messageService.messages.isNotEmpty;
|
||||
if (!hasMessage) {
|
||||
_removeOverlay();
|
||||
}
|
||||
},
|
||||
onPositionChanged: (position) {
|
||||
debugPrint('📍 FloatingIcon position changed: $position (using child properties)');
|
||||
// 先执行外部传入的 onPositionChanged(如果有)
|
||||
if (widget.child!.onPositionChanged != null) {
|
||||
widget.child!.onPositionChanged!(position);
|
||||
}
|
||||
// 执行内部业务逻辑
|
||||
setState(() {
|
||||
_iconPosition = position;
|
||||
});
|
||||
},
|
||||
// 传递其他可能的属性
|
||||
onLongPressEnd: widget.child!.onLongPressEnd,
|
||||
onDragStart: widget.child!.onDragStart,
|
||||
onDragEnd: widget.child!.onDragEnd,
|
||||
);
|
||||
}
|
||||
|
||||
// 如果没有传入 child,使用原来的逻辑
|
||||
return ChatFloatingIcon(
|
||||
iconSize: widget.iconSize ?? _iconSizeDefault,
|
||||
icon: widget.icon,
|
||||
onTap: () async {
|
||||
debugPrint('🖱️ FloatingIcon onTap triggered!');
|
||||
final messageService = context.read<MessageService>();
|
||||
_removeOverlay();
|
||||
|
||||
await Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => ChangeNotifierProvider.value(
|
||||
value: messageService, // 传递同一个单例实例
|
||||
child: const FullScreenPage(),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
onLongPress: () async {
|
||||
debugPrint('⏳ FloatingIcon onLongPress triggered!');
|
||||
final messageService = MessageService.instance;
|
||||
_insertOverlay();
|
||||
await messageService.startVoiceInput();
|
||||
},
|
||||
onLongPressUp: () async {
|
||||
debugPrint('⏫ FloatingIcon onLongPressUp triggered!');
|
||||
final messageService = MessageService.instance;
|
||||
await messageService.stopAndProcessVoiceInput();
|
||||
final hasMessage = messageService.messages.isNotEmpty;
|
||||
@@ -145,11 +223,10 @@ class _ChatPopupState extends State<ChatPopup> with SingleTickerProviderStateMix
|
||||
}
|
||||
},
|
||||
onPositionChanged: (position) {
|
||||
debugPrint('📍 FloatingIcon position changed: $position');
|
||||
setState(() {
|
||||
_iconPosition = position;
|
||||
});
|
||||
// 重新构建 overlay 以更新弹窗位置
|
||||
_overlayEntry?.markNeedsBuild();
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -199,10 +276,4 @@ class _ChatPopupState extends State<ChatPopup> with SingleTickerProviderStateMix
|
||||
bottom: chatBottom,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// 只渲染 FloatingIcon,Overlay 弹窗内容在长按时插入
|
||||
return _buildFloatingIcon();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user