fox_utils 0.1.7: clipboard, drag & drop, Android file saving

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
FoxyzandClaude Opus 5.5 committed 2026-10-09 06:31:15 +03:00
commit dc16bba054
46 files changed
+4738

No files matched your search

+211
View File
@@ -0,0 +1,211 @@
import 'dart:convert';
import 'package:dio/dio.dart';
extension DioExceptionX on DioException {
String get userMessage {
switch (type) {
case DioExceptionType.connectionTimeout:
case DioExceptionType.sendTimeout:
case DioExceptionType.receiveTimeout:
return 'Connection timeout. Please check your internet.';
case DioExceptionType.badResponse:
final statusCode = response?.statusCode;
if (statusCode == 401) return 'Unauthorized. Please log in again.';
if (statusCode == 404) return 'Resource not found.';
if (statusCode != null && statusCode >= 500) {
return 'Server error (Status $statusCode).';
}
return 'Request failed with status $statusCode.';
case DioExceptionType.cancel:
return 'Request was cancelled.';
case DioExceptionType.unknown:
return 'An unexpected error occurred.';
default:
return 'Request failed.';
}
}
String toDebugString() {
final buffer = StringBuffer();
buffer.writeln('🚨 [DioException] ${type.name}');
buffer.writeln('📤 Request: ${requestOptions.method} ${requestOptions.uri}');
if (response != null) {
buffer.writeln('📥 Response: ${response!.statusCode}');
if (response!.data != null) {
buffer.writeln(' Body: ${_formatData(response!.data)}');
}
} else if (error != null) {
buffer.writeln('⚠️ Error: ${error.toString()}');
}
return buffer.toString().trim();
}
String _formatData(dynamic data) {
if (data == null) return 'null';
if (data is Map || data is List) {
try {
const encoder = JsonEncoder.withIndent(' ');
return encoder.convert(data);
} catch (_) {
return data.toString();
}
}
return data.toString();
}
}
extension DiscordDioException on DioException {
/// Generates a Markdown formatted error report suitable for Discord messages.
String toDiscordMarkdown() {
final emoji = _getEmoji();
final title = _getFriendlyTitle();
final message = userMessage;
final buffer = StringBuffer();
buffer.writeln('$emoji **$title**');
buffer.writeln();
buffer.writeln(message);
buffer.writeln();
buffer.writeln('`${requestOptions.method} ${requestOptions.uri}`');
buffer.writeln();
// Technical details
buffer.writeln('```yaml');
buffer.writeln('Type: ${type.name}');
if (response != null) {
buffer.writeln('Status: ${response!.statusCode}');
}
if (error != null) {
final errorStr = error.toString();
buffer.writeln('Error: ${errorStr.length > 200 ? '${errorStr.substring(0, 200)}...' : errorStr}');
}
buffer.writeln('```');
// Ensure whole message fits in Discord's 2000‑char limit
var result = buffer.toString().trim();
const maxLength = 2000;
if (result.length > maxLength) {
result = '${result.substring(0, maxLength - 3)}...';
}
return result;
}
/// Human‑readable message, enhanced with the server’s own error message if available.
String get userMessage {
print("Server message: ${response?.data}");
final base = _baseUserMessage();
final server = _getServerMessage();
if (server == null || server.isEmpty) return base;
if (base.contains(server)) return base; // avoid duplication
return '$base — $server';
}
// ---------------------------------------------------------------------------
// Private helpers
// ---------------------------------------------------------------------------
String _baseUserMessage() {
switch (type) {
case DioExceptionType.connectionTimeout:
case DioExceptionType.sendTimeout:
case DioExceptionType.receiveTimeout:
return 'The connection timed out.';
case DioExceptionType.connectionError:
final errorStr = error?.toString().toLowerCase() ?? '';
if (errorStr.contains('semaphore timeout') || errorStr.contains('timed out')) {
return 'Connection timed out. The server took too long to respond.';
}
if (errorStr.contains('connection refused')) {
return 'Connection refused. The server is likely offline.';
}
if (errorStr.contains('failed host lookup')) {
return 'DNS Lookup Failed. Could not reach the host.';
}
return 'A network connection error occurred.';
case DioExceptionType.badResponse:
final statusCode = response?.statusCode;
// Common client errors
if (statusCode == 400) return 'Bad Request (400).';
if (statusCode == 401) return 'Unauthorized (401). Authentication failed.';
if (statusCode == 403) return 'Forbidden (403). You lack permission.';
if (statusCode == 404) return 'Not Found (404). The resource does not exist.';
if (statusCode == 422) return 'Validation Error (422). Some fields are invalid.';
if (statusCode == 429) return 'Too Many Requests (429). You are being rate‑limited.';
// Server errors
if (statusCode != null && statusCode >= 500) {
if (statusCode == 503) return 'Service Unavailable (503).';
return 'Server Error ($statusCode). Please try again later.';
}
return 'Request failed with status $statusCode.';
case DioExceptionType.cancel:
return 'The request was cancelled.';
case DioExceptionType.unknown:
final errorStr = error?.toString().toLowerCase() ?? '';
if (errorStr.contains('socketexception')) {
if (errorStr.contains('connection refused')) return 'Server connection refused.';
if (errorStr.contains('failed host lookup')) return 'Could not find the server address.';
if (errorStr.contains('semaphore timeout')) return 'Network timeout.';
return 'Network connection unstable.';
}
return 'An unexpected error occurred.';
default:
return 'Request failed.';
}
}
String? _getServerMessage() {
final data = response?.data;
if (data is Map) {
// Try common top‑level keys
for (final key in ['message', 'error', 'detail', 'description']) {
final val = data[key];
if (val is String && val.trim().isNotEmpty) return val.trim();
}
// Nested errors map (e.g. { "errors": { "email": ["is taken"] } })
final errors = data['errors'];
if (errors is Map) {
for (final entry in errors.entries) {
final v = entry.value;
if (v is List && v.isNotEmpty) return '${entry.key}: ${v.first}';
if (v is String && v.isNotEmpty) return '${entry.key}: $v';
}
}
} else if (data is List && data.isNotEmpty) {
if (data.first is Map) {
return _getServerMessageFromMap(data.first as Map);
}
} else if (data is String && data.isNotEmpty) {
// Plain text body – use it directly, but keep it short
return data.length > 200 ? '${data.substring(0, 200)}...' : data;
}
return null;
}
// Helper for when the first element of a list is a map
String? _getServerMessageFromMap(Map map) {
print("Server has map: ${map}");
for (final key in ['message', 'error', 'detail']) {
final val = map[key];
if (val is String && val.trim().isNotEmpty) {
return val.trim();
}
}
return null;
}
String _getFriendlyTitle() {
if (type == DioExceptionType.badResponse) return 'API Error';
return 'Network Error';
}
String _getEmoji() {
if (type == DioExceptionType.badResponse) return '⚠️';
return '⛔';
}
}