💄 Hovering actions

This commit is contained in:
2025-10-08 23:48:06 +08:00
parent 018386d14e
commit 27b390a51c

View File

@@ -160,7 +160,12 @@ class MessageItem extends HookConsumerWidget {
? Theme.of(context).colorScheme.primaryContainer.withOpacity(0.8) ? Theme.of(context).colorScheme.primaryContainer.withOpacity(0.8)
: Colors.transparent; : Colors.transparent;
return InkWell( final isHovered = useState(false);
return Stack(
clipBehavior: Clip.none,
children: [
InkWell(
mouseCursor: MouseCursor.defer, mouseCursor: MouseCursor.defer,
focusColor: Colors.transparent, focusColor: Colors.transparent,
onLongPress: showActionMenu, onLongPress: showActionMenu,
@@ -173,6 +178,11 @@ class MessageItem extends HookConsumerWidget {
onJump(message.meta['message_id']); onJump(message.meta['message_id']);
} }
}, },
child: Container(
width: double.infinity,
child: MouseRegion(
onEnter: (_) => isHovered.value = true,
onExit: (_) => isHovered.value = false,
child: AnimatedContainer( child: AnimatedContainer(
curve: Curves.easeInOut, curve: Curves.easeInOut,
duration: const Duration(milliseconds: kFlashDuration), duration: const Duration(milliseconds: kFlashDuration),
@@ -207,6 +217,28 @@ class MessageItem extends HookConsumerWidget {
), ),
}, },
), ),
),
),
),
if (isHovered.value && !isMobile)
Positioned(
top: -15,
right: 15,
child: MouseRegion(
onEnter: (_) => isHovered.value = true,
onExit: (_) => isHovered.value = false,
child: MessageHoverActionMenu(
isCurrentUser: isCurrentUser,
onAction: onAction,
translatableLanguage: translatableLanguage,
translating: translating.value,
translatedText: translatedText.value,
translate: translate,
remoteMessage: remoteMessage,
),
),
),
],
); );
} }
} }
@@ -317,6 +349,88 @@ class MessageActionSheet extends StatelessWidget {
} }
} }
class MessageHoverActionMenu extends StatelessWidget {
final bool isCurrentUser;
final Function(String action)? onAction;
final bool translatableLanguage;
final bool translating;
final String? translatedText;
final VoidCallback translate;
final dynamic remoteMessage;
const MessageHoverActionMenu({
super.key,
required this.isCurrentUser,
required this.onAction,
required this.translatableLanguage,
required this.translating,
required this.translatedText,
required this.translate,
required this.remoteMessage,
});
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainerHighest,
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 4,
offset: const Offset(0, 2),
),
],
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (isCurrentUser)
IconButton(
icon: Icon(Symbols.edit, size: 16),
onPressed: () => onAction?.call(MessageItemAction.edit),
tooltip: 'edit'.tr(),
padding: const EdgeInsets.all(8),
constraints: const BoxConstraints(minWidth: 32, minHeight: 32),
),
if (isCurrentUser)
IconButton(
icon: Icon(Symbols.delete, size: 16),
onPressed: () => onAction?.call(MessageItemAction.delete),
tooltip: 'delete'.tr(),
padding: const EdgeInsets.all(8),
constraints: const BoxConstraints(minWidth: 32, minHeight: 32),
),
IconButton(
icon: Icon(Symbols.reply, size: 16),
onPressed: () => onAction?.call(MessageItemAction.reply),
tooltip: 'reply'.tr(),
padding: const EdgeInsets.all(8),
constraints: const BoxConstraints(minWidth: 32, minHeight: 32),
),
IconButton(
icon: Icon(Symbols.forward, size: 16),
onPressed: () => onAction?.call(MessageItemAction.forward),
tooltip: 'forward'.tr(),
padding: const EdgeInsets.all(8),
constraints: const BoxConstraints(minWidth: 32, minHeight: 32),
),
if (translatableLanguage)
IconButton(
icon: Icon(Symbols.translate, size: 16),
onPressed: translate,
tooltip:
translatedText == null ? 'translate'.tr() : 'translated'.tr(),
padding: const EdgeInsets.all(8),
constraints: const BoxConstraints(minWidth: 32, minHeight: 32),
),
],
),
);
}
}
class MessageItemDisplayBubble extends HookConsumerWidget { class MessageItemDisplayBubble extends HookConsumerWidget {
final LocalChatMessage message; final LocalChatMessage message;
final bool isCurrentUser; final bool isCurrentUser;