Add file ID support to FileItem and update related components for consistency
This commit is contained in:
@@ -4,6 +4,7 @@ import 'dart:typed_data';
|
||||
enum FileType { folder, file }
|
||||
|
||||
class FileItem extends Equatable {
|
||||
final String? id;
|
||||
final String name;
|
||||
final String path;
|
||||
final FileType type;
|
||||
@@ -13,6 +14,7 @@ class FileItem extends Equatable {
|
||||
final Uint8List? bytes; // optional file bytes for web/desktop uploads
|
||||
|
||||
const FileItem({
|
||||
this.id,
|
||||
required this.name,
|
||||
required this.path,
|
||||
required this.type,
|
||||
@@ -23,9 +25,10 @@ class FileItem extends Equatable {
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [name, path, type, size, lastModified];
|
||||
List<Object?> get props => [id, name, path, type, size, lastModified];
|
||||
|
||||
FileItem copyWith({
|
||||
String? id,
|
||||
String? name,
|
||||
String? path,
|
||||
FileType? type,
|
||||
@@ -33,6 +36,7 @@ class FileItem extends Equatable {
|
||||
DateTime? lastModified,
|
||||
}) {
|
||||
return FileItem(
|
||||
id: id ?? this.id,
|
||||
name: name ?? this.name,
|
||||
path: path ?? this.path,
|
||||
type: type ?? this.type,
|
||||
|
||||
@@ -560,10 +560,13 @@ class _FileExplorerState extends State<FileExplorer> {
|
||||
if (file.type == FileType.folder) {
|
||||
context.read<FileBrowserBloc>().add(NavigateToFolder(file.path));
|
||||
} else {
|
||||
final fileId = file.path.startsWith('/')
|
||||
? file.path.substring(1)
|
||||
: file.path;
|
||||
_showDocumentViewer(widget.orgId, fileId);
|
||||
if (file.id == null || file.id!.isEmpty) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Error: File ID is missing')),
|
||||
);
|
||||
return;
|
||||
}
|
||||
_showDocumentViewer(widget.orgId, file.id!);
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
@@ -728,9 +731,18 @@ class _FileExplorerState extends State<FileExplorer> {
|
||||
children: [
|
||||
ModernGlassButton(
|
||||
onPressed: () async {
|
||||
print(
|
||||
'[FileExplorer-Empty] Upload clicked, currentPath=${state.currentPath}',
|
||||
);
|
||||
final result = await FilePicker.platform
|
||||
.pickFiles(withData: true);
|
||||
if (result != null && result.files.isNotEmpty) {
|
||||
print(
|
||||
'[FileExplorer-Empty] Selected ${result.files.length} files',
|
||||
);
|
||||
print(
|
||||
'[FileExplorer-Empty] Will upload to: ${state.currentPath}',
|
||||
);
|
||||
final files = result.files
|
||||
.map(
|
||||
(file) => FileItem(
|
||||
|
||||
@@ -6,9 +6,11 @@ import '../models/document_capabilities.dart';
|
||||
import '../models/api_error.dart';
|
||||
import '../repositories/file_repository.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
class MockFileRepository implements FileRepository {
|
||||
final Map<String, List<FileItem>> _orgFiles = {};
|
||||
final _uuid = const Uuid();
|
||||
|
||||
List<FileItem> _getFilesForOrg(String orgId) {
|
||||
if (!_orgFiles.containsKey(orgId)) {
|
||||
@@ -16,18 +18,21 @@ class MockFileRepository implements FileRepository {
|
||||
if (orgId == 'org1') {
|
||||
_orgFiles[orgId] = [
|
||||
FileItem(
|
||||
id: _uuid.v4(),
|
||||
name: 'Personal Documents',
|
||||
path: '/Personal Documents',
|
||||
type: FileType.folder,
|
||||
lastModified: DateTime.now(),
|
||||
),
|
||||
FileItem(
|
||||
id: _uuid.v4(),
|
||||
name: 'Photos',
|
||||
path: '/Photos',
|
||||
type: FileType.folder,
|
||||
lastModified: DateTime.now(),
|
||||
),
|
||||
FileItem(
|
||||
id: _uuid.v4(),
|
||||
name: 'resume.pdf',
|
||||
path: '/resume.pdf',
|
||||
type: FileType.file,
|
||||
@@ -35,6 +40,7 @@ class MockFileRepository implements FileRepository {
|
||||
lastModified: DateTime.now(),
|
||||
),
|
||||
FileItem(
|
||||
id: _uuid.v4(),
|
||||
name: 'notes.txt',
|
||||
path: '/notes.txt',
|
||||
type: FileType.file,
|
||||
@@ -45,12 +51,14 @@ class MockFileRepository implements FileRepository {
|
||||
} else if (orgId == 'org2') {
|
||||
_orgFiles[orgId] = [
|
||||
FileItem(
|
||||
id: _uuid.v4(),
|
||||
name: 'Company Reports',
|
||||
path: '/Company Reports',
|
||||
type: FileType.folder,
|
||||
lastModified: DateTime.now(),
|
||||
),
|
||||
FileItem(
|
||||
id: _uuid.v4(),
|
||||
name: 'annual_report.pdf',
|
||||
path: '/annual_report.pdf',
|
||||
type: FileType.file,
|
||||
@@ -58,6 +66,7 @@ class MockFileRepository implements FileRepository {
|
||||
lastModified: DateTime.now(),
|
||||
),
|
||||
FileItem(
|
||||
id: _uuid.v4(),
|
||||
name: 'presentation.pptx',
|
||||
path: '/presentation.pptx',
|
||||
type: FileType.file,
|
||||
@@ -68,12 +77,14 @@ class MockFileRepository implements FileRepository {
|
||||
} else if (orgId == 'org3') {
|
||||
_orgFiles[orgId] = [
|
||||
FileItem(
|
||||
id: _uuid.v4(),
|
||||
name: 'Project Code',
|
||||
path: '/Project Code',
|
||||
type: FileType.folder,
|
||||
lastModified: DateTime.now(),
|
||||
),
|
||||
FileItem(
|
||||
id: _uuid.v4(),
|
||||
name: 'side_project.dart',
|
||||
path: '/side_project.dart',
|
||||
type: FileType.file,
|
||||
@@ -153,6 +164,7 @@ class MockFileRepository implements FileRepository {
|
||||
final files = _getFilesForOrg(orgId);
|
||||
files.add(
|
||||
FileItem(
|
||||
id: _uuid.v4(),
|
||||
name: normalizedName,
|
||||
path: newPath,
|
||||
type: FileType.folder,
|
||||
@@ -175,6 +187,7 @@ class MockFileRepository implements FileRepository {
|
||||
final newName = file.path.split('/').last;
|
||||
final newPath = targetPath == '/' ? '/$newName' : '$targetPath/$newName';
|
||||
files[fileIndex] = FileItem(
|
||||
id: file.id,
|
||||
name: file.name,
|
||||
path: newPath,
|
||||
type: file.type,
|
||||
@@ -194,6 +207,7 @@ class MockFileRepository implements FileRepository {
|
||||
final parentPath = p.dirname(path);
|
||||
final newPath = parentPath == '.' ? '/$newName' : '$parentPath/$newName';
|
||||
files[fileIndex] = FileItem(
|
||||
id: file.id,
|
||||
name: newName,
|
||||
path: newPath,
|
||||
type: file.type,
|
||||
@@ -216,7 +230,16 @@ class MockFileRepository implements FileRepository {
|
||||
Future<void> uploadFile(String orgId, FileItem file) async {
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
final files = _getFilesForOrg(orgId);
|
||||
files.add(file);
|
||||
files.add(
|
||||
FileItem(
|
||||
id: _uuid.v4(),
|
||||
name: file.name,
|
||||
path: file.path,
|
||||
type: file.type,
|
||||
size: file.size,
|
||||
lastModified: file.lastModified,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -22,6 +22,7 @@ class FileService {
|
||||
'/user/files',
|
||||
queryParameters: pathParam,
|
||||
fromJson: (data) => FileItem(
|
||||
id: data['id'],
|
||||
name: data['name'],
|
||||
path: data['path'],
|
||||
type: data['type'] == 'file' ? FileType.file : FileType.folder,
|
||||
@@ -35,6 +36,7 @@ class FileService {
|
||||
'/orgs/$orgId/files',
|
||||
queryParameters: pathParam,
|
||||
fromJson: (data) => FileItem(
|
||||
id: data['id'],
|
||||
name: data['name'],
|
||||
path: data['path'],
|
||||
type: data['type'] == 'file' ? FileType.file : FileType.folder,
|
||||
@@ -52,32 +54,19 @@ class FileService {
|
||||
// If bytes or localPath available, send multipart upload with field 'file'
|
||||
final Map<String, dynamic> fields = {'path': file.path};
|
||||
FormData formData;
|
||||
print(
|
||||
'[FileService] uploadFile: file=${file.name}, path=${file.path}, orgId=$orgId',
|
||||
);
|
||||
print(
|
||||
'[FileService] bytes=${file.bytes?.length ?? 0}, localPath=${file.localPath}',
|
||||
);
|
||||
|
||||
if (file.bytes != null) {
|
||||
print(
|
||||
'[FileService] Using bytes for upload (${file.bytes!.length} bytes)',
|
||||
);
|
||||
formData = FormData.fromMap({
|
||||
...fields,
|
||||
'file': MultipartFile.fromBytes(file.bytes!, filename: file.name),
|
||||
});
|
||||
} else if (file.localPath != null) {
|
||||
print('[FileService] Using localPath for upload: ${file.localPath}');
|
||||
formData = FormData.fromMap({
|
||||
...fields,
|
||||
'file': MultipartFile.fromFile(file.localPath!, filename: file.name),
|
||||
});
|
||||
} else {
|
||||
// Fallback to metadata-only create (folders or client that can't send file content)
|
||||
print(
|
||||
'[FileService] No bytes or localPath; falling back to metadata-only',
|
||||
);
|
||||
final data = {
|
||||
'name': file.name,
|
||||
'path': file.path,
|
||||
@@ -97,9 +86,7 @@ class FileService {
|
||||
}
|
||||
|
||||
final endpoint = orgId.isEmpty ? '/user/files' : '/orgs/$orgId/files';
|
||||
print('[FileService] Uploading to endpoint: $endpoint');
|
||||
await _apiClient.post(endpoint, data: formData, fromJson: (d) => null);
|
||||
print('[FileService] Upload completed for ${file.name}');
|
||||
}
|
||||
|
||||
Future<void> deleteFile(String orgId, String path) async {
|
||||
|
||||
@@ -338,6 +338,7 @@ func listFilesHandler(w http.ResponseWriter, r *http.Request, db *database.DB) {
|
||||
out := make([]map[string]interface{}, 0, len(files))
|
||||
for _, f := range files {
|
||||
out = append(out, map[string]interface{}{
|
||||
"id": f.ID.String(),
|
||||
"name": f.Name,
|
||||
"path": f.Path,
|
||||
"type": f.Type,
|
||||
@@ -884,6 +885,7 @@ func userFilesHandler(w http.ResponseWriter, r *http.Request, db *database.DB) {
|
||||
out := make([]map[string]interface{}, 0, len(files))
|
||||
for _, f := range files {
|
||||
out = append(out, map[string]interface{}{
|
||||
"id": f.ID.String(),
|
||||
"name": f.Name,
|
||||
"path": f.Path,
|
||||
"type": f.Type,
|
||||
|
||||
Reference in New Issue
Block a user