feat(ui): Refactor context menu with platform-specific styling
feat(navigation): migrate to super_drag_and_drop for folder drag and drop feat(ui): Add context menu preview builders for chat and notes refactor(ui): Remove preview builders and simplify note card rendering
This commit is contained in:
@@ -709,39 +709,50 @@ class _NoteEditorPageState extends ConsumerState<NoteEditorPage> {
|
||||
),
|
||||
),
|
||||
),
|
||||
// Actions (more menu)
|
||||
// Actions (more menu) - uses PopupMenuButton for tap interaction
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: Spacing.inputPadding),
|
||||
child: Center(
|
||||
child: PopupMenuButton<String>(
|
||||
tooltip: '',
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(AppBorderRadius.md),
|
||||
),
|
||||
onSelected: (value) {
|
||||
switch (value) {
|
||||
case 'generate_title':
|
||||
case 'generate':
|
||||
HapticFeedback.selectionClick();
|
||||
_generateTitle();
|
||||
case 'copy':
|
||||
HapticFeedback.selectionClick();
|
||||
_copyToClipboard();
|
||||
case 'delete':
|
||||
HapticFeedback.mediumImpact();
|
||||
_deleteNote();
|
||||
}
|
||||
},
|
||||
offset: const Offset(0, 44),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(
|
||||
AppBorderRadius.card,
|
||||
),
|
||||
),
|
||||
color: conduitTheme.surfaceContainer,
|
||||
itemBuilder: (context) => [
|
||||
PopupMenuItem(
|
||||
value: 'generate_title',
|
||||
value: 'generate',
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Platform.isIOS
|
||||
? CupertinoIcons.sparkles
|
||||
: Icons.auto_awesome_rounded,
|
||||
color: conduitTheme.buttonPrimary,
|
||||
size: IconSize.md,
|
||||
size: IconSize.small,
|
||||
color: conduitTheme.textPrimary,
|
||||
),
|
||||
const SizedBox(width: Spacing.sm),
|
||||
Text(l10n.generateTitle),
|
||||
Text(
|
||||
l10n.generateTitle,
|
||||
style: TextStyle(
|
||||
color: conduitTheme.textPrimary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -753,11 +764,16 @@ class _NoteEditorPageState extends ConsumerState<NoteEditorPage> {
|
||||
Platform.isIOS
|
||||
? CupertinoIcons.doc_on_clipboard
|
||||
: Icons.copy_rounded,
|
||||
color: conduitTheme.iconPrimary,
|
||||
size: IconSize.md,
|
||||
size: IconSize.small,
|
||||
color: conduitTheme.textPrimary,
|
||||
),
|
||||
const SizedBox(width: Spacing.sm),
|
||||
Text(l10n.copy),
|
||||
Text(
|
||||
l10n.copy,
|
||||
style: TextStyle(
|
||||
color: conduitTheme.textPrimary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -769,13 +785,15 @@ class _NoteEditorPageState extends ConsumerState<NoteEditorPage> {
|
||||
Platform.isIOS
|
||||
? CupertinoIcons.delete
|
||||
: Icons.delete_rounded,
|
||||
size: IconSize.small,
|
||||
color: conduitTheme.error,
|
||||
size: IconSize.md,
|
||||
),
|
||||
const SizedBox(width: Spacing.sm),
|
||||
Text(
|
||||
l10n.delete,
|
||||
style: TextStyle(color: conduitTheme.error),
|
||||
style: TextStyle(
|
||||
color: conduitTheme.error,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -823,17 +841,13 @@ class _NoteEditorPageState extends ConsumerState<NoteEditorPage> {
|
||||
}
|
||||
|
||||
Widget _buildFloatingMetadataBar(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final conduitTheme = context.conduitTheme;
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final isDark = theme.brightness == Brightness.dark;
|
||||
|
||||
final backgroundColor = isDark
|
||||
? Color.lerp(conduitTheme.cardBackground, Colors.white, 0.08)!
|
||||
: Color.lerp(conduitTheme.inputBackground, Colors.black, 0.06)!;
|
||||
|
||||
final borderColor = conduitTheme.cardBorder.withValues(
|
||||
alpha: isDark ? 0.65 : 0.55,
|
||||
// Use consistent colors with the floating app bar pills
|
||||
final backgroundColor = conduitTheme.surfaceContainer.withValues(alpha: 0.9);
|
||||
final borderColor = conduitTheme.surfaceContainerHighest.withValues(
|
||||
alpha: 0.4,
|
||||
);
|
||||
|
||||
final dateFormat = DateFormat.MMMd();
|
||||
@@ -898,7 +912,7 @@ class _NoteEditorPageState extends ConsumerState<NoteEditorPage> {
|
||||
child: Text(
|
||||
'·',
|
||||
style: AppTypography.tiny.copyWith(
|
||||
color: theme.textTertiary.withValues(alpha: 0.5),
|
||||
color: theme.textSecondary.withValues(alpha: 0.5),
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -921,14 +935,14 @@ class _NoteEditorPageState extends ConsumerState<NoteEditorPage> {
|
||||
children: [
|
||||
Icon(
|
||||
icon,
|
||||
color: theme.textTertiary.withValues(alpha: 0.7),
|
||||
color: theme.textSecondary,
|
||||
size: IconSize.xs,
|
||||
),
|
||||
const SizedBox(width: Spacing.xxs),
|
||||
Text(
|
||||
label,
|
||||
style: AppTypography.tiny.copyWith(
|
||||
color: theme.textTertiary.withValues(alpha: 0.7),
|
||||
color: theme.textSecondary,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -414,33 +414,40 @@ class _NotesListPageState extends ConsumerState<NotesListPage> {
|
||||
return Colors.transparent;
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: Spacing.sm),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: sidebarTheme.accent.withValues(alpha: 0.5),
|
||||
borderRadius: BorderRadius.circular(AppBorderRadius.card),
|
||||
border: Border.all(
|
||||
color: sidebarTheme.border.withValues(alpha: 0.15),
|
||||
width: BorderWidth.thin,
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.04),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.02),
|
||||
blurRadius: 4,
|
||||
offset: const Offset(0, 1),
|
||||
),
|
||||
],
|
||||
),
|
||||
// Compute opaque background for proper context menu snapshot rendering
|
||||
final cardBackground = Color.alphaBlend(
|
||||
sidebarTheme.accent.withValues(alpha: 0.5),
|
||||
sidebarTheme.background,
|
||||
);
|
||||
|
||||
return ConduitContextMenu(
|
||||
actions: _buildNoteActions(context, note),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(bottom: Spacing.sm),
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
color: cardBackground,
|
||||
borderRadius: BorderRadius.circular(AppBorderRadius.card),
|
||||
child: InkWell(
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(AppBorderRadius.card),
|
||||
border: Border.all(
|
||||
color: sidebarTheme.border.withValues(alpha: 0.15),
|
||||
width: BorderWidth.thin,
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.04),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.02),
|
||||
blurRadius: 4,
|
||||
offset: const Offset(0, 1),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(AppBorderRadius.card),
|
||||
overlayColor: WidgetStateProperty.resolveWith(overlayForStates),
|
||||
onTap: () {
|
||||
@@ -450,7 +457,7 @@ class _NotesListPageState extends ConsumerState<NotesListPage> {
|
||||
pathParameters: {'id': note.id},
|
||||
);
|
||||
},
|
||||
onLongPress: () => _showNoteContextMenu(context, note),
|
||||
onLongPress: null, // Handled by ConduitContextMenu
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(Spacing.md),
|
||||
child: Row(
|
||||
@@ -558,32 +565,13 @@ class _NotesListPageState extends ConsumerState<NotesListPage> {
|
||||
],
|
||||
),
|
||||
),
|
||||
// More button
|
||||
Builder(
|
||||
builder: (buttonContext) => IconButton(
|
||||
icon: Icon(
|
||||
Platform.isIOS
|
||||
? CupertinoIcons.ellipsis
|
||||
: Icons.more_vert_rounded,
|
||||
color: sidebarTheme.foreground.withValues(alpha: 0.5),
|
||||
size: IconSize.md,
|
||||
),
|
||||
visualDensity: VisualDensity.compact,
|
||||
padding: EdgeInsets.zero,
|
||||
constraints: const BoxConstraints(
|
||||
minWidth: TouchTarget.badge,
|
||||
minHeight: TouchTarget.badge,
|
||||
),
|
||||
onPressed: () =>
|
||||
_showNoteContextMenu(buttonContext, note),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -594,51 +582,51 @@ class _NotesListPageState extends ConsumerState<NotesListPage> {
|
||||
date.day == now.day;
|
||||
}
|
||||
|
||||
void _showNoteContextMenu(BuildContext context, Note note) {
|
||||
List<ConduitContextMenuAction> _buildNoteActions(
|
||||
BuildContext context,
|
||||
Note note,
|
||||
) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
|
||||
showConduitContextMenu(
|
||||
context: context,
|
||||
actions: [
|
||||
ConduitContextMenuAction(
|
||||
cupertinoIcon: CupertinoIcons.pencil,
|
||||
materialIcon: Icons.edit_rounded,
|
||||
label: l10n.edit,
|
||||
onBeforeClose: () => HapticFeedback.selectionClick(),
|
||||
onSelected: () async {
|
||||
context.pushNamed(
|
||||
RouteNames.noteEditor,
|
||||
pathParameters: {'id': note.id},
|
||||
);
|
||||
},
|
||||
),
|
||||
ConduitContextMenuAction(
|
||||
cupertinoIcon: CupertinoIcons.doc_on_clipboard,
|
||||
materialIcon: Icons.copy_rounded,
|
||||
label: l10n.copy,
|
||||
onBeforeClose: () => HapticFeedback.selectionClick(),
|
||||
onSelected: () async {
|
||||
final messenger = ScaffoldMessenger.of(context);
|
||||
await Clipboard.setData(ClipboardData(text: note.markdownContent));
|
||||
if (!mounted) return;
|
||||
messenger.showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(l10n.noteCopiedToClipboard),
|
||||
duration: const Duration(seconds: 2),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
ConduitContextMenuAction(
|
||||
cupertinoIcon: CupertinoIcons.delete,
|
||||
materialIcon: Icons.delete_rounded,
|
||||
label: l10n.delete,
|
||||
destructive: true,
|
||||
onBeforeClose: () => HapticFeedback.mediumImpact(),
|
||||
onSelected: () async => _deleteNote(note),
|
||||
),
|
||||
],
|
||||
);
|
||||
return [
|
||||
ConduitContextMenuAction(
|
||||
cupertinoIcon: CupertinoIcons.pencil,
|
||||
materialIcon: Icons.edit_rounded,
|
||||
label: l10n.edit,
|
||||
onBeforeClose: () => HapticFeedback.selectionClick(),
|
||||
onSelected: () async {
|
||||
context.pushNamed(
|
||||
RouteNames.noteEditor,
|
||||
pathParameters: {'id': note.id},
|
||||
);
|
||||
},
|
||||
),
|
||||
ConduitContextMenuAction(
|
||||
cupertinoIcon: CupertinoIcons.doc_on_clipboard,
|
||||
materialIcon: Icons.copy_rounded,
|
||||
label: l10n.copy,
|
||||
onBeforeClose: () => HapticFeedback.selectionClick(),
|
||||
onSelected: () async {
|
||||
final messenger = ScaffoldMessenger.of(context);
|
||||
await Clipboard.setData(ClipboardData(text: note.markdownContent));
|
||||
if (!mounted) return;
|
||||
messenger.showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(l10n.noteCopiedToClipboard),
|
||||
duration: const Duration(seconds: 2),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
ConduitContextMenuAction(
|
||||
cupertinoIcon: CupertinoIcons.delete,
|
||||
materialIcon: Icons.delete_rounded,
|
||||
label: l10n.delete,
|
||||
destructive: true,
|
||||
onBeforeClose: () => HapticFeedback.mediumImpact(),
|
||||
onSelected: () async => _deleteNote(note),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
Widget _buildEmptyState(BuildContext context) {
|
||||
|
||||
Reference in New Issue
Block a user