super_clipboard pulled in super_native_extensions (a Rust native layer
built via cargokit), whose precompiled-binary download from GitHub
release assets has been intermittently failing the build ("Connection
closed while receiving data"). It was used for exactly one feature -
reading a clipboard image into the composer - in a single file; the
other 12 imports were dead.
- Swap super_clipboard -> pasteboard (platform-channel, no Rust).
- Rewrite readClipboardImageAsXFileInternal() on Pasteboard.image
(PNG bytes), collapsing three helpers into one.
- Remove 12 unused super_clipboard imports.
- Regenerated plugin registrants / lockfiles drop super_native_extensions.
Removes the Rust toolchain requirement and the flaky download entirely.
Text copy/paste already used Flutter's built-in Clipboard and is
unaffected.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
118 lines
3.9 KiB
Dart
118 lines
3.9 KiB
Dart
// ignore_for_file: unused_import, unnecessary_import
|
|
|
|
import 'dart:async';
|
|
import 'dart:convert';
|
|
import 'dart:io';
|
|
import 'dart:math' as math;
|
|
import 'package:file_selector/file_selector.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:flutter_markdown/flutter_markdown.dart';
|
|
import 'package:markdown/markdown.dart' as md;
|
|
import 'package:path_provider/path_provider.dart';
|
|
import 'package:pasteboard/pasteboard.dart';
|
|
import '../../app/app_controller.dart';
|
|
import '../../app/app_metadata.dart';
|
|
import '../../app/ui_feature_manifest.dart';
|
|
import '../../i18n/app_language.dart';
|
|
import '../../models/app_models.dart';
|
|
import '../../runtime/runtime_models.dart';
|
|
import '../../theme/app_palette.dart';
|
|
import '../../theme/app_theme.dart';
|
|
import '../../widgets/assistant_focus_panel.dart';
|
|
import '../../widgets/assistant_artifact_sidebar.dart';
|
|
import '../../widgets/desktop_workspace_scaffold.dart';
|
|
import '../../widgets/pane_resize_handle.dart';
|
|
import '../../widgets/surface_card.dart';
|
|
import 'assistant_page_main.dart';
|
|
import 'assistant_page_components.dart';
|
|
import 'assistant_page_composer_bar.dart';
|
|
import 'assistant_page_composer_state_helpers.dart';
|
|
import 'assistant_page_composer_support.dart';
|
|
import 'assistant_page_tooltip_labels.dart';
|
|
import 'assistant_page_message_widgets.dart';
|
|
import 'assistant_page_task_models.dart';
|
|
import 'assistant_page_composer_skill_picker.dart';
|
|
import 'assistant_page_components_core.dart';
|
|
|
|
class ComposerAttachmentInternal {
|
|
const ComposerAttachmentInternal({
|
|
required this.name,
|
|
required this.path,
|
|
required this.icon,
|
|
required this.mimeType,
|
|
});
|
|
|
|
final String name;
|
|
final String path;
|
|
final IconData icon;
|
|
final String mimeType;
|
|
|
|
factory ComposerAttachmentInternal.fromXFile(XFile file) {
|
|
final extension = file.name.split('.').last.toLowerCase();
|
|
final mimeType = switch (extension) {
|
|
'png' => 'image/png',
|
|
'jpg' || 'jpeg' => 'image/jpeg',
|
|
'gif' => 'image/gif',
|
|
'webp' => 'image/webp',
|
|
'json' => 'application/json',
|
|
'csv' => 'text/csv',
|
|
'txt' || 'log' || 'md' || 'yaml' || 'yml' => 'text/plain',
|
|
'pdf' => 'application/pdf',
|
|
'zip' => 'application/zip',
|
|
_ => 'application/octet-stream',
|
|
};
|
|
final icon = switch (extension) {
|
|
'png' || 'jpg' || 'jpeg' || 'gif' || 'webp' => Icons.image_outlined,
|
|
'log' || 'txt' || 'json' || 'csv' => Icons.description_outlined,
|
|
_ => Icons.insert_drive_file_outlined,
|
|
};
|
|
|
|
return ComposerAttachmentInternal(
|
|
name: file.name,
|
|
path: file.path,
|
|
icon: icon,
|
|
mimeType: mimeType,
|
|
);
|
|
}
|
|
}
|
|
|
|
class AssistantPasteIntent extends Intent {
|
|
const AssistantPasteIntent();
|
|
}
|
|
|
|
Future<XFile?> readClipboardImageAsXFileInternal() async {
|
|
// pasteboard normalizes clipboard images to PNG bytes across platforms.
|
|
Uint8List? bytes;
|
|
try {
|
|
bytes = await Pasteboard.image;
|
|
} catch (error, stackTrace) {
|
|
debugPrint('Error reading clipboard image: $error\n$stackTrace');
|
|
return null;
|
|
}
|
|
if (bytes == null || bytes.isEmpty) {
|
|
return null;
|
|
}
|
|
final temporaryDirectory =
|
|
await resolveClipboardAttachmentTempDirectoryInternal();
|
|
final fileName =
|
|
'clipboard-image-${DateTime.now().microsecondsSinceEpoch}.png';
|
|
final file = File('${temporaryDirectory.path}/$fileName');
|
|
await file.writeAsBytes(bytes, flush: true);
|
|
return XFile(file.path, mimeType: 'image/png', name: fileName);
|
|
}
|
|
|
|
Future<Directory> resolveClipboardAttachmentTempDirectoryInternal() async {
|
|
Directory rootDirectory;
|
|
try {
|
|
rootDirectory = await getTemporaryDirectory();
|
|
} catch (e, stackTrace) { debugPrint('Error: $e\n$stackTrace');
|
|
rootDirectory = Directory.systemTemp;
|
|
}
|
|
final clipboardDirectory = Directory(
|
|
'${rootDirectory.path}/xworkmate-clipboard-attachments',
|
|
);
|
|
await clipboardDirectory.create(recursive: true);
|
|
return clipboardDirectory;
|
|
}
|