diff --git a/lib/api/api.dart b/lib/api/api.dart index 022807bc..4f19efe0 100644 --- a/lib/api/api.dart +++ b/lib/api/api.dart @@ -538,10 +538,12 @@ class _OcsFilesSharingShares { /// Get Shares from a specific file or folder /// /// See: https://docs.nextcloud.com/server/latest/developer_manual/client_apis/OCS/ocs-share-api.html#get-shares-from-a-specific-file-or-folder + /// See: https://doc.owncloud.com/server/latest/developer_manual/core/apis/ocs-share-api.html#get-all-shares Future get({ String? path, bool? reshares, bool? subfiles, + bool? sharedWithMe, }) async { try { return await _filesSharing._ocs._api.request( @@ -555,6 +557,7 @@ class _OcsFilesSharingShares { if (path != null) "path": path, if (reshares != null) "reshares": reshares.toString(), if (subfiles != null) "subfiles": subfiles.toString(), + if (sharedWithMe != null) "shared_with_me": sharedWithMe.toString(), }, ); } catch (e) { diff --git a/lib/bloc/list_sharing.dart b/lib/bloc/list_sharing.dart index a73369cc..b84dce9c 100644 --- a/lib/bloc/list_sharing.dart +++ b/lib/bloc/list_sharing.dart @@ -174,6 +174,15 @@ class ListSharingBloc extends Bloc { } Future> _query(ListSharingBlocQuery ev) async { + return (await Future.wait([ + _querySharesByMe(ev), + _querySharesWithMe(ev), + ])) + .reduce((value, element) => value + element); + } + + Future> _querySharesByMe( + ListSharingBlocQuery ev) async { final shareRepo = ShareRepo(ShareRemoteDataSource()); final shares = await shareRepo.listAll(ev.account); final futures = shares.map((e) async { @@ -206,7 +215,32 @@ class ListSharingBloc extends Bloc { final file = await FindFile()(ev.account, e.itemSource); return ListSharingItem(e, file); } catch (_) { - _log.warning("[_query] File not found: ${e.itemSource}"); + _log.warning("[_querySharesByMe] File not found: ${e.itemSource}"); + return null; + } + }); + return (await Future.wait(futures)).whereType().toList(); + } + + Future> _querySharesWithMe( + ListSharingBlocQuery ev) async { + final shareRepo = ShareRepo(ShareRemoteDataSource()); + final shares = await shareRepo.reverseListAll(ev.account); + final futures = shares.map((e) async { + if (!file_util.isSupportedMime(e.mimeType)) { + return null; + } + if (ev.account.roots + .every((r) => r.isNotEmpty && !e.path.startsWith("/$r/"))) { + // ignore files not under root dirs + return null; + } + + try { + final file = await FindFile()(ev.account, e.itemSource); + return ListSharingItem(e, file); + } catch (_) { + _log.warning("[_querySharesWithMe] File not found: ${e.itemSource}"); return null; } }); diff --git a/lib/entity/share.dart b/lib/entity/share.dart index 87e769cd..22f35acd 100644 --- a/lib/entity/share.dart +++ b/lib/entity/share.dart @@ -88,6 +88,8 @@ class Share with EquatableMixin { required this.id, required this.shareType, required this.stime, + required this.uidOwner, + required this.displaynameOwner, required String path, required this.itemType, required this.mimeType, @@ -103,6 +105,8 @@ class Share with EquatableMixin { "id: $id, " "shareType: $shareType, " "stime: $stime, " + "uidOwner: $uidOwner, " + "displaynameOwner: $displaynameOwner, " "path: $path, " "itemType: $itemType, " "mimeType: $mimeType, " @@ -118,6 +122,8 @@ class Share with EquatableMixin { id, shareType, stime, + uidOwner, + displaynameOwner, path, itemType, mimeType, @@ -131,6 +137,8 @@ class Share with EquatableMixin { final String id; final ShareType shareType; final DateTime stime; + final String uidOwner; + final String displaynameOwner; final String path; final ShareItemType itemType; final String mimeType; @@ -158,6 +166,10 @@ class ShareRepo { /// See [ShareDataSource.listAll] Future> listAll(Account account) => dataSrc.listAll(account); + /// See [ShareDataSource.reverseListAll] + Future> reverseListAll(Account account) => + dataSrc.reverseListAll(account); + /// See [ShareDataSource.create] Future create(Account account, File file, String shareWith) => dataSrc.create(account, file, shareWith); @@ -187,6 +199,9 @@ abstract class ShareDataSource { /// List all shares from a given user Future> listAll(Account account); + /// List all shares by other users with a given user + Future> reverseListAll(Account account); + /// Share a file/folder with a user Future create(Account account, File file, String shareWith); diff --git a/lib/entity/share/data_source.dart b/lib/entity/share/data_source.dart index 7b1b3048..a9c526f8 100644 --- a/lib/entity/share/data_source.dart +++ b/lib/entity/share/data_source.dart @@ -6,6 +6,7 @@ import 'package:nc_photos/api/api.dart'; import 'package:nc_photos/entity/file.dart'; import 'package:nc_photos/entity/share.dart'; import 'package:nc_photos/exception.dart'; +import 'package:nc_photos/iterable_extension.dart'; import 'package:nc_photos/type.dart'; class ShareRemoteDataSource implements ShareDataSource { @@ -35,6 +36,15 @@ class ShareRemoteDataSource implements ShareDataSource { return _onListResult(response); } + @override + reverseListAll(Account account) async { + _log.info("[reverseListAll] $account"); + final response = await Api(account).ocs().filesSharing().shares().get( + sharedWithMe: true, + ); + return _onListResult(response); + } + @override create(Account account, File file, String shareWith) async { _log.info("[create] Share '${file.path}' with '$shareWith'"); @@ -127,8 +137,9 @@ class _ShareParser { return Share( id: json["id"], shareType: shareType, - stime: - DateTime.fromMillisecondsSinceEpoch(json["stime"] * 1000), + stime: DateTime.fromMillisecondsSinceEpoch(json["stime"] * 1000), + uidOwner: json["uid_owner"], + displaynameOwner: json["displayname_owner"], path: json["path"], itemType: itemType, mimeType: json["mimetype"], diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 3e08da35..bb7a1163 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -869,6 +869,19 @@ } } }, + "fileLastSharedByOthersDescription": "{user} shared with you on {date}", + "@fileLastSharedByOthersDescription": { + "description": "The date when this file was shared with you", + "placeholders": { + "user": { + "example": "Alice" + }, + "date": { + "example": "Jan 1, 2021", + "description": "The date string is formatted according to the current locale" + } + } + }, "sharedWithLabel": "Shared with", "@sharedWithLabel": { "description": "A list of users or links where this file is sharing with" diff --git a/lib/l10n/untranslated-messages.txt b/lib/l10n/untranslated-messages.txt index 42e4d032..08497510 100644 --- a/lib/l10n/untranslated-messages.txt +++ b/lib/l10n/untranslated-messages.txt @@ -8,6 +8,7 @@ "sortOptionManualLabel", "collectionSharingLabel", "fileLastSharedDescription", + "fileLastSharedByOthersDescription", "sharedWithLabel", "unshareTooltip", "unshareSuccessNotification", @@ -45,6 +46,7 @@ "shareMethodPasswordLinkDescription", "collectionSharingLabel", "fileLastSharedDescription", + "fileLastSharedByOthersDescription", "sharedWithLabel", "unshareTooltip", "unshareSuccessNotification", @@ -137,6 +139,7 @@ "shareMethodPasswordLinkDescription", "collectionSharingLabel", "fileLastSharedDescription", + "fileLastSharedByOthersDescription", "sharedWithLabel", "unshareTooltip", "unshareSuccessNotification", @@ -160,6 +163,7 @@ "sortOptionManualLabel", "collectionSharingLabel", "fileLastSharedDescription", + "fileLastSharedByOthersDescription", "sharedWithLabel", "unshareTooltip", "unshareSuccessNotification", @@ -232,6 +236,7 @@ "shareMethodPasswordLinkDescription", "collectionSharingLabel", "fileLastSharedDescription", + "fileLastSharedByOthersDescription", "sharedWithLabel", "unshareTooltip", "unshareSuccessNotification", @@ -277,6 +282,7 @@ "shareMethodPasswordLinkDescription", "collectionSharingLabel", "fileLastSharedDescription", + "fileLastSharedByOthersDescription", "sharedWithLabel", "unshareTooltip", "unshareSuccessNotification", diff --git a/lib/widget/shared_file_viewer.dart b/lib/widget/shared_file_viewer.dart index f731afec..2cb2ead7 100644 --- a/lib/widget/shared_file_viewer.dart +++ b/lib/widget/shared_file_viewer.dart @@ -127,23 +127,25 @@ class _SharedFileViewerState extends State { title: Text(widget.file.strippedPath), ), ), - SliverToBoxAdapter( - child: Padding( - padding: const EdgeInsets.all(16), - child: Text( - L10n.global().sharedWithLabel, - style: TextStyle( - color: Theme.of(context).colorScheme.primary, + if (widget.shares.first.uidOwner == widget.account.username) ...[ + SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.all(16), + child: Text( + L10n.global().sharedWithLabel, + style: TextStyle( + color: Theme.of(context).colorScheme.primary, + ), ), ), ), - ), - SliverList( - delegate: SliverChildBuilderDelegate( - (context, index) => _buildShareItem(context, _shares[index]), - childCount: _shares.length, + SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) => _buildShareItem(context, _shares[index]), + childCount: _shares.length, + ), ), - ), + ], ], ); } diff --git a/lib/widget/sharing_browser.dart b/lib/widget/sharing_browser.dart index c1fcf209..8222f60f 100644 --- a/lib/widget/sharing_browser.dart +++ b/lib/widget/sharing_browser.dart @@ -201,7 +201,10 @@ class _SharingBrowserState extends State { overflow: TextOverflow.ellipsis, ), Text( - L10n.global().fileLastSharedDescription(dateStr), + shares.first.share.uidOwner == widget.account.username + ? L10n.global().fileLastSharedDescription(dateStr) + : L10n.global().fileLastSharedByOthersDescription( + shares.first.share.displaynameOwner, dateStr), style: TextStyle( color: AppTheme.getSecondaryTextColor(context), ), @@ -238,8 +241,10 @@ class _SharingBrowserState extends State { // group shares of the same file final map = >{}; for (final i in items) { - map[i.share.path] ??= []; - map[i.share.path]!.add(i); + final isSharedByMe = i.share.uidOwner == widget.account.username; + final groupKey = "${i.share.path}?$isSharedByMe"; + map[groupKey] ??= []; + map[groupKey]!.add(i); } // sort the sub-lists for (final list in map.values) {