implemented a comprehensive Nextcloud file sharing system with support for user, group, and public link shares with gating based on server-side permissions; added sharing management interfaces including a share sheet; updated the file list with visual badges for incoming shares and improved OCS API response handling.
This commit is contained in:
@@ -24,6 +24,11 @@ class CacheableFile {
|
||||
/// when a preview is going to load anyway.
|
||||
bool? hasPreview;
|
||||
|
||||
/// True when this entry is an incoming share — i.e. shared with the current
|
||||
/// user by someone else (`nc:mount-type == 'shared'`). Used to badge the
|
||||
/// file/folder in the list. Nullable so older cached entries decode fine.
|
||||
bool? isSharedWithMe;
|
||||
|
||||
CacheableFile({
|
||||
required this.path,
|
||||
required this.isDirectory,
|
||||
@@ -35,6 +40,7 @@ class CacheableFile {
|
||||
this.modifiedAt,
|
||||
this.fileId,
|
||||
this.hasPreview,
|
||||
this.isSharedWithMe,
|
||||
});
|
||||
|
||||
CacheableFile.fromDavFile(WebDavFile file) {
|
||||
@@ -48,6 +54,11 @@ class CacheableFile {
|
||||
modifiedAt = file.lastModified;
|
||||
fileId = int.tryParse(file.fileId ?? '');
|
||||
hasPreview = file.hasPreview;
|
||||
// Incoming share: the item is mounted into the user's files by someone
|
||||
// else. Outgoing shares ([isSharedByMe]) can't be derived from WebDAV with
|
||||
// the pinned package, so they are filled in by ListFiles via one OCS call
|
||||
// per folder.
|
||||
isSharedWithMe = file.props.ncmounttype == 'shared';
|
||||
}
|
||||
|
||||
factory CacheableFile.fromJson(Map<String, dynamic> json) =>
|
||||
|
||||
@@ -22,6 +22,7 @@ CacheableFile _$CacheableFileFromJson(Map<String, dynamic> json) =>
|
||||
: DateTime.parse(json['modifiedAt'] as String),
|
||||
fileId: (json['fileId'] as num?)?.toInt(),
|
||||
hasPreview: json['hasPreview'] as bool?,
|
||||
isSharedWithMe: json['isSharedWithMe'] as bool?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$CacheableFileToJson(CacheableFile instance) =>
|
||||
@@ -36,4 +37,5 @@ Map<String, dynamic> _$CacheableFileToJson(CacheableFile instance) =>
|
||||
'modifiedAt': instance.modifiedAt?.toIso8601String(),
|
||||
'fileId': instance.fileId,
|
||||
'hasPreview': instance.hasPreview,
|
||||
'isSharedWithMe': instance.isSharedWithMe,
|
||||
};
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:nextcloud/nextcloud.dart';
|
||||
|
||||
import '../../webdav_api.dart';
|
||||
@@ -37,19 +39,46 @@ class ListFiles extends WebdavApi<ListFilesParams> {
|
||||
ocsize: true,
|
||||
nccreationtime: true,
|
||||
nchaspreview: true,
|
||||
// 'shared' here means an incoming share (mounted into the user's files
|
||||
// by someone else); used to badge those entries in the list.
|
||||
ncmounttype: true,
|
||||
);
|
||||
|
||||
var files = await _fetch(webdav, prop, timeout);
|
||||
// A freshly-entered incoming share sometimes answers its first PROPFIND
|
||||
// without the OC/NC props (no fileid / has-preview / mount-type) while the
|
||||
// share mount warms up server-side — which drops thumbnails AND share
|
||||
// badges together. Retry a couple of times so the folder self-heals
|
||||
// instead of needing manual re-entry.
|
||||
for (var attempt = 0; attempt < 2 && _looksIncomplete(files); attempt++) {
|
||||
await Future<void>.delayed(const Duration(milliseconds: 700));
|
||||
files = await _fetch(webdav, prop, timeout);
|
||||
}
|
||||
|
||||
return ListFilesResponse(files);
|
||||
}
|
||||
|
||||
Future<Set<CacheableFile>> _fetch(
|
||||
WebDavClient webdav,
|
||||
WebDavPropWithoutValues prop,
|
||||
Duration timeout,
|
||||
) async {
|
||||
final davFiles =
|
||||
(await webdav
|
||||
.propfind(PathUri.parse(params.path), prop: prop)
|
||||
.timeout(timeout))
|
||||
.toWebDavFiles();
|
||||
final files = davFiles.map(CacheableFile.fromDavFile).toSet();
|
||||
|
||||
// somehow the current working folder is also listed, it is filtered here.
|
||||
files.removeWhere(
|
||||
(element) => element.path == '/${params.path}/' || element.path == '/',
|
||||
);
|
||||
|
||||
return ListFilesResponse(files);
|
||||
return files;
|
||||
}
|
||||
|
||||
/// True when the server returned entries but none carry a `fileId` — a sign
|
||||
/// the OC/NC properties were omitted (cold share mount), so thumbnails and
|
||||
/// share badges would be missing for the whole folder.
|
||||
bool _looksIncomplete(Set<CacheableFile> files) =>
|
||||
files.isNotEmpty && files.every((file) => file.fileId == null);
|
||||
}
|
||||
|
||||
@@ -40,9 +40,7 @@ class ListFilesCache extends SimpleCache<ListFilesResponse> {
|
||||
/// [invalidate].
|
||||
static int _cacheTimeFor(String path) {
|
||||
final stripped = path.replaceAll('/', '').trim();
|
||||
return stripped.isEmpty
|
||||
? RequestCache.cacheDay
|
||||
: RequestCache.cacheNothing;
|
||||
return stripped.isEmpty ? RequestCache.cacheDay : RequestCache.cacheNothing;
|
||||
}
|
||||
|
||||
/// Triggers a root-listing fetch in the background if no cached payload
|
||||
|
||||
Reference in New Issue
Block a user