feat: pass button BuildContext to conversation menu to position it
This commit is contained in:
@@ -1254,9 +1254,9 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
|
|||||||
? null
|
? null
|
||||||
: () => _selectConversation(context, conv.id),
|
: () => _selectConversation(context, conv.id),
|
||||||
onLongPress: null,
|
onLongPress: null,
|
||||||
onMorePressed: () {
|
onMorePressed: (buttonContext) {
|
||||||
showConversationContextMenu(
|
showConversationContextMenu(
|
||||||
context: context,
|
context: buttonContext,
|
||||||
ref: ref,
|
ref: ref,
|
||||||
conversation: conv,
|
conversation: conv,
|
||||||
);
|
);
|
||||||
@@ -1609,7 +1609,7 @@ class _ConversationTileContent extends StatelessWidget {
|
|||||||
final bool pinned;
|
final bool pinned;
|
||||||
final bool selected;
|
final bool selected;
|
||||||
final bool isLoading;
|
final bool isLoading;
|
||||||
final VoidCallback? onMorePressed;
|
final void Function(BuildContext)? onMorePressed;
|
||||||
final Widget? leading;
|
final Widget? leading;
|
||||||
|
|
||||||
const _ConversationTileContent({
|
const _ConversationTileContent({
|
||||||
@@ -1664,22 +1664,29 @@ class _ConversationTileContent extends StatelessWidget {
|
|||||||
} else if (onMorePressed != null) {
|
} else if (onMorePressed != null) {
|
||||||
trailing.addAll([
|
trailing.addAll([
|
||||||
const SizedBox(width: Spacing.sm),
|
const SizedBox(width: Spacing.sm),
|
||||||
IconButton(
|
Builder(
|
||||||
iconSize: IconSize.sm,
|
builder: (buttonContext) {
|
||||||
visualDensity: const VisualDensity(horizontal: -2, vertical: -2),
|
return IconButton(
|
||||||
padding: EdgeInsets.zero,
|
iconSize: IconSize.sm,
|
||||||
constraints: const BoxConstraints(
|
visualDensity: const VisualDensity(
|
||||||
minWidth: TouchTarget.listItem,
|
horizontal: -2,
|
||||||
minHeight: TouchTarget.listItem,
|
vertical: -2,
|
||||||
),
|
),
|
||||||
icon: Icon(
|
padding: EdgeInsets.zero,
|
||||||
Platform.isIOS
|
constraints: const BoxConstraints(
|
||||||
? CupertinoIcons.ellipsis
|
minWidth: TouchTarget.listItem,
|
||||||
: Icons.more_vert_rounded,
|
minHeight: TouchTarget.listItem,
|
||||||
color: theme.iconSecondary,
|
),
|
||||||
),
|
icon: Icon(
|
||||||
onPressed: onMorePressed,
|
Platform.isIOS
|
||||||
tooltip: AppLocalizations.of(context)!.more,
|
? CupertinoIcons.ellipsis
|
||||||
|
: Icons.more_vert_rounded,
|
||||||
|
color: theme.iconSecondary,
|
||||||
|
),
|
||||||
|
onPressed: () => onMorePressed!(buttonContext),
|
||||||
|
tooltip: AppLocalizations.of(context)!.more,
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -1720,7 +1727,7 @@ class _ConversationTile extends StatelessWidget {
|
|||||||
final Widget? leading;
|
final Widget? leading;
|
||||||
final VoidCallback? onTap;
|
final VoidCallback? onTap;
|
||||||
final VoidCallback? onLongPress;
|
final VoidCallback? onLongPress;
|
||||||
final VoidCallback? onMorePressed;
|
final void Function(BuildContext)? onMorePressed;
|
||||||
|
|
||||||
const _ConversationTile({
|
const _ConversationTile({
|
||||||
required this.title,
|
required this.title,
|
||||||
|
|||||||
@@ -3,9 +3,6 @@ import 'dart:io' show Platform;
|
|||||||
import 'package:conduit/core/providers/app_providers.dart';
|
import 'package:conduit/core/providers/app_providers.dart';
|
||||||
import 'package:conduit/l10n/app_localizations.dart';
|
import 'package:conduit/l10n/app_localizations.dart';
|
||||||
import 'package:conduit/shared/theme/theme_extensions.dart';
|
import 'package:conduit/shared/theme/theme_extensions.dart';
|
||||||
import 'package:conduit/shared/widgets/conduit_components.dart';
|
|
||||||
import 'package:conduit/shared/widgets/modal_safe_area.dart';
|
|
||||||
import 'package:conduit/shared/widgets/sheet_handle.dart';
|
|
||||||
import 'package:conduit/shared/widgets/themed_dialogs.dart';
|
import 'package:conduit/shared/widgets/themed_dialogs.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -35,74 +32,76 @@ class ConduitContextMenuAction {
|
|||||||
Future<void> showConduitContextMenu({
|
Future<void> showConduitContextMenu({
|
||||||
required BuildContext context,
|
required BuildContext context,
|
||||||
required List<ConduitContextMenuAction> actions,
|
required List<ConduitContextMenuAction> actions,
|
||||||
|
Offset? position,
|
||||||
}) async {
|
}) async {
|
||||||
if (actions.isEmpty) return;
|
if (actions.isEmpty) return;
|
||||||
|
|
||||||
final theme = context.conduitTheme;
|
final theme = context.conduitTheme;
|
||||||
|
final RenderBox? overlay =
|
||||||
|
Overlay.of(context).context.findRenderObject() as RenderBox?;
|
||||||
|
|
||||||
await showModalBottomSheet(
|
if (overlay == null) return;
|
||||||
|
|
||||||
|
// Determine menu position
|
||||||
|
final Offset menuPosition = position ?? _getDefaultMenuPosition(context);
|
||||||
|
|
||||||
|
final result = await showMenu<ConduitContextMenuAction>(
|
||||||
context: context,
|
context: context,
|
||||||
backgroundColor: Colors.transparent,
|
position: RelativeRect.fromLTRB(
|
||||||
builder: (sheetContext) {
|
menuPosition.dx,
|
||||||
Future<void> handleAction(ConduitContextMenuAction action) async {
|
menuPosition.dy,
|
||||||
action.onBeforeClose?.call();
|
overlay.size.width - menuPosition.dx,
|
||||||
Navigator.of(sheetContext).pop();
|
overlay.size.height - menuPosition.dy,
|
||||||
await Future.microtask(action.onSelected);
|
),
|
||||||
}
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(AppBorderRadius.md),
|
||||||
List<Widget> buildActionTiles() {
|
),
|
||||||
return actions
|
color: theme.surfaceBackground,
|
||||||
.map(
|
elevation: 8,
|
||||||
(action) => ConduitListItem(
|
items: actions.map((action) {
|
||||||
isCompact: true,
|
return PopupMenuItem<ConduitContextMenuAction>(
|
||||||
leading: Icon(
|
value: action,
|
||||||
Platform.isIOS ? action.cupertinoIcon : action.materialIcon,
|
|
||||||
color: action.destructive ? Colors.red : theme.iconPrimary,
|
|
||||||
size: IconSize.modal,
|
|
||||||
),
|
|
||||||
title: Text(
|
|
||||||
action.label,
|
|
||||||
style: AppTypography.standard.copyWith(
|
|
||||||
color: action.destructive ? Colors.red : theme.textPrimary,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
onTap: () => handleAction(action),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
final actionTiles = buildActionTiles();
|
|
||||||
|
|
||||||
return ModalSheetSafeArea(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal: Spacing.screenPadding,
|
horizontal: Spacing.md,
|
||||||
vertical: Spacing.screenPadding,
|
vertical: Spacing.xs,
|
||||||
),
|
),
|
||||||
child: Container(
|
child: Row(
|
||||||
decoration: BoxDecoration(
|
children: [
|
||||||
color: theme.surfaceBackground,
|
Icon(
|
||||||
borderRadius: BorderRadius.circular(AppBorderRadius.lg),
|
Platform.isIOS ? action.cupertinoIcon : action.materialIcon,
|
||||||
boxShadow: ConduitShadows.modal(context),
|
color: action.destructive ? Colors.red : theme.iconPrimary,
|
||||||
),
|
size: IconSize.sm,
|
||||||
child: Column(
|
),
|
||||||
mainAxisSize: MainAxisSize.min,
|
const SizedBox(width: Spacing.md),
|
||||||
children: [
|
Expanded(
|
||||||
const SizedBox(height: Spacing.sm),
|
child: Text(
|
||||||
const SheetHandle(),
|
action.label,
|
||||||
const SizedBox(height: Spacing.sm),
|
style: AppTypography.standard.copyWith(
|
||||||
for (var i = 0; i < actionTiles.length; i++) ...[
|
color: action.destructive ? Colors.red : theme.textPrimary,
|
||||||
if (i != 0) const ConduitDivider(isCompact: true),
|
fontWeight: FontWeight.w500,
|
||||||
actionTiles[i],
|
),
|
||||||
],
|
),
|
||||||
const SizedBox(height: Spacing.sm),
|
),
|
||||||
],
|
],
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
}).toList(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (result != null) {
|
||||||
|
result.onBeforeClose?.call();
|
||||||
|
await Future.microtask(result.onSelected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Offset _getDefaultMenuPosition(BuildContext context) {
|
||||||
|
final RenderBox? renderBox = context.findRenderObject() as RenderBox?;
|
||||||
|
if (renderBox == null) {
|
||||||
|
return Offset.zero;
|
||||||
|
}
|
||||||
|
final position = renderBox.localToGlobal(Offset.zero);
|
||||||
|
final size = renderBox.size;
|
||||||
|
return Offset(position.dx + size.width, position.dy);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> showConversationContextMenu({
|
Future<void> showConversationContextMenu({
|
||||||
|
|||||||
Reference in New Issue
Block a user