♻️ Better file upload
This commit is contained in:
@@ -10,6 +10,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:island/models/file.dart';
|
||||
import 'package:island/pods/network.dart';
|
||||
import 'package:island/services/file.dart';
|
||||
import 'package:island/services/file_uploader.dart';
|
||||
import 'package:island/utils/format.dart';
|
||||
import 'package:island/widgets/alert.dart';
|
||||
import 'package:island/widgets/content/cloud_files.dart';
|
||||
@@ -107,13 +108,23 @@ class AttachmentPreview extends HookConsumerWidget {
|
||||
static final GlobalKey<SensitiveMarksSelectorState> _sensitiveSelectorKey =
|
||||
GlobalKey<SensitiveMarksSelectorState>();
|
||||
|
||||
Future<void> _showRenameDialog(BuildContext context, WidgetRef ref) async {
|
||||
final nameController = TextEditingController(text: item.data.name);
|
||||
String _getDisplayName() {
|
||||
return item.displayName ??
|
||||
(item.data is XFile
|
||||
? (item.data as XFile).name
|
||||
: item.isOnCloud
|
||||
? item.data.name
|
||||
: '');
|
||||
}
|
||||
|
||||
Future<void> _showRenameSheet(BuildContext context, WidgetRef ref) async {
|
||||
final nameController = TextEditingController(text: _getDisplayName());
|
||||
String? errorMessage;
|
||||
|
||||
await showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
useRootNavigator: true,
|
||||
builder:
|
||||
(context) => SheetScaffold(
|
||||
heightFactor: 0.6,
|
||||
@@ -152,22 +163,32 @@ class AttachmentPreview extends HookConsumerWidget {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
showLoadingModal(context);
|
||||
final apiClient = ref.watch(apiClientProvider);
|
||||
await apiClient.patch(
|
||||
'/drive/files/${item.data.id}/name',
|
||||
data: jsonEncode(newName),
|
||||
);
|
||||
final newData = item.data;
|
||||
newData.name = newName;
|
||||
final updatedFile = item.copyWith(data: newData);
|
||||
onUpdate?.call(item.copyWith(data: updatedFile));
|
||||
if (item.isOnCloud) {
|
||||
try {
|
||||
showLoadingModal(context);
|
||||
final apiClient = ref.watch(apiClientProvider);
|
||||
await apiClient.patch(
|
||||
'/drive/files/${item.data.id}/name',
|
||||
data: jsonEncode(newName),
|
||||
);
|
||||
final newData = item.data;
|
||||
newData.name = newName;
|
||||
onUpdate?.call(
|
||||
item.copyWith(
|
||||
data: newData,
|
||||
displayName: newName,
|
||||
),
|
||||
);
|
||||
if (context.mounted) Navigator.pop(context);
|
||||
} catch (err) {
|
||||
showErrorAlert(err);
|
||||
} finally {
|
||||
if (context.mounted) hideLoadingModal(context);
|
||||
}
|
||||
} else {
|
||||
// Local file rename
|
||||
onUpdate?.call(item.copyWith(displayName: newName));
|
||||
if (context.mounted) Navigator.pop(context);
|
||||
} catch (err) {
|
||||
showErrorAlert(err);
|
||||
} finally {
|
||||
if (context.mounted) hideLoadingModal(context);
|
||||
}
|
||||
},
|
||||
child: Text('rename'.tr()),
|
||||
@@ -292,6 +313,8 @@ class AttachmentPreview extends HookConsumerWidget {
|
||||
_ => Symbols.insert_drive_file,
|
||||
};
|
||||
|
||||
final mimeType = FileUploader.getMimeType(item);
|
||||
|
||||
if (item.isOnCloud) {
|
||||
return CloudFileWidget(item: item.data);
|
||||
} else if (item.data is XFile) {
|
||||
@@ -321,7 +344,12 @@ class AttachmentPreview extends HookConsumerWidget {
|
||||
children: [
|
||||
Icon(fallbackIcon),
|
||||
const Gap(6),
|
||||
Text(file.name, textAlign: TextAlign.center),
|
||||
Text(
|
||||
_getDisplayName(),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
Text(mimeType, style: TextStyle(fontSize: 10)),
|
||||
const Gap(1),
|
||||
FutureBuilder(
|
||||
future: file.length(),
|
||||
builder: (context, snapshot) {
|
||||
@@ -347,6 +375,8 @@ class AttachmentPreview extends HookConsumerWidget {
|
||||
children: [
|
||||
Icon(fallbackIcon),
|
||||
const Gap(6),
|
||||
Text(mimeType, style: TextStyle(fontSize: 10)),
|
||||
const Gap(1),
|
||||
Text(
|
||||
formatFileSize(item.data.length),
|
||||
).fontSize(11),
|
||||
@@ -542,12 +572,20 @@ class AttachmentPreview extends HookConsumerWidget {
|
||||
onUpdate?.call(item.copyWith(data: result));
|
||||
},
|
||||
),
|
||||
if (item.isOnDevice)
|
||||
MenuAction(
|
||||
title: 'rename'.tr(),
|
||||
image: MenuImage.icon(Symbols.edit),
|
||||
callback: () async {
|
||||
await _showRenameSheet(context, ref);
|
||||
},
|
||||
),
|
||||
if (item.isOnCloud)
|
||||
MenuAction(
|
||||
title: 'rename'.tr(),
|
||||
image: MenuImage.icon(Symbols.edit),
|
||||
callback: () async {
|
||||
await _showRenameDialog(context, ref);
|
||||
await _showRenameSheet(context, ref);
|
||||
},
|
||||
),
|
||||
if (item.isOnCloud)
|
||||
|
@@ -6,9 +6,8 @@ import 'package:gap/gap.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:island/models/file.dart';
|
||||
import 'package:island/pods/config.dart';
|
||||
import 'package:island/pods/network.dart';
|
||||
import 'package:island/services/file.dart';
|
||||
import 'package:island/services/file_uploader.dart';
|
||||
import 'package:island/widgets/alert.dart';
|
||||
import 'package:island/widgets/content/attachment_preview.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
@@ -42,10 +41,6 @@ class CloudFilePicker extends HookConsumerWidget {
|
||||
Future<void> startUpload() async {
|
||||
if (files.value.isEmpty) return;
|
||||
|
||||
final baseUrl = ref.read(serverUrlProvider);
|
||||
final token = await getToken(ref.watch(tokenProvider));
|
||||
if (token == null) throw Exception("Unauthorized");
|
||||
|
||||
List<SnCloudFile> result = List.empty(growable: true);
|
||||
|
||||
uploadProgress.value = 0;
|
||||
@@ -55,19 +50,9 @@ class CloudFilePicker extends HookConsumerWidget {
|
||||
uploadPosition.value = idx;
|
||||
final file = files.value[idx];
|
||||
final cloudFile =
|
||||
await putFileToCloud(
|
||||
await FileUploader.createCloudFile(
|
||||
fileData: file,
|
||||
atk: token,
|
||||
baseUrl: baseUrl,
|
||||
filename: file.data.name ?? 'Post media',
|
||||
mimetype:
|
||||
file.data.mimeType ??
|
||||
switch (file.type) {
|
||||
UniversalFileType.image => 'image/unknown',
|
||||
UniversalFileType.video => 'video/unknown',
|
||||
UniversalFileType.audio => 'audio/unknown',
|
||||
UniversalFileType.file => 'application/octet-stream',
|
||||
},
|
||||
client: ref.read(apiClientProvider),
|
||||
onProgress: (progress, _) {
|
||||
uploadProgress.value = progress;
|
||||
},
|
||||
|
@@ -32,7 +32,7 @@ class PostComposeCard extends HookConsumerWidget {
|
||||
final Function(SnPost)? onSubmit;
|
||||
final Function(ComposeState)? onStateChanged;
|
||||
|
||||
PostComposeCard({
|
||||
const PostComposeCard({
|
||||
super.key,
|
||||
this.originalPost,
|
||||
this.initialState,
|
||||
|
@@ -14,9 +14,8 @@ import 'package:island/models/post.dart';
|
||||
import 'package:island/models/post_category.dart';
|
||||
import 'package:island/models/publisher.dart';
|
||||
import 'package:island/models/realm.dart';
|
||||
import 'package:island/pods/config.dart';
|
||||
import 'package:island/pods/network.dart';
|
||||
import 'package:island/services/file.dart';
|
||||
import 'package:island/services/file_uploader.dart';
|
||||
import 'package:island/services/compose_storage_db.dart';
|
||||
import 'package:island/widgets/alert.dart';
|
||||
import 'package:island/widgets/post/compose_link_attachments.dart';
|
||||
@@ -177,25 +176,14 @@ class ComposeLogic {
|
||||
|
||||
try {
|
||||
// Upload any local attachments first
|
||||
final baseUrl = ref.watch(serverUrlProvider);
|
||||
final token = await getToken(ref.watch(tokenProvider));
|
||||
if (token == null) throw ArgumentError('Token is null');
|
||||
|
||||
for (int i = 0; i < state.attachments.value.length; i++) {
|
||||
final attachment = state.attachments.value[i];
|
||||
if (attachment.data is! SnCloudFile) {
|
||||
try {
|
||||
final cloudFile =
|
||||
await putFileToCloud(
|
||||
await FileUploader.createCloudFile(
|
||||
client: ref.read(apiClientProvider),
|
||||
fileData: attachment,
|
||||
atk: token,
|
||||
baseUrl: baseUrl,
|
||||
filename:
|
||||
attachment.data.name ??
|
||||
(state.postType == 1 ? 'Article media' : 'Post media'),
|
||||
mimetype:
|
||||
attachment.data.mimeType ??
|
||||
ComposeLogic.getMimeTypeFromFileType(attachment.type),
|
||||
).future;
|
||||
if (cloudFile != null) {
|
||||
// Update attachments list with cloud file
|
||||
@@ -509,15 +497,11 @@ class ComposeLogic {
|
||||
WidgetRef ref,
|
||||
ComposeState state,
|
||||
int index, {
|
||||
String? poolId, // For Unit Test
|
||||
String? poolId,
|
||||
}) async {
|
||||
final attachment = state.attachments.value[index];
|
||||
if (attachment.isOnCloud) return;
|
||||
|
||||
final baseUrl = ref.watch(serverUrlProvider);
|
||||
final token = await getToken(ref.watch(tokenProvider));
|
||||
if (token == null) throw ArgumentError('Token is null');
|
||||
|
||||
try {
|
||||
state.attachmentProgress.value = {
|
||||
...state.attachmentProgress.value,
|
||||
@@ -530,19 +514,10 @@ class ComposeLogic {
|
||||
final selectedPoolId = resolveDefaultPoolId(ref, pools);
|
||||
|
||||
cloudFile =
|
||||
await putFileToCloud(
|
||||
await FileUploader.createCloudFile(
|
||||
client: ref.read(apiClientProvider),
|
||||
fileData: attachment,
|
||||
atk: token,
|
||||
baseUrl: baseUrl,
|
||||
poolId: selectedPoolId,
|
||||
filename:
|
||||
attachment.data.name ??
|
||||
(attachment.type == UniversalFileType.file
|
||||
? 'General file'
|
||||
: 'Post media'),
|
||||
mimetype:
|
||||
attachment.data.mimeType ??
|
||||
getMimeTypeFromFileType(attachment.type),
|
||||
poolId: poolId ?? selectedPoolId,
|
||||
mode:
|
||||
attachment.type == UniversalFileType.file
|
||||
? FileUploadMode.generic
|
||||
@@ -563,7 +538,7 @@ class ComposeLogic {
|
||||
clone[index] = UniversalFile(data: cloudFile, type: attachment.type);
|
||||
state.attachments.value = clone;
|
||||
} catch (err) {
|
||||
showErrorAlert(err.toString());
|
||||
showErrorAlert(err);
|
||||
} finally {
|
||||
state.attachmentProgress.value = {...state.attachmentProgress.value}
|
||||
..remove(index);
|
||||
|
@@ -1,8 +1,10 @@
|
||||
import 'dart:io';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:island/services/file_uploader.dart';
|
||||
import 'package:island/widgets/alert.dart';
|
||||
import 'package:island/widgets/content/sheet.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
@@ -10,11 +12,7 @@ import 'package:island/screens/posts/compose.dart';
|
||||
import 'package:island/models/file.dart';
|
||||
import 'package:island/pods/link_preview.dart';
|
||||
import 'package:island/pods/network.dart';
|
||||
import 'package:island/pods/config.dart';
|
||||
import 'package:island/services/file.dart';
|
||||
import 'package:mime/mime.dart';
|
||||
|
||||
import 'dart:io';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:island/models/chat.dart';
|
||||
import 'package:island/screens/chat/chat.dart';
|
||||
@@ -192,7 +190,6 @@ class _ShareSheetState extends ConsumerState<ShareSheet> {
|
||||
setState(() => _isLoading = true);
|
||||
try {
|
||||
final apiClient = ref.read(apiClientProvider);
|
||||
final serverUrl = ref.read(serverUrlProvider);
|
||||
|
||||
String content = _messageController.text.trim();
|
||||
List<String> attachmentIds = [];
|
||||
@@ -216,11 +213,6 @@ class _ShareSheetState extends ConsumerState<ShareSheet> {
|
||||
case ShareContentType.file:
|
||||
// Upload files to cloud storage
|
||||
if (widget.content.files?.isNotEmpty == true) {
|
||||
final token = ref.watch(tokenProvider)?.token;
|
||||
if (token == null) {
|
||||
throw Exception('Authentication required');
|
||||
}
|
||||
|
||||
final universalFiles =
|
||||
widget.content.files!.map((file) {
|
||||
UniversalFileType fileType;
|
||||
@@ -247,19 +239,9 @@ class _ShareSheetState extends ConsumerState<ShareSheet> {
|
||||
for (var idx = 0; idx < universalFiles.length; idx++) {
|
||||
final file = universalFiles[idx];
|
||||
final cloudFile =
|
||||
await putFileToCloud(
|
||||
await FileUploader.createCloudFile(
|
||||
client: apiClient,
|
||||
fileData: file,
|
||||
atk: token,
|
||||
baseUrl: serverUrl,
|
||||
filename: file.data.name ?? 'Shared file',
|
||||
mimetype:
|
||||
file.data.mimeType ??
|
||||
switch (file.type) {
|
||||
UniversalFileType.image => 'image/unknown',
|
||||
UniversalFileType.video => 'video/unknown',
|
||||
UniversalFileType.audio => 'audio/unknown',
|
||||
UniversalFileType.file => 'application/octet-stream',
|
||||
},
|
||||
onProgress: (progress, _) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
|
Reference in New Issue
Block a user