♻️ Fixes and optimizations in file list
This commit is contained in:
@@ -6,18 +6,23 @@ import '../models/file.dart';
|
|||||||
import '../widgets/content/cloud_files.dart';
|
import '../widgets/content/cloud_files.dart';
|
||||||
|
|
||||||
/// Returns an appropriate icon widget for the given file based on its MIME type
|
/// Returns an appropriate icon widget for the given file based on its MIME type
|
||||||
Widget getFileIcon(SnCloudFile file, {required double size}) {
|
Widget getFileIcon(
|
||||||
|
SnCloudFile file, {
|
||||||
|
required double size,
|
||||||
|
bool tinyPreview = true,
|
||||||
|
}) {
|
||||||
final itemType = file.mimeType?.split('/').firstOrNull;
|
final itemType = file.mimeType?.split('/').firstOrNull;
|
||||||
final mimeType = file.mimeType ?? '';
|
final mimeType = file.mimeType ?? '';
|
||||||
final extension = file.name.split('.').lastOrNull?.toLowerCase() ?? '';
|
final extension = file.name.split('.').lastOrNull?.toLowerCase() ?? '';
|
||||||
|
|
||||||
// For images, show the actual image thumbnail
|
// For images, show the actual image thumbnail
|
||||||
if (itemType == 'image') {
|
if (itemType == 'image' && tinyPreview) {
|
||||||
return CloudImageWidget(file: file);
|
return CloudImageWidget(file: file);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return icon based on MIME type or file extension
|
// Return icon based on MIME type or file extension
|
||||||
final icon = switch ((itemType, mimeType, extension)) {
|
final icon = switch ((itemType, mimeType, extension)) {
|
||||||
|
('image', _, _) => Symbols.image,
|
||||||
('audio', _, _) => Symbols.audio_file,
|
('audio', _, _) => Symbols.audio_file,
|
||||||
('video', _, _) => Symbols.video_file,
|
('video', _, _) => Symbols.video_file,
|
||||||
('application', 'application/pdf', _) => Symbols.picture_as_pdf,
|
('application', 'application/pdf', _) => Symbols.picture_as_pdf,
|
||||||
|
|||||||
@@ -176,18 +176,7 @@ class FileListView extends HookConsumerWidget {
|
|||||||
ValueNotifier<String> currentPath,
|
ValueNotifier<String> currentPath,
|
||||||
ValueNotifier<FileListViewMode> currentViewMode,
|
ValueNotifier<FileListViewMode> currentViewMode,
|
||||||
) {
|
) {
|
||||||
// Check if all files are images
|
return switch (currentViewMode.value) {
|
||||||
final fileItems = items.whereType<FileItem>();
|
|
||||||
final allFilesAreImages =
|
|
||||||
fileItems.isNotEmpty &&
|
|
||||||
fileItems.every(
|
|
||||||
(fileItem) =>
|
|
||||||
fileItem.fileIndex.file.mimeType?.startsWith('image/') == true,
|
|
||||||
);
|
|
||||||
|
|
||||||
return switch (allFilesAreImages
|
|
||||||
? FileListViewMode.waterfall
|
|
||||||
: currentViewMode.value) {
|
|
||||||
// Waterfall mode
|
// Waterfall mode
|
||||||
FileListViewMode.waterfall => SliverMasonryGrid(
|
FileListViewMode.waterfall => SliverMasonryGrid(
|
||||||
gridDelegate: SliverSimpleGridDelegateWithMaxCrossAxisExtent(
|
gridDelegate: SliverSimpleGridDelegateWithMaxCrossAxisExtent(
|
||||||
@@ -539,13 +528,56 @@ class FileListView extends HookConsumerWidget {
|
|||||||
WidgetRef ref,
|
WidgetRef ref,
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
) {
|
) {
|
||||||
final file = fileItem.fileIndex.file;
|
return _buildWaterfallFileTileBase(
|
||||||
|
fileItem.fileIndex.file,
|
||||||
|
() => '/files/${fileItem.fileIndex.id}',
|
||||||
|
ref,
|
||||||
|
context,
|
||||||
|
[
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Symbols.delete),
|
||||||
|
onPressed: () async {
|
||||||
|
final confirmed = await showConfirmAlert(
|
||||||
|
'confirmDeleteFile'.tr(),
|
||||||
|
'deleteFile'.tr(),
|
||||||
|
);
|
||||||
|
if (!confirmed) return;
|
||||||
|
|
||||||
|
if (context.mounted) {
|
||||||
|
showLoadingModal(context);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
final client = ref.read(apiClientProvider);
|
||||||
|
await client.delete(
|
||||||
|
'/drive/index/remove/${fileItem.fileIndex.id}',
|
||||||
|
);
|
||||||
|
ref.invalidate(cloudFileListNotifierProvider);
|
||||||
|
} catch (e) {
|
||||||
|
showSnackBar('failedToDeleteFile'.tr());
|
||||||
|
} finally {
|
||||||
|
if (context.mounted) {
|
||||||
|
hideLoadingModal(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildWaterfallFileTileBase(
|
||||||
|
SnCloudFile file,
|
||||||
|
String Function() getRoutePath,
|
||||||
|
WidgetRef ref,
|
||||||
|
BuildContext context,
|
||||||
|
List<Widget>? actions,
|
||||||
|
) {
|
||||||
final meta = file.fileMeta is Map ? (file.fileMeta as Map) : const {};
|
final meta = file.fileMeta is Map ? (file.fileMeta as Map) : const {};
|
||||||
final ratio =
|
final ratio =
|
||||||
meta['ratio'] is num ? (meta['ratio'] as num).toDouble() : 1.0;
|
meta['ratio'] is num ? (meta['ratio'] as num).toDouble() : 1.0;
|
||||||
final itemType = file.mimeType?.split('/').first;
|
final itemType = file.mimeType?.split('/').first;
|
||||||
final uri =
|
final uri =
|
||||||
'${ref.read(apiClientProvider).options.baseUrl}/drive/files/${fileItem.fileIndex.id}';
|
'${ref.read(apiClientProvider).options.baseUrl}/drive/files/${file.id}';
|
||||||
|
|
||||||
Widget previewWidget;
|
Widget previewWidget;
|
||||||
switch (itemType) {
|
switch (itemType) {
|
||||||
@@ -602,7 +634,7 @@ class FileListView extends HookConsumerWidget {
|
|||||||
return InkWell(
|
return InkWell(
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.push('/files/${fileItem.fileIndex.id}', extra: file);
|
context.push(getRoutePath(), extra: file);
|
||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
@@ -628,7 +660,7 @@ class FileListView extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
getFileIcon(file, size: 24),
|
getFileIcon(file, size: 24, tinyPreview: false),
|
||||||
const Gap(16),
|
const Gap(16),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -649,33 +681,7 @@ class FileListView extends HookConsumerWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
IconButton(
|
if (actions != null) ...actions,
|
||||||
icon: const Icon(Symbols.delete),
|
|
||||||
onPressed: () async {
|
|
||||||
final confirmed = await showConfirmAlert(
|
|
||||||
'confirmDeleteFile'.tr(),
|
|
||||||
'deleteFile'.tr(),
|
|
||||||
);
|
|
||||||
if (!confirmed) return;
|
|
||||||
|
|
||||||
if (context.mounted) {
|
|
||||||
showLoadingModal(context);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
final client = ref.read(apiClientProvider);
|
|
||||||
await client.delete(
|
|
||||||
'/drive/index/remove/${fileItem.fileIndex.id}',
|
|
||||||
);
|
|
||||||
ref.invalidate(cloudFileListNotifierProvider);
|
|
||||||
} catch (e) {
|
|
||||||
showSnackBar('failedToDeleteFile'.tr());
|
|
||||||
} finally {
|
|
||||||
if (context.mounted) {
|
|
||||||
hideLoadingModal(context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
).padding(horizontal: 16, vertical: 4),
|
).padding(horizontal: 16, vertical: 4),
|
||||||
],
|
],
|
||||||
@@ -740,18 +746,7 @@ class FileListView extends HookConsumerWidget {
|
|||||||
BuildContext context,
|
BuildContext context,
|
||||||
ValueNotifier<FileListViewMode> currentViewMode,
|
ValueNotifier<FileListViewMode> currentViewMode,
|
||||||
) {
|
) {
|
||||||
// Check if all unindexed files are images
|
return switch (currentViewMode.value) {
|
||||||
final unindexedFiles = items.whereType<UnindexedFileItem>();
|
|
||||||
final allFilesAreImages =
|
|
||||||
unindexedFiles.isNotEmpty &&
|
|
||||||
unindexedFiles.every(
|
|
||||||
(unindexedFileItem) =>
|
|
||||||
unindexedFileItem.file.mimeType?.startsWith('image/') == true,
|
|
||||||
);
|
|
||||||
|
|
||||||
return switch (allFilesAreImages
|
|
||||||
? FileListViewMode.waterfall
|
|
||||||
: currentViewMode.value) {
|
|
||||||
// Waterfall mode
|
// Waterfall mode
|
||||||
FileListViewMode.waterfall => SliverMasonryGrid(
|
FileListViewMode.waterfall => SliverMasonryGrid(
|
||||||
gridDelegate: SliverSimpleGridDelegateWithMaxCrossAxisExtent(
|
gridDelegate: SliverSimpleGridDelegateWithMaxCrossAxisExtent(
|
||||||
@@ -822,95 +817,14 @@ class FileListView extends HookConsumerWidget {
|
|||||||
WidgetRef ref,
|
WidgetRef ref,
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
) {
|
) {
|
||||||
final file = unindexedFileItem.file;
|
return _buildWaterfallFileTileBase(
|
||||||
final meta = file.fileMeta is Map ? (file.fileMeta as Map) : const {};
|
unindexedFileItem.file,
|
||||||
final ratio =
|
() => '/files/${unindexedFileItem.file.id}',
|
||||||
meta['ratio'] is num ? (meta['ratio'] as num).toDouble() : 1.0;
|
ref,
|
||||||
final itemType = file.mimeType?.split('/').first;
|
context,
|
||||||
final tileRatio = itemType == 'image' ? ratio : 1.0;
|
[
|
||||||
final uri =
|
IconButton(
|
||||||
'${ref.read(apiClientProvider).options.baseUrl}/drive/files/${file.id}';
|
icon: const Icon(Symbols.delete),
|
||||||
|
|
||||||
Widget previewWidget;
|
|
||||||
switch (itemType) {
|
|
||||||
case 'image':
|
|
||||||
previewWidget = CloudImageWidget(
|
|
||||||
file: file,
|
|
||||||
aspectRatio: ratio,
|
|
||||||
fit: BoxFit.cover,
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case 'video':
|
|
||||||
previewWidget = CloudVideoWidget(item: file);
|
|
||||||
break;
|
|
||||||
case 'audio':
|
|
||||||
previewWidget = getFileIcon(file, size: 48);
|
|
||||||
break;
|
|
||||||
case 'text':
|
|
||||||
previewWidget = FutureBuilder<String>(
|
|
||||||
future: ref
|
|
||||||
.read(apiClientProvider)
|
|
||||||
.get(uri)
|
|
||||||
.then((response) => response.data as String),
|
|
||||||
builder:
|
|
||||||
(context, snapshot) =>
|
|
||||||
snapshot.hasData
|
|
||||||
? SingleChildScrollView(
|
|
||||||
child: Text(
|
|
||||||
snapshot.data!,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 8,
|
|
||||||
fontFamily: 'monospace',
|
|
||||||
),
|
|
||||||
maxLines: 20,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: const Center(child: CircularProgressIndicator()),
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case 'application' when file.mimeType == 'application/pdf':
|
|
||||||
previewWidget = SfPdfViewer.network(
|
|
||||||
uri,
|
|
||||||
canShowScrollStatus: false,
|
|
||||||
canShowScrollHead: false,
|
|
||||||
enableDoubleTapZooming: false,
|
|
||||||
pageSpacing: 0,
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
previewWidget = getFileIcon(file, size: 48);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return InkWell(
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
onTap: () {
|
|
||||||
context.push('/files/${file.id}', extra: file);
|
|
||||||
},
|
|
||||||
child: Stack(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
border: Border.all(
|
|
||||||
color: Theme.of(context).colorScheme.outline.withOpacity(0.3),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: AspectRatio(
|
|
||||||
aspectRatio: tileRatio,
|
|
||||||
child: ClipRRect(
|
|
||||||
borderRadius: BorderRadius.circular(4),
|
|
||||||
child: Container(color: Colors.white, child: previewWidget),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Positioned(
|
|
||||||
top: 6,
|
|
||||||
right: 6,
|
|
||||||
child: IconButton(
|
|
||||||
icon: const Icon(Symbols.delete, color: Colors.white),
|
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final confirmed = await showConfirmAlert(
|
final confirmed = await showConfirmAlert(
|
||||||
'confirmDeleteFile'.tr(),
|
'confirmDeleteFile'.tr(),
|
||||||
@@ -923,7 +837,7 @@ class FileListView extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
final client = ref.read(apiClientProvider);
|
final client = ref.read(apiClientProvider);
|
||||||
await client.delete('/drive/files/${file.id}');
|
await client.delete('/drive/files/${unindexedFileItem.file.id}');
|
||||||
ref.invalidate(unindexedFileListNotifierProvider);
|
ref.invalidate(unindexedFileListNotifierProvider);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showSnackBar('failedToDeleteFile'.tr());
|
showSnackBar('failedToDeleteFile'.tr());
|
||||||
@@ -934,9 +848,7 @@ class FileListView extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user