fix: remove clipboard paste menu item, fix timeline line overlap, dim input placeholder
- Remove redundant "从剪贴板粘贴" option from attachment menu (long-press to paste natively) - Remove super_clipboard dependency (no longer needed) - Fix timeline vertical line overlapping icon nodes by using dynamic dotRadius - Dim input field placeholder color to AppColors.textMuted Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
cfc0a97da7
commit
1f1bf18a75
|
|
@ -1,10 +1,8 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:super_clipboard/super_clipboard.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import '../../../../core/theme/app_colors.dart';
|
||||
import '../../domain/entities/chat_message.dart';
|
||||
|
|
@ -93,11 +91,6 @@ class _ChatPageState extends ConsumerState<ChatPage> {
|
|||
title: const Text('拍照'),
|
||||
onTap: () { Navigator.pop(ctx); _pickImage(ImageSource.camera); },
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.content_paste),
|
||||
title: const Text('从剪贴板粘贴'),
|
||||
onTap: () { Navigator.pop(ctx); _pasteFromClipboard(); },
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.attach_file),
|
||||
title: const Text('选择文件'),
|
||||
|
|
@ -193,58 +186,6 @@ class _ChatPageState extends ConsumerState<ChatPage> {
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> _pasteFromClipboard() async {
|
||||
final clipboard = SystemClipboard.instance;
|
||||
if (clipboard == null) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('当前设备不支持剪贴板读取')),
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final reader = await clipboard.read();
|
||||
for (final item in reader.items) {
|
||||
// Try PNG first, then JPEG
|
||||
for (final format in [Formats.png, Formats.jpeg]) {
|
||||
if (item.canProvide(format)) {
|
||||
final completer = Completer<List<int>>();
|
||||
item.getFile(format, (file) async {
|
||||
try {
|
||||
final bytes = await file.readAll();
|
||||
completer.complete(bytes);
|
||||
} catch (e) {
|
||||
completer.completeError(e);
|
||||
}
|
||||
}, onError: (e) {
|
||||
if (!completer.isCompleted) completer.completeError(e);
|
||||
});
|
||||
final bytes = await completer.future;
|
||||
final mediaType = format == Formats.png ? 'image/png' : 'image/jpeg';
|
||||
setState(() {
|
||||
_pendingAttachments.add(ChatAttachment(
|
||||
base64Data: base64Encode(bytes),
|
||||
mediaType: mediaType,
|
||||
fileName: 'clipboard.${format == Formats.png ? "png" : "jpg"}',
|
||||
));
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Clipboard read failed
|
||||
}
|
||||
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('剪贴板中没有图片')),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _pickFile() async {
|
||||
final remaining = _maxAttachments - _pendingAttachments.length;
|
||||
if (remaining <= 0) {
|
||||
|
|
@ -673,6 +614,7 @@ class _ChatPageState extends ConsumerState<ChatPage> {
|
|||
controller: _messageController,
|
||||
decoration: InputDecoration(
|
||||
hintText: isStreaming ? '追加指令...' : '输入指令...',
|
||||
hintStyle: TextStyle(color: AppColors.textMuted),
|
||||
border: const OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(24)),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -40,12 +40,16 @@ class TimelineEventNode extends StatelessWidget {
|
|||
Widget build(BuildContext context) {
|
||||
// Use CustomPaint for the timeline line to avoid IntrinsicHeight which
|
||||
// causes bottom-overflow on long content.
|
||||
// Icon nodes are 16px tall, plain dots are 10px. The gap in the
|
||||
// vertical line must be large enough to avoid overlapping either.
|
||||
final dotRadius = icon != null ? 10.0 : 6.0;
|
||||
return CustomPaint(
|
||||
painter: _TimelineLinePainter(
|
||||
color: AppColors.surfaceLight,
|
||||
isFirst: isFirst,
|
||||
isLast: isLast,
|
||||
dotOffset: 14, // vertical center of the dot area
|
||||
dotCenter: 14, // vertical center of the dot area
|
||||
dotRadius: dotRadius,
|
||||
lineX: 14, // horizontal center of the 28px left column
|
||||
),
|
||||
child: Row(
|
||||
|
|
@ -109,14 +113,16 @@ class _TimelineLinePainter extends CustomPainter {
|
|||
final Color color;
|
||||
final bool isFirst;
|
||||
final bool isLast;
|
||||
final double dotOffset;
|
||||
final double dotCenter;
|
||||
final double dotRadius;
|
||||
final double lineX;
|
||||
|
||||
_TimelineLinePainter({
|
||||
required this.color,
|
||||
required this.isFirst,
|
||||
required this.isLast,
|
||||
required this.dotOffset,
|
||||
required this.dotCenter,
|
||||
required this.dotRadius,
|
||||
required this.lineX,
|
||||
});
|
||||
|
||||
|
|
@ -126,15 +132,15 @@ class _TimelineLinePainter extends CustomPainter {
|
|||
..color = color
|
||||
..strokeWidth = 1.5;
|
||||
|
||||
// Line above the dot
|
||||
// Line above the dot/icon
|
||||
if (!isFirst) {
|
||||
canvas.drawLine(Offset(lineX, 0), Offset(lineX, dotOffset), paint);
|
||||
canvas.drawLine(Offset(lineX, 0), Offset(lineX, dotCenter - dotRadius), paint);
|
||||
}
|
||||
|
||||
// Line below the dot
|
||||
// Line below the dot/icon
|
||||
if (!isLast) {
|
||||
canvas.drawLine(
|
||||
Offset(lineX, dotOffset + 10),
|
||||
Offset(lineX, dotCenter + dotRadius),
|
||||
Offset(lineX, size.height),
|
||||
paint,
|
||||
);
|
||||
|
|
@ -145,7 +151,8 @@ class _TimelineLinePainter extends CustomPainter {
|
|||
bool shouldRepaint(_TimelineLinePainter oldDelegate) =>
|
||||
color != oldDelegate.color ||
|
||||
isFirst != oldDelegate.isFirst ||
|
||||
isLast != oldDelegate.isLast;
|
||||
isLast != oldDelegate.isLast ||
|
||||
dotRadius != oldDelegate.dotRadius;
|
||||
}
|
||||
|
||||
/// Static colored dot with optional icon for completed/error/idle states.
|
||||
|
|
|
|||
|
|
@ -37,7 +37,6 @@ dependencies:
|
|||
gpt_markdown: ^1.1.5
|
||||
flutter_svg: ^2.0.10+1
|
||||
image_picker: ^1.1.2
|
||||
super_clipboard: ^0.8.1
|
||||
file_picker: ^8.0.0
|
||||
|
||||
# Voice
|
||||
|
|
|
|||
Loading…
Reference in New Issue