[REF] unify file upload logic and pool utils

- merge putMediaToCloud and putFileToPool into putFileToCloud
  with FileUploadMode for media-safe vs generic uploads

Signed-off-by: Texas0295 <kimura@texas0295.top>
This commit is contained in:
Texas0295
2025-09-21 22:38:49 +08:00
parent 1391fa0dde
commit ace302111a
11 changed files with 69 additions and 131 deletions

View File

@@ -66,7 +66,7 @@ class UpdateProfileScreen extends HookConsumerWidget {
final token = await getToken(ref.watch(tokenProvider)); final token = await getToken(ref.watch(tokenProvider));
if (token == null) throw ArgumentError('Token is null'); if (token == null) throw ArgumentError('Token is null');
final cloudFile = final cloudFile =
await putMediaToCloud( await putFileToCloud(
fileData: UniversalFile( fileData: UniversalFile(
data: result, data: result,
type: UniversalFileType.image, type: UniversalFileType.image,

View File

@@ -543,7 +543,7 @@ class EditChatScreen extends HookConsumerWidget {
final token = await getToken(ref.watch(tokenProvider)); final token = await getToken(ref.watch(tokenProvider));
if (token == null) throw ArgumentError('Token is null'); if (token == null) throw ArgumentError('Token is null');
final cloudFile = final cloudFile =
await putMediaToCloud( await putFileToCloud(
fileData: UniversalFile( fileData: UniversalFile(
data: result, data: result,
type: UniversalFileType.image, type: UniversalFileType.image,

View File

@@ -649,7 +649,7 @@ Future<void> loadMore() async {
var cloudAttachments = List.empty(growable: true); var cloudAttachments = List.empty(growable: true);
for (var idx = 0; idx < attachments.length; idx++) { for (var idx = 0; idx < attachments.length; idx++) {
final cloudFile = final cloudFile =
await putMediaToCloud( await putFileToCloud(
fileData: attachments[idx], fileData: attachments[idx],
atk: token, atk: token,
baseUrl: baseUrl, baseUrl: baseUrl,

View File

@@ -98,7 +98,7 @@ class EditPublisherScreen extends HookConsumerWidget {
final token = await getToken(ref.watch(tokenProvider)); final token = await getToken(ref.watch(tokenProvider));
if (token == null) throw ArgumentError('Token is null'); if (token == null) throw ArgumentError('Token is null');
final cloudFile = final cloudFile =
await putMediaToCloud( await putFileToCloud(
fileData: UniversalFile( fileData: UniversalFile(
data: result, data: result,
type: UniversalFileType.image, type: UniversalFileType.image,

View File

@@ -141,7 +141,7 @@ class EditAppScreen extends HookConsumerWidget {
final token = await getToken(ref.watch(tokenProvider)); final token = await getToken(ref.watch(tokenProvider));
if (token == null) throw ArgumentError('Token is null'); if (token == null) throw ArgumentError('Token is null');
final cloudFile = final cloudFile =
await putMediaToCloud( await putFileToCloud(
fileData: UniversalFile( fileData: UniversalFile(
data: result, data: result,
type: UniversalFileType.image, type: UniversalFileType.image,

View File

@@ -127,7 +127,7 @@ class EditBotScreen extends HookConsumerWidget {
final token = await getToken(ref.watch(tokenProvider)); final token = await getToken(ref.watch(tokenProvider));
if (token == null) throw ArgumentError('Token is null'); if (token == null) throw ArgumentError('Token is null');
final cloudFile = final cloudFile =
await putMediaToCloud( await putFileToCloud(
fileData: UniversalFile( fileData: UniversalFile(
data: result, data: result,
type: UniversalFileType.image, type: UniversalFileType.image,

View File

@@ -211,7 +211,7 @@ class EditRealmScreen extends HookConsumerWidget {
final token = await getToken(ref.watch(tokenProvider)); final token = await getToken(ref.watch(tokenProvider));
if (token == null) throw ArgumentError('Access token is null'); if (token == null) throw ArgumentError('Access token is null');
final cloudFile = final cloudFile =
await putMediaToCloud( await putFileToCloud(
fileData: UniversalFile( fileData: UniversalFile(
data: result, data: result,
type: UniversalFileType.image, type: UniversalFileType.image,

View File

@@ -11,6 +11,8 @@ import 'package:island/services/file_uploader.dart';
import 'package:native_exif/native_exif.dart'; import 'package:native_exif/native_exif.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
enum FileUploadMode { generic, mediaSafe }
Future<XFile?> cropImage( Future<XFile?> cropImage(
BuildContext context, { BuildContext context, {
required XFile image, required XFile image,
@@ -40,98 +42,46 @@ Future<XFile?> cropImage(
); );
} }
Completer<SnCloudFile?> putFileToPool({ Completer<SnCloudFile?> putFileToCloud({
required UniversalFile fileData,
required String atk,
required String baseUrl,
required String poolId,
String? filename,
String? mimetype,
Function(double progress, Duration estimate)? onProgress,
}) {
final completer = Completer<SnCloudFile?>();
final data = fileData.data;
if (data is! XFile) {
completer.completeError(
ArgumentError('Unsupported fileData type for putFileToPool'),
);
return completer;
}
final actualFilename = filename ?? data.name;
final actualMimetype = mimetype ?? data.mimeType ?? 'application/octet-stream';
final dio = Dio(BaseOptions(
baseUrl: baseUrl,
headers: {
'Authorization': 'AtField $atk',
'Accept': 'application/json',
},
));
final uploader = FileUploader(dio);
final fileObj = File(data.path);
onProgress?.call(0.0, Duration.zero);
uploader.uploadFile(
file: fileObj,
fileName: actualFilename,
contentType: actualMimetype,
poolId: poolId,
).then((result) {
onProgress?.call(1.0, Duration.zero);
completer.complete(result);
}).catchError((e) {
completer.completeError(e);
});
return completer;
}
Completer<SnCloudFile?> putMediaToCloud({
required UniversalFile fileData, required UniversalFile fileData,
required String atk, required String atk,
required String baseUrl, required String baseUrl,
String? poolId, String? poolId,
String? filename, String? filename,
String? mimetype, String? mimetype,
FileUploadMode? mode,
Function(double progress, Duration estimate)? onProgress, Function(double progress, Duration estimate)? onProgress,
}) { }) {
final completer = Completer<SnCloudFile?>(); final completer = Completer<SnCloudFile?>();
// Process the image to remove GPS EXIF data if needed final effectiveMode =
if (fileData.isOnDevice && fileData.type == UniversalFileType.image) { mode ??
(fileData.type == UniversalFileType.file
? FileUploadMode.generic
: FileUploadMode.mediaSafe);
if (effectiveMode == FileUploadMode.mediaSafe &&
fileData.isOnDevice &&
fileData.type == UniversalFileType.image) {
final data = fileData.data; final data = fileData.data;
if (data is XFile && !kIsWeb && (Platform.isIOS || Platform.isAndroid)) { if (data is XFile && !kIsWeb && (Platform.isIOS || Platform.isAndroid)) {
// Use native_exif to selectively remove GPS data
Exif.fromPath(data.path) Exif.fromPath(data.path)
.then((exif) { .then((exif) async {
// Remove GPS-related attributes final gpsAttributes = {
final gpsAttributes = [ 'GPSLatitude': '',
'GPSLatitude', 'GPSLatitudeRef': '',
'GPSLatitudeRef', 'GPSLongitude': '',
'GPSLongitude', 'GPSLongitudeRef': '',
'GPSLongitudeRef', 'GPSAltitude': '',
'GPSAltitude', 'GPSAltitudeRef': '',
'GPSAltitudeRef', 'GPSTimeStamp': '',
'GPSTimeStamp', 'GPSProcessingMethod': '',
'GPSProcessingMethod', 'GPSDateStamp': '',
'GPSDateStamp', };
]; await exif.writeAttributes(gpsAttributes);
// Create a map of attributes to clear
final clearAttributes = <String, String>{};
for (final attr in gpsAttributes) {
clearAttributes[attr] = '';
}
// Write empty values to remove GPS data
return exif.writeAttributes(clearAttributes);
}) })
.then((_) { .then(
// Continue with upload after GPS data is removed (_) => _processUpload(
_processUpload(
fileData, fileData,
atk, atk,
baseUrl, baseUrl,
@@ -140,12 +90,11 @@ Completer<SnCloudFile?> putMediaToCloud({
mimetype, mimetype,
onProgress, onProgress,
completer, completer,
); ),
}) )
.catchError((e) { .catchError((e) {
// If there's an error, continue with the original file
debugPrint('Error removing GPS EXIF data: $e'); debugPrint('Error removing GPS EXIF data: $e');
_processUpload( return _processUpload(
fileData, fileData,
atk, atk,
baseUrl, baseUrl,
@@ -161,7 +110,6 @@ Completer<SnCloudFile?> putMediaToCloud({
} }
} }
// If not an image or on web, continue with normal upload
_processUpload( _processUpload(
fileData, fileData,
atk, atk,

View File

@@ -55,7 +55,7 @@ class CloudFilePicker extends HookConsumerWidget {
uploadPosition.value = idx; uploadPosition.value = idx;
final file = files.value[idx]; final file = files.value[idx];
final cloudFile = final cloudFile =
await putMediaToCloud( await putFileToCloud(
fileData: file, fileData: file,
atk: token, atk: token,
baseUrl: baseUrl, baseUrl: baseUrl,

View File

@@ -185,7 +185,7 @@ class ComposeLogic {
if (attachment.data is! SnCloudFile) { if (attachment.data is! SnCloudFile) {
try { try {
final cloudFile = final cloudFile =
await putMediaToCloud( await putFileToCloud(
fileData: attachment, fileData: attachment,
atk: token, atk: token,
baseUrl: baseUrl, baseUrl: baseUrl,
@@ -525,17 +525,25 @@ class ComposeLogic {
final pools = await ref.read(poolsProvider.future); final pools = await ref.read(poolsProvider.future);
final selectedPoolId = resolveDefaultPoolId(ref, pools); final selectedPoolId = resolveDefaultPoolId(ref, pools);
if (attachment.type == UniversalFileType.file) {
cloudFile = cloudFile =
await putFileToPool( await putFileToCloud(
fileData: attachment, fileData: attachment,
atk: token, atk: token,
baseUrl: baseUrl, baseUrl: baseUrl,
poolId: selectedPoolId, poolId: selectedPoolId,
filename: attachment.data.name ?? 'General file', filename:
attachment.data.name ??
(attachment.type == UniversalFileType.file
? 'General file'
: 'Post media'),
mimetype: mimetype:
attachment.data.mimeType ?? attachment.data.mimeType ??
getMimeTypeFromFileType(attachment.type), getMimeTypeFromFileType(attachment.type),
mode:
attachment.type == UniversalFileType.file
? FileUploadMode.generic
: FileUploadMode.mediaSafe,
onProgress: (progress, _) { onProgress: (progress, _) {
state.attachmentProgress.value = { state.attachmentProgress.value = {
...state.attachmentProgress.value, ...state.attachmentProgress.value,
@@ -543,24 +551,6 @@ class ComposeLogic {
}; };
}, },
).future; ).future;
} else {
cloudFile =
await putMediaToCloud(
fileData: attachment,
atk: token,
baseUrl: baseUrl,
filename: attachment.data.name ?? 'Post media',
mimetype:
attachment.data.mimeType ??
getMimeTypeFromFileType(attachment.type),
onProgress: (progress, _) {
state.attachmentProgress.value = {
...state.attachmentProgress.value,
index: progress,
};
},
).future;
}
if (cloudFile == null) { if (cloudFile == null) {
throw ArgumentError('Failed to upload the file...'); throw ArgumentError('Failed to upload the file...');

View File

@@ -247,7 +247,7 @@ class _ShareSheetState extends ConsumerState<ShareSheet> {
for (var idx = 0; idx < universalFiles.length; idx++) { for (var idx = 0; idx < universalFiles.length; idx++) {
final file = universalFiles[idx]; final file = universalFiles[idx];
final cloudFile = final cloudFile =
await putMediaToCloud( await putFileToCloud(
fileData: file, fileData: file,
atk: token, atk: token,
baseUrl: serverUrl, baseUrl: serverUrl,