feat: add audio playback support (MP3/WAV/FLAC) and fix attachment loading in creator posts
Build and Release / Build Windows x64 (push) Has been cancelled
Build and Release / Build macOS (push) Has been cancelled

This commit is contained in:
2026-09-05 15:16:08 +03:00
parent 2dad5e9c82
commit 5e3efb0da9
13 changed files with 1073 additions and 54 deletions
+68 -8
View File
@@ -414,7 +414,15 @@ class PawchiveProvider
}
final attachments = json['attachments'];
if (attachments is List) {
files.addAll(attachments.whereType<Map>().map(Map<String, dynamic>.from));
for (final att in attachments.whereType<Map>()) {
final m = Map<String, dynamic>.from(att);
final path = (m['path'] ?? '').toString();
if (path.isNotEmpty &&
files.any((existing) => (existing['path'] ?? '').toString() == path)) {
continue;
}
files.add(m);
}
}
// Support link-only posts where author uploaded files to cloud drives (MEGA, GDrive, etc.)
@@ -468,6 +476,17 @@ class PawchiveProvider
];
}
// Determine if there is a cover illustration for the post
String? postCoverUrl;
for (final f in files) {
final p = (f['path'] ?? '').toString();
final n = (f['name'] ?? '').toString();
if (_isPhotoType('$n $p') && p.isNotEmpty) {
postCoverUrl = _thumbnailUrl(p);
break;
}
}
var index = 0;
return files
.map((fileMap) {
@@ -475,18 +494,26 @@ class PawchiveProvider
final fileName = (fileMap['name'] ?? '').toString();
final previewOnly = fileMap['preview_only'] == true;
final previewUrl = _thumbnailUrl(rawPath);
final type = _fileType('$fileName $rawPath');
final isPhoto = _isPhotoType('$fileName $rawPath');
// img.pawchive.pw/thumbnail only generates thumbnails for images/gifs.
// Non-image files (audio, video, archive) return 404 from thumbnail server.
final previewUrl = isPhoto
? _thumbnailUrl(rawPath)
: (postCoverUrl ?? '');
final fileUrl = previewOnly
? previewUrl
? (isPhoto ? previewUrl : _fileDownloadUrl(rawPath, fileName))
: _fileDownloadUrl(rawPath, fileName);
final type = _fileType('$fileName $rawPath');
final stableId =
'${query.service}:${query.artistId}:$postId:${index++}';
final tags = [
query.service,
query.artistName,
if (title.trim().isNotEmpty) title.trim(),
if (type == 'audio') 'audio',
];
return Post(
@@ -600,24 +627,57 @@ class PawchiveProvider
}
String _fileType(String value) {
final lower = value.toLowerCase().split('?').first;
if (lower.endsWith('.webm') || lower.endsWith('.mp4') || lower.endsWith('.mov')) {
final lower = value.toLowerCase().split('?').first.trim();
if (lower.endsWith('.webm') ||
lower.endsWith('.mp4') ||
lower.endsWith('.mov') ||
lower.endsWith('.mkv') ||
lower.endsWith('.avi') ||
lower.endsWith('.flv') ||
lower.endsWith('.wmv') ||
lower.endsWith('.m4v') ||
lower.endsWith('.ts')) {
return 'video';
}
if (lower.endsWith('.mp3') ||
lower.endsWith('.m4a') ||
lower.endsWith('.wav') ||
lower.endsWith('.ogg') ||
lower.endsWith('.flac') ||
lower.endsWith('.aac') ||
lower.endsWith('.opus') ||
lower.endsWith('.wma')) {
return 'audio';
}
if (lower.endsWith('.gif')) return 'gif';
if (lower.endsWith('.swf')) return 'swf';
if (lower.endsWith('.zip') || lower.endsWith('.rar') || lower.endsWith('.7z')) {
if (lower.endsWith('.zip') ||
lower.endsWith('.rar') ||
lower.endsWith('.7z') ||
lower.endsWith('.tar') ||
lower.endsWith('.gz') ||
lower.endsWith('.pdf') ||
lower.endsWith('.txt')) {
return 'archive';
}
if (lower.endsWith('.png') ||
lower.endsWith('.jpg') ||
lower.endsWith('.jpeg') ||
lower.endsWith('.webp')) {
lower.endsWith('.webp') ||
lower.endsWith('.avif') ||
lower.endsWith('.bmp') ||
lower.endsWith('.heic') ||
lower.endsWith('.tiff')) {
return 'photo';
}
return 'unknown';
}
bool _isPhotoType(String value) {
final type = _fileType(value);
return type == 'photo' || type == 'gif';
}
DateTime? _dateFromAny(dynamic value) {
if (value == null) return null;
if (value is num) {