mirror of
https://gitlab.com/nkming2/nc-photos.git
synced 2025-02-24 18:38:48 +01:00
Revamp how share is managed for album
Shares are now stored in the album json, such that users other than the album owner are aware of the shares
This commit is contained in:
parent
b9655c66c2
commit
6e9a34342a
12 changed files with 598 additions and 259 deletions
|
@ -1,20 +1,85 @@
|
|||
import 'package:bloc/bloc.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:nc_photos/account.dart';
|
||||
import 'package:nc_photos/debug_util.dart';
|
||||
import 'package:nc_photos/entity/album.dart';
|
||||
import 'package:nc_photos/entity/album/item.dart';
|
||||
import 'package:nc_photos/entity/album/provider.dart';
|
||||
import 'package:nc_photos/entity/file.dart';
|
||||
import 'package:nc_photos/entity/share.dart';
|
||||
import 'package:nc_photos/entity/sharee.dart';
|
||||
import 'package:nc_photos/iterable_extension.dart';
|
||||
import 'package:nc_photos/object_extension.dart';
|
||||
import 'package:nc_photos/string_extension.dart';
|
||||
import 'package:nc_photos/use_case/list_share.dart';
|
||||
import 'package:nc_photos/use_case/list_sharee.dart';
|
||||
|
||||
class ListAlbumShareOutlierItem {
|
||||
const ListAlbumShareOutlierItem(this.file, this.shares);
|
||||
class ListAlbumShareOutlierItem with EquatableMixin {
|
||||
const ListAlbumShareOutlierItem(this.file, this.shareItems);
|
||||
|
||||
@override
|
||||
toString() {
|
||||
return "$runtimeType {"
|
||||
"file: '${file.path}', "
|
||||
"shareItems: ${shareItems.toReadableString()}, "
|
||||
"}";
|
||||
}
|
||||
|
||||
@override
|
||||
get props => [
|
||||
file,
|
||||
shareItems,
|
||||
];
|
||||
|
||||
final File file;
|
||||
final List<Share> shares;
|
||||
final List<ListAlbumShareOutlierShareItem> shareItems;
|
||||
}
|
||||
|
||||
abstract class ListAlbumShareOutlierShareItem with EquatableMixin {
|
||||
const ListAlbumShareOutlierShareItem();
|
||||
}
|
||||
|
||||
class ListAlbumShareOutlierExtraShareItem
|
||||
extends ListAlbumShareOutlierShareItem {
|
||||
const ListAlbumShareOutlierExtraShareItem(this.share);
|
||||
|
||||
@override
|
||||
toString() {
|
||||
return "$runtimeType {"
|
||||
"share: $share, "
|
||||
"}";
|
||||
}
|
||||
|
||||
@override
|
||||
get props => [
|
||||
share,
|
||||
];
|
||||
|
||||
final Share share;
|
||||
}
|
||||
|
||||
class ListAlbumShareOutlierMissingShareItem
|
||||
extends ListAlbumShareOutlierShareItem {
|
||||
const ListAlbumShareOutlierMissingShareItem(
|
||||
this.shareWith, this.shareWithDisplayName);
|
||||
|
||||
@override
|
||||
toString() {
|
||||
return "$runtimeType {"
|
||||
"shareWith: $shareWith, "
|
||||
"shareWithDisplayName: $shareWithDisplayName, "
|
||||
"}";
|
||||
}
|
||||
|
||||
@override
|
||||
get props => [
|
||||
shareWith,
|
||||
shareWithDisplayName,
|
||||
];
|
||||
|
||||
final String shareWith;
|
||||
final String? shareWithDisplayName;
|
||||
}
|
||||
|
||||
abstract class ListAlbumShareOutlierBlocEvent {
|
||||
|
@ -36,47 +101,47 @@ class ListAlbumShareOutlierBlocQuery extends ListAlbumShareOutlierBlocEvent {
|
|||
final Album album;
|
||||
}
|
||||
|
||||
abstract class ListAlbumShareOutlierBlocState {
|
||||
const ListAlbumShareOutlierBlocState(
|
||||
this.account, this.albumShares, this.items);
|
||||
abstract class ListAlbumShareOutlierBlocState with EquatableMixin {
|
||||
const ListAlbumShareOutlierBlocState(this.account, this.items);
|
||||
|
||||
@override
|
||||
toString() {
|
||||
return "$runtimeType {"
|
||||
"account: $account, "
|
||||
"albumShares: List {length: ${albumShares.length}}, "
|
||||
"items: List {length: ${items.length}}, "
|
||||
"items: ${items.toReadableString()}, "
|
||||
"}";
|
||||
}
|
||||
|
||||
@override
|
||||
get props => [
|
||||
account,
|
||||
items,
|
||||
];
|
||||
|
||||
final Account? account;
|
||||
final List<Share> albumShares;
|
||||
final List<ListAlbumShareOutlierItem> items;
|
||||
}
|
||||
|
||||
class ListAlbumShareOutlierBlocInit extends ListAlbumShareOutlierBlocState {
|
||||
ListAlbumShareOutlierBlocInit() : super(null, const [], const []);
|
||||
ListAlbumShareOutlierBlocInit() : super(null, const []);
|
||||
}
|
||||
|
||||
class ListAlbumShareOutlierBlocLoading extends ListAlbumShareOutlierBlocState {
|
||||
const ListAlbumShareOutlierBlocLoading(Account? account,
|
||||
List<Share> albumShares, List<ListAlbumShareOutlierItem> items)
|
||||
: super(account, albumShares, items);
|
||||
const ListAlbumShareOutlierBlocLoading(
|
||||
Account? account, List<ListAlbumShareOutlierItem> items)
|
||||
: super(account, items);
|
||||
}
|
||||
|
||||
class ListAlbumShareOutlierBlocSuccess extends ListAlbumShareOutlierBlocState {
|
||||
const ListAlbumShareOutlierBlocSuccess(Account? account,
|
||||
List<Share> albumShares, List<ListAlbumShareOutlierItem> items)
|
||||
: super(account, albumShares, items);
|
||||
const ListAlbumShareOutlierBlocSuccess(
|
||||
Account? account, List<ListAlbumShareOutlierItem> items)
|
||||
: super(account, items);
|
||||
}
|
||||
|
||||
class ListAlbumShareOutlierBlocFailure extends ListAlbumShareOutlierBlocState {
|
||||
const ListAlbumShareOutlierBlocFailure(
|
||||
Account? account,
|
||||
List<Share> albumShares,
|
||||
List<ListAlbumShareOutlierItem> items,
|
||||
this.exception)
|
||||
: super(account, albumShares, items);
|
||||
Account? account, List<ListAlbumShareOutlierItem> items, this.exception)
|
||||
: super(account, items);
|
||||
|
||||
@override
|
||||
toString() {
|
||||
|
@ -86,6 +151,12 @@ class ListAlbumShareOutlierBlocFailure extends ListAlbumShareOutlierBlocState {
|
|||
"}";
|
||||
}
|
||||
|
||||
@override
|
||||
get props => [
|
||||
...super.props,
|
||||
exception,
|
||||
];
|
||||
|
||||
final dynamic exception;
|
||||
}
|
||||
|
||||
|
@ -95,7 +166,7 @@ class ListAlbumShareOutlierBlocFailure extends ListAlbumShareOutlierBlocState {
|
|||
/// belongs, e.g., an unshared item in a shared album, or vice versa
|
||||
class ListAlbumShareOutlierBloc extends Bloc<ListAlbumShareOutlierBlocEvent,
|
||||
ListAlbumShareOutlierBlocState> {
|
||||
ListAlbumShareOutlierBloc(this.shareRepo)
|
||||
ListAlbumShareOutlierBloc(this.shareRepo, this.shareeRepo)
|
||||
: super(ListAlbumShareOutlierBlocInit());
|
||||
|
||||
@override
|
||||
|
@ -110,54 +181,150 @@ class ListAlbumShareOutlierBloc extends Bloc<ListAlbumShareOutlierBlocEvent,
|
|||
ListAlbumShareOutlierBlocQuery ev) async* {
|
||||
try {
|
||||
assert(ev.album.provider is AlbumStaticProvider);
|
||||
yield ListAlbumShareOutlierBlocLoading(
|
||||
ev.account, state.albumShares, state.items);
|
||||
yield ListAlbumShareOutlierBlocLoading(ev.account, state.items);
|
||||
|
||||
final albumShares =
|
||||
(await ListShare(shareRepo)(ev.account, ev.album.albumFile!))
|
||||
.where((element) => element.shareWith != null)
|
||||
.sorted((a, b) => a.shareWith!.compareTo(b.shareWith!));
|
||||
final albumSharees = albumShares
|
||||
.where((element) => element.shareType == ShareType.user)
|
||||
.map((e) => e.shareWith!)
|
||||
.sorted();
|
||||
final files = AlbumStaticProvider.of(ev.album)
|
||||
.items
|
||||
.whereType<AlbumFileItem>()
|
||||
.map((e) => e.file)
|
||||
.toList();
|
||||
final products = <ListAlbumShareOutlierItem>[];
|
||||
Object? error;
|
||||
for (final f in files) {
|
||||
try {
|
||||
final shares = (await ListShare(shareRepo)(ev.account, f))
|
||||
.where((element) => element.shareType == ShareType.user)
|
||||
.toList();
|
||||
final sharees = shares.map((e) => e.shareWith!).sorted();
|
||||
if (!listEquals(sharees, albumSharees)) {
|
||||
products.add(ListAlbumShareOutlierItem(f, shares));
|
||||
}
|
||||
} catch (e, stackTrace) {
|
||||
_log.severe("[_query] Failed while listing share for file: ${f.path}",
|
||||
e, stackTrace);
|
||||
error = e;
|
||||
final albumShares = await () async {
|
||||
var temp = (ev.album.shares ?? [])
|
||||
.where((s) => !s.userId.equalsIgnoreCase(ev.account.username))
|
||||
.toList();
|
||||
if (ev.album.albumFile!.ownerId != ev.account.username) {
|
||||
// add owner if the album is not owned by this account
|
||||
final ownerSharee = (await ListSharee(shareeRepo)(ev.account))
|
||||
.firstWhere((s) => s.shareWith == ev.album.albumFile!.ownerId);
|
||||
temp.add(AlbumShare(
|
||||
userId: ownerSharee.shareWith,
|
||||
displayName: ownerSharee.shareWithDisplayNameUnique,
|
||||
));
|
||||
}
|
||||
}
|
||||
if (error == null) {
|
||||
yield ListAlbumShareOutlierBlocSuccess(
|
||||
ev.account, albumShares, products);
|
||||
return Map.fromEntries(
|
||||
temp.map((as) => MapEntry(as.userId.toLowerCase(), as)));
|
||||
}();
|
||||
final albumSharees =
|
||||
albumShares.values.map((s) => s.userId.toLowerCase()).toSet();
|
||||
|
||||
final products = <ListAlbumShareOutlierItem>[];
|
||||
final errors = <Object>[];
|
||||
products.addAll(await _processAlbumFile(
|
||||
ev.account, ev.album, albumShares, albumSharees, errors));
|
||||
products.addAll(await _processAlbumItems(
|
||||
ev.account, ev.album, albumShares, albumSharees, errors));
|
||||
|
||||
if (errors.isEmpty) {
|
||||
yield ListAlbumShareOutlierBlocSuccess(ev.account, products);
|
||||
} else {
|
||||
yield ListAlbumShareOutlierBlocFailure(
|
||||
ev.account, albumShares, products, error);
|
||||
ev.account, products, errors.first);
|
||||
}
|
||||
} catch (e, stackTrace) {
|
||||
_log.severe("[_onEventQuery] Exception while request", e, stackTrace);
|
||||
yield ListAlbumShareOutlierBlocFailure(
|
||||
ev.account, state.albumShares, state.items, e);
|
||||
yield ListAlbumShareOutlierBlocFailure(ev.account, state.items, e);
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<ListAlbumShareOutlierItem>> _processAlbumFile(
|
||||
Account account,
|
||||
Album album,
|
||||
Map<String, AlbumShare> albumShares,
|
||||
Set<String> albumSharees,
|
||||
List<Object> errors,
|
||||
) async {
|
||||
try {
|
||||
final item = await _processSingleFile(
|
||||
account, album.albumFile!, albumShares, albumSharees, errors);
|
||||
return item == null ? [] : [item];
|
||||
} catch (e, stackTrace) {
|
||||
_log.severe(
|
||||
"[_processAlbumFile] Failed while _processSingleFile: ${logFilename(album.albumFile?.path)}",
|
||||
e,
|
||||
stackTrace);
|
||||
errors.add(e);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<ListAlbumShareOutlierItem>> _processAlbumItems(
|
||||
Account account,
|
||||
Album album,
|
||||
Map<String, AlbumShare> albumShares,
|
||||
Set<String> albumSharees,
|
||||
List<Object> errors,
|
||||
) async {
|
||||
final products = <ListAlbumShareOutlierItem>[];
|
||||
final files = AlbumStaticProvider.of(album)
|
||||
.items
|
||||
.whereType<AlbumFileItem>()
|
||||
.map((e) => e.file)
|
||||
.toList();
|
||||
for (final f in files) {
|
||||
try {
|
||||
(await _processSingleFile(
|
||||
account, f, albumShares, albumSharees, errors))
|
||||
?.apply((item) {
|
||||
products.add(item);
|
||||
});
|
||||
} catch (e, stackTrace) {
|
||||
_log.severe(
|
||||
"[_processAlbumItems] Failed while _processSingleFile: ${logFilename(f.path)}",
|
||||
e,
|
||||
stackTrace);
|
||||
errors.add(e);
|
||||
}
|
||||
}
|
||||
return products;
|
||||
}
|
||||
|
||||
Future<ListAlbumShareOutlierItem?> _processSingleFile(
|
||||
Account account,
|
||||
File file,
|
||||
Map<String, AlbumShare> albumShares,
|
||||
Set<String> albumSharees,
|
||||
List<Object> errors,
|
||||
) async {
|
||||
final shareItems = <ListAlbumShareOutlierShareItem>[];
|
||||
final shares = (await ListShare(shareRepo)(account, file))
|
||||
.where((element) => element.shareType == ShareType.user)
|
||||
.toList();
|
||||
final sharees = shares.map((s) => s.shareWith!.toLowerCase()).toSet();
|
||||
final missings = albumSharees.difference(sharees);
|
||||
_log.info(
|
||||
"Missing shares: ${missings.toReadableString()} for file: ${logFilename(file.path)}");
|
||||
for (final m in missings) {
|
||||
try {
|
||||
final as = albumShares[m.toLowerCase()]!;
|
||||
shareItems.add(
|
||||
ListAlbumShareOutlierMissingShareItem(as.userId, as.displayName));
|
||||
} catch (e, stackTrace) {
|
||||
_log.severe(
|
||||
"[_processSingleFile] Failed while processing missing share for file: ${logFilename(file.path)}",
|
||||
e,
|
||||
stackTrace);
|
||||
errors.add(e);
|
||||
}
|
||||
}
|
||||
final extras = sharees.difference(albumSharees);
|
||||
_log.info(
|
||||
"Extra shares: ${extras.toReadableString()} for file: ${logFilename(file.path)}");
|
||||
for (final e in extras) {
|
||||
try {
|
||||
shareItems.add(ListAlbumShareOutlierExtraShareItem(shares
|
||||
.firstWhere((s) => s.shareWith?.equalsIgnoreCase(e) == true)));
|
||||
} catch (e, stackTrace) {
|
||||
_log.severe(
|
||||
"[_processSingleFile] Failed while processing extra share for file: ${logFilename(file.path)}",
|
||||
e,
|
||||
stackTrace);
|
||||
errors.add(e);
|
||||
}
|
||||
}
|
||||
if (shareItems.isNotEmpty) {
|
||||
return ListAlbumShareOutlierItem(file, shareItems);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
final ShareRepo shareRepo;
|
||||
final ShareeRepo shareeRepo;
|
||||
|
||||
static final _log =
|
||||
Logger("bloc.list_album_share_outlier.ListAlbumShareOutlierBloc");
|
||||
|
|
|
@ -18,6 +18,7 @@ import 'package:nc_photos/int_util.dart' as int_util;
|
|||
import 'package:nc_photos/iterable_extension.dart';
|
||||
import 'package:nc_photos/or_null.dart';
|
||||
import 'package:nc_photos/remote_storage_util.dart' as remote_storage_util;
|
||||
import 'package:nc_photos/string_extension.dart';
|
||||
import 'package:nc_photos/type.dart';
|
||||
import 'package:nc_photos/use_case/get_file_binary.dart';
|
||||
import 'package:nc_photos/use_case/ls.dart';
|
||||
|
@ -36,6 +37,7 @@ class Album with EquatableMixin {
|
|||
required this.provider,
|
||||
required this.coverProvider,
|
||||
required this.sortProvider,
|
||||
this.shares,
|
||||
this.albumFile,
|
||||
}) : lastUpdated = (lastUpdated ?? DateTime.now()).toUtc();
|
||||
|
||||
|
@ -95,6 +97,9 @@ class Album with EquatableMixin {
|
|||
result["coverProvider"].cast<String, dynamic>()),
|
||||
sortProvider: AlbumSortProvider.fromJson(
|
||||
result["sortProvider"].cast<String, dynamic>()),
|
||||
shares: (result["shares"] as List?)
|
||||
?.map((e) => AlbumShare.fromJson(e.cast<String, dynamic>()))
|
||||
.toList(),
|
||||
albumFile: result["albumFile"] == null
|
||||
? null
|
||||
: File.fromJson(result["albumFile"].cast<String, dynamic>()),
|
||||
|
@ -109,6 +114,7 @@ class Album with EquatableMixin {
|
|||
"provider: ${provider.toString(isDeep: isDeep)}, "
|
||||
"coverProvider: $coverProvider, "
|
||||
"sortProvider: $sortProvider, "
|
||||
"shares: ${shares?.toReadableString()}, "
|
||||
"albumFile: $albumFile, "
|
||||
"}";
|
||||
}
|
||||
|
@ -124,6 +130,7 @@ class Album with EquatableMixin {
|
|||
AlbumProvider? provider,
|
||||
AlbumCoverProvider? coverProvider,
|
||||
AlbumSortProvider? sortProvider,
|
||||
OrNull<List<AlbumShare>>? shares,
|
||||
OrNull<File>? albumFile,
|
||||
}) {
|
||||
return Album(
|
||||
|
@ -133,6 +140,7 @@ class Album with EquatableMixin {
|
|||
provider: provider ?? this.provider,
|
||||
coverProvider: coverProvider ?? this.coverProvider,
|
||||
sortProvider: sortProvider ?? this.sortProvider,
|
||||
shares: shares == null ? this.shares : shares.obj,
|
||||
albumFile: albumFile == null ? this.albumFile : albumFile.obj,
|
||||
);
|
||||
}
|
||||
|
@ -145,6 +153,7 @@ class Album with EquatableMixin {
|
|||
"provider": provider.toJson(),
|
||||
"coverProvider": coverProvider.toJson(),
|
||||
"sortProvider": sortProvider.toJson(),
|
||||
if (shares != null) "shares": shares!.map((e) => e.toJson()).toList(),
|
||||
// ignore albumFile
|
||||
};
|
||||
}
|
||||
|
@ -157,6 +166,7 @@ class Album with EquatableMixin {
|
|||
"provider": provider.toJson(),
|
||||
"coverProvider": coverProvider.toJson(),
|
||||
"sortProvider": sortProvider.toJson(),
|
||||
if (shares != null) "shares": shares!.map((e) => e.toJson()).toList(),
|
||||
if (albumFile != null) "albumFile": albumFile!.toJson(),
|
||||
};
|
||||
}
|
||||
|
@ -168,6 +178,7 @@ class Album with EquatableMixin {
|
|||
provider,
|
||||
coverProvider,
|
||||
sortProvider,
|
||||
shares,
|
||||
albumFile,
|
||||
];
|
||||
|
||||
|
@ -177,6 +188,7 @@ class Album with EquatableMixin {
|
|||
final AlbumProvider provider;
|
||||
final AlbumCoverProvider coverProvider;
|
||||
final AlbumSortProvider sortProvider;
|
||||
final List<AlbumShare>? shares;
|
||||
|
||||
/// How is this album stored on server
|
||||
///
|
||||
|
@ -187,6 +199,50 @@ class Album with EquatableMixin {
|
|||
static const version = 6;
|
||||
}
|
||||
|
||||
class AlbumShare {
|
||||
const AlbumShare({
|
||||
required this.userId,
|
||||
this.displayName,
|
||||
});
|
||||
|
||||
factory AlbumShare.fromJson(JsonObj json) {
|
||||
return AlbumShare(
|
||||
userId: json["userId"],
|
||||
displayName: json["displayName"],
|
||||
);
|
||||
}
|
||||
|
||||
JsonObj toJson() {
|
||||
return {
|
||||
"userId": userId,
|
||||
if (displayName != null) "displayName": displayName,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
other is AlbumShare &&
|
||||
runtimeType == other.runtimeType &&
|
||||
userId.equalsIgnoreCase(other.userId);
|
||||
}
|
||||
|
||||
@override
|
||||
get hashCode => userId.toLowerCase().hashCode;
|
||||
|
||||
@override
|
||||
toString() {
|
||||
return "$runtimeType {"
|
||||
"userId: $userId, "
|
||||
"displayName: $displayName, "
|
||||
"}";
|
||||
}
|
||||
|
||||
/// User ID or username, case insensitive
|
||||
final String userId;
|
||||
final String? displayName;
|
||||
}
|
||||
|
||||
class AlbumRepo {
|
||||
AlbumRepo(this.dataSrc);
|
||||
|
||||
|
|
6
lib/object_extension.dart
Normal file
6
lib/object_extension.dart
Normal file
|
@ -0,0 +1,6 @@
|
|||
extension ObjectExtension<T> on T {
|
||||
T apply(void Function(T obj) fn) {
|
||||
fn(this);
|
||||
return this;
|
||||
}
|
||||
}
|
|
@ -8,6 +8,7 @@ import 'package:nc_photos/entity/album/provider.dart';
|
|||
import 'package:nc_photos/entity/file.dart';
|
||||
import 'package:nc_photos/entity/share.dart';
|
||||
import 'package:nc_photos/pref.dart';
|
||||
import 'package:nc_photos/string_extension.dart';
|
||||
import 'package:nc_photos/use_case/create_share.dart';
|
||||
import 'package:nc_photos/use_case/list_share.dart';
|
||||
import 'package:nc_photos/use_case/preprocess_album.dart';
|
||||
|
@ -55,44 +56,39 @@ class AddToAlbum {
|
|||
|
||||
Future<void> _shareFiles(
|
||||
Account account, Album album, List<File> files) async {
|
||||
try {
|
||||
final albumShares =
|
||||
(await ListShare(shareRepo)(account, album.albumFile!))
|
||||
.where((element) => element.shareType == ShareType.user)
|
||||
.map((e) => e.shareWith!)
|
||||
.toSet();
|
||||
if (albumShares.isEmpty) {
|
||||
return;
|
||||
}
|
||||
for (final f in files) {
|
||||
try {
|
||||
final fileShares = (await ListShare(shareRepo)(account, f))
|
||||
.where((element) => element.shareType == ShareType.user)
|
||||
.map((e) => e.shareWith!)
|
||||
.toSet();
|
||||
final diffShares = albumShares.difference(fileShares);
|
||||
for (final s in diffShares) {
|
||||
try {
|
||||
await CreateUserShare(shareRepo)(account, f, s);
|
||||
} catch (e, stackTrace) {
|
||||
_log.shout(
|
||||
"[_shareFiles] Failed while CreateUserShare: ${logFilename(f.path)}",
|
||||
e,
|
||||
stackTrace);
|
||||
}
|
||||
if (album.shares?.isNotEmpty != true) {
|
||||
return;
|
||||
}
|
||||
final albumShares = (album.shares!.map((e) => e.userId).toList()
|
||||
..add(album.albumFile!.ownerId ?? account.username))
|
||||
.where((element) => !element.equalsIgnoreCase(account.username))
|
||||
.toSet();
|
||||
if (albumShares.isEmpty) {
|
||||
return;
|
||||
}
|
||||
for (final f in files) {
|
||||
try {
|
||||
final fileShares = (await ListShare(shareRepo)(account, f))
|
||||
.where((element) => element.shareType == ShareType.user)
|
||||
.map((e) => e.shareWith!)
|
||||
.toSet();
|
||||
final diffShares = albumShares.difference(fileShares);
|
||||
for (final s in diffShares) {
|
||||
try {
|
||||
await CreateUserShare(shareRepo)(account, f, s);
|
||||
} catch (e, stackTrace) {
|
||||
_log.shout(
|
||||
"[_shareFiles] Failed while CreateUserShare: ${logFilename(f.path)}",
|
||||
e,
|
||||
stackTrace);
|
||||
}
|
||||
} catch (e, stackTrace) {
|
||||
_log.shout(
|
||||
"[_shareFiles] Failed while listing shares: ${logFilename(f.path)}",
|
||||
e,
|
||||
stackTrace);
|
||||
}
|
||||
} catch (e, stackTrace) {
|
||||
_log.shout(
|
||||
"[_shareFiles] Failed while listing shares: ${logFilename(f.path)}",
|
||||
e,
|
||||
stackTrace);
|
||||
}
|
||||
} catch (e, stackTrace) {
|
||||
_log.shout(
|
||||
"[_shareFiles] Failed while listing album shares: ${logFilename(album.albumFile?.path)}",
|
||||
e,
|
||||
stackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,56 +0,0 @@
|
|||
import 'package:nc_photos/account.dart';
|
||||
import 'package:nc_photos/entity/album.dart';
|
||||
import 'package:nc_photos/entity/file.dart';
|
||||
import 'package:nc_photos/entity/share.dart';
|
||||
import 'package:nc_photos/remote_storage_util.dart' as remote_storage_util;
|
||||
import 'package:nc_photos/use_case/list_dir_share.dart';
|
||||
import 'package:nc_photos/use_case/ls.dart';
|
||||
|
||||
class ListSharedAlbumItem {
|
||||
const ListSharedAlbumItem(this.share, this.album);
|
||||
|
||||
final Share share;
|
||||
final Album album;
|
||||
}
|
||||
|
||||
class ListSharedAlbum {
|
||||
const ListSharedAlbum(this.shareRepo, this.fileRepo, this.albumRepo);
|
||||
|
||||
/// List albums that are currently shared by you
|
||||
///
|
||||
/// If [whereShareWith] is not null, only shares sharing with [whereShareWith]
|
||||
/// will be returned.
|
||||
Future<List<ListSharedAlbumItem>> call(
|
||||
Account account, {
|
||||
String? whereShareWith,
|
||||
}) async {
|
||||
final shareItems = await ListDirShare(shareRepo)(
|
||||
account, File(path: remote_storage_util.getRemoteAlbumsDir(account)));
|
||||
final files = await Ls(fileRepo)(
|
||||
account,
|
||||
File(path: remote_storage_util.getRemoteAlbumsDir(account)),
|
||||
);
|
||||
|
||||
final products = <ListSharedAlbumItem>[];
|
||||
for (final si in shareItems) {
|
||||
// find the file
|
||||
final albumFile =
|
||||
files.firstWhere((element) => element.compareServerIdentity(si.file));
|
||||
final album = await albumRepo.get(
|
||||
account,
|
||||
albumFile,
|
||||
);
|
||||
for (final s in si.shares) {
|
||||
if (whereShareWith != null && s.shareWith != whereShareWith) {
|
||||
continue;
|
||||
}
|
||||
products.add(ListSharedAlbumItem(s, album));
|
||||
}
|
||||
}
|
||||
return products;
|
||||
}
|
||||
|
||||
final ShareRepo shareRepo;
|
||||
final FileRepo fileRepo;
|
||||
final AlbumRepo albumRepo;
|
||||
}
|
|
@ -9,7 +9,7 @@ import 'package:nc_photos/entity/file.dart';
|
|||
import 'package:nc_photos/entity/share.dart';
|
||||
import 'package:nc_photos/iterable_extension.dart';
|
||||
import 'package:nc_photos/pref.dart';
|
||||
import 'package:nc_photos/use_case/list_share.dart';
|
||||
import 'package:nc_photos/string_extension.dart';
|
||||
import 'package:nc_photos/use_case/preprocess_album.dart';
|
||||
import 'package:nc_photos/use_case/unshare_file_from_album.dart';
|
||||
import 'package:nc_photos/use_case/update_album.dart';
|
||||
|
@ -43,15 +43,7 @@ class RemoveFromAlbum {
|
|||
final removeFiles =
|
||||
items.whereType<AlbumFileItem>().map((e) => e.file).toList();
|
||||
if (removeFiles.isNotEmpty) {
|
||||
final albumShares =
|
||||
(await ListShare(shareRepo)(account, newAlbum.albumFile!))
|
||||
.where((element) => element.shareType == ShareType.user)
|
||||
.map((e) => e.shareWith!)
|
||||
.toList();
|
||||
if (albumShares.isNotEmpty) {
|
||||
await UnshareFileFromAlbum(shareRepo, fileRepo, albumRepo)(
|
||||
account, newAlbum, removeFiles, albumShares);
|
||||
}
|
||||
await _unshareFiles(account, newAlbum, removeFiles);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -96,6 +88,21 @@ class RemoveFromAlbum {
|
|||
return newAlbum;
|
||||
}
|
||||
|
||||
Future<void> _unshareFiles(
|
||||
Account account, Album album, List<File> files) async {
|
||||
if (album.shares?.isNotEmpty != true) {
|
||||
return;
|
||||
}
|
||||
final albumShares = (album.shares!.map((e) => e.userId).toList()
|
||||
..add(album.albumFile!.ownerId ?? account.username))
|
||||
.where((element) => !element.equalsIgnoreCase(account.username))
|
||||
.toList();
|
||||
if (albumShares.isNotEmpty) {
|
||||
await UnshareFileFromAlbum(shareRepo, fileRepo, albumRepo)(
|
||||
account, album, files, albumShares);
|
||||
}
|
||||
}
|
||||
|
||||
final AlbumRepo albumRepo;
|
||||
final ShareRepo shareRepo;
|
||||
final FileRepo fileRepo;
|
||||
|
|
|
@ -6,30 +6,65 @@ import 'package:nc_photos/entity/album/item.dart';
|
|||
import 'package:nc_photos/entity/album/provider.dart';
|
||||
import 'package:nc_photos/entity/file.dart';
|
||||
import 'package:nc_photos/entity/share.dart';
|
||||
import 'package:nc_photos/entity/sharee.dart';
|
||||
import 'package:nc_photos/or_null.dart';
|
||||
import 'package:nc_photos/use_case/create_share.dart';
|
||||
import 'package:nc_photos/use_case/update_album.dart';
|
||||
|
||||
class ShareAlbumWithUser {
|
||||
ShareAlbumWithUser(this.shareRepo);
|
||||
ShareAlbumWithUser(this.shareRepo, this.albumRepo);
|
||||
|
||||
Future<void> call(
|
||||
Account account,
|
||||
Album album,
|
||||
Sharee sharee, {
|
||||
void Function(File)? onShareFileFailed,
|
||||
}) async {
|
||||
assert(album.provider is AlbumStaticProvider);
|
||||
// add the share to album file
|
||||
final newAlbum = album.copyWith(
|
||||
shares: OrNull((album.shares ?? [])
|
||||
..add(AlbumShare(
|
||||
userId: sharee.shareWith,
|
||||
displayName: sharee.shareWithDisplayNameUnique,
|
||||
))),
|
||||
);
|
||||
await UpdateAlbum(albumRepo)(account, newAlbum);
|
||||
|
||||
await _createFileShares(
|
||||
account,
|
||||
newAlbum,
|
||||
sharee.shareWith,
|
||||
onShareFileFailed: onShareFileFailed,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _createFileShares(
|
||||
Account account,
|
||||
Album album,
|
||||
String shareWith, {
|
||||
void Function(File)? onShareFileFailed,
|
||||
}) async {
|
||||
assert(album.provider is AlbumStaticProvider);
|
||||
final files = AlbumStaticProvider.of(album)
|
||||
.items
|
||||
.whereType<AlbumFileItem>()
|
||||
.map((e) => e.file);
|
||||
await CreateUserShare(shareRepo)(account, album.albumFile!, shareWith);
|
||||
try {
|
||||
await CreateUserShare(shareRepo)(account, album.albumFile!, shareWith);
|
||||
} catch (e, stackTrace) {
|
||||
_log.severe(
|
||||
"[_createFileShares] Failed sharing album file '${logFilename(album.albumFile?.path)}' with '$shareWith'",
|
||||
e,
|
||||
stackTrace);
|
||||
onShareFileFailed?.call(album.albumFile!);
|
||||
}
|
||||
for (final f in files) {
|
||||
_log.info("[call] Sharing '${f.path}' with '$shareWith'");
|
||||
_log.info("[_createFileShares] Sharing '${f.path}' with '$shareWith'");
|
||||
try {
|
||||
await CreateUserShare(shareRepo)(account, f, shareWith);
|
||||
} catch (e, stackTrace) {
|
||||
_log.severe(
|
||||
"[call] Failed sharing file '${logFilename(f.path)}' with '$shareWith'",
|
||||
"[_createFileShares] Failed sharing file '${logFilename(f.path)}' with '$shareWith'",
|
||||
e,
|
||||
stackTrace);
|
||||
onShareFileFailed?.call(f);
|
||||
|
@ -38,6 +73,7 @@ class ShareAlbumWithUser {
|
|||
}
|
||||
|
||||
final ShareRepo shareRepo;
|
||||
final AlbumRepo albumRepo;
|
||||
|
||||
static final _log =
|
||||
Logger("use_case.share_album_with_user.ShareAlbumWithUser");
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
import 'package:logging/logging.dart';
|
||||
import 'package:nc_photos/account.dart';
|
||||
import 'package:nc_photos/debug_util.dart';
|
||||
import 'package:nc_photos/entity/album.dart';
|
||||
import 'package:nc_photos/entity/album/item.dart';
|
||||
import 'package:nc_photos/entity/album/provider.dart';
|
||||
import 'package:nc_photos/entity/file.dart';
|
||||
import 'package:nc_photos/entity/share.dart';
|
||||
import 'package:nc_photos/entity/share/data_source.dart';
|
||||
import 'package:nc_photos/use_case/list_shared_album.dart';
|
||||
import 'package:nc_photos/or_null.dart';
|
||||
import 'package:nc_photos/use_case/list_share.dart';
|
||||
import 'package:nc_photos/use_case/remove_share.dart';
|
||||
import 'package:nc_photos/use_case/unshare_file_from_album.dart';
|
||||
import 'package:nc_photos/use_case/update_album.dart';
|
||||
|
||||
class UnshareAlbumWithUser {
|
||||
UnshareAlbumWithUser(this.shareRepo, this.fileRepo, this.albumRepo);
|
||||
|
@ -19,16 +22,43 @@ class UnshareAlbumWithUser {
|
|||
void Function(Share)? onUnshareFileFailed,
|
||||
}) async {
|
||||
assert(album.provider is AlbumStaticProvider);
|
||||
final shareRepo = ShareRepo(ShareRemoteDataSource());
|
||||
final sharedItems =
|
||||
await ListSharedAlbum(shareRepo, fileRepo, albumRepo)(account);
|
||||
final thisShare = sharedItems
|
||||
.firstWhere((element) =>
|
||||
element.album.albumFile!.compareServerIdentity(album.albumFile!) &&
|
||||
element.share.shareWith == shareWith)
|
||||
.share;
|
||||
await RemoveShare(shareRepo)(account, thisShare);
|
||||
// remove the share from album file
|
||||
final newShares =
|
||||
album.shares?.where((s) => s.userId != shareWith).toList() ?? [];
|
||||
final newAlbum = album.copyWith(
|
||||
shares: OrNull(newShares.isEmpty ? null : newShares),
|
||||
);
|
||||
await UpdateAlbum(albumRepo)(account, newAlbum);
|
||||
|
||||
await _deleteFileShares(
|
||||
account,
|
||||
newAlbum,
|
||||
shareWith,
|
||||
onUnshareFileFailed: onUnshareFileFailed,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _deleteFileShares(
|
||||
Account account,
|
||||
Album album,
|
||||
String shareWith, {
|
||||
void Function(Share)? onUnshareFileFailed,
|
||||
}) async {
|
||||
// remove share from the album file
|
||||
final albumShares = await ListShare(shareRepo)(account, album.albumFile!);
|
||||
for (final s in albumShares.where((s) => s.shareWith == shareWith)) {
|
||||
try {
|
||||
await RemoveShare(shareRepo)(account, s);
|
||||
} catch (e, stackTrace) {
|
||||
_log.severe(
|
||||
"[_deleteFileShares] Failed unsharing album file '${logFilename(album.albumFile?.path)}' with '$shareWith'",
|
||||
e,
|
||||
stackTrace);
|
||||
onUnshareFileFailed?.call(s);
|
||||
}
|
||||
}
|
||||
|
||||
// then remove shares from all files in this album
|
||||
final files = AlbumStaticProvider.of(album)
|
||||
.items
|
||||
.whereType<AlbumFileItem>()
|
||||
|
@ -39,7 +69,6 @@ class UnshareAlbumWithUser {
|
|||
album,
|
||||
files,
|
||||
[shareWith],
|
||||
listSharedAlbumResults: sharedItems,
|
||||
onUnshareFileFailed: onUnshareFileFailed,
|
||||
);
|
||||
}
|
||||
|
@ -47,4 +76,7 @@ class UnshareAlbumWithUser {
|
|||
final ShareRepo shareRepo;
|
||||
final FileRepo fileRepo;
|
||||
final AlbumRepo albumRepo;
|
||||
|
||||
static final _log =
|
||||
Logger("use_case.unshare_album_with_user.UnshareAlbumWithUser");
|
||||
}
|
||||
|
|
|
@ -6,8 +6,8 @@ import 'package:nc_photos/entity/album/item.dart';
|
|||
import 'package:nc_photos/entity/album/provider.dart';
|
||||
import 'package:nc_photos/entity/file.dart';
|
||||
import 'package:nc_photos/entity/share.dart';
|
||||
import 'package:nc_photos/use_case/list_album.dart';
|
||||
import 'package:nc_photos/use_case/list_share.dart';
|
||||
import 'package:nc_photos/use_case/list_shared_album.dart';
|
||||
import 'package:nc_photos/use_case/remove_share.dart';
|
||||
|
||||
class UnshareFileFromAlbum {
|
||||
|
@ -22,19 +22,19 @@ class UnshareFileFromAlbum {
|
|||
Album album,
|
||||
List<File> files,
|
||||
List<String> unshareWith, {
|
||||
List<ListSharedAlbumItem>? listSharedAlbumResults,
|
||||
void Function(Share)? onUnshareFileFailed,
|
||||
}) async {
|
||||
_log.info(
|
||||
"[call] Unshare ${files.length} files from album '${album.name}' with ${unshareWith.length} users");
|
||||
// list albums with shares identical to one of [unshareWith]
|
||||
final otherAlbums = (listSharedAlbumResults ??
|
||||
await ListSharedAlbum(shareRepo, fileRepo, albumRepo)(account))
|
||||
.where((element) =>
|
||||
!element.album.albumFile!.compareServerIdentity(album.albumFile!) &&
|
||||
element.album.provider is AlbumStaticProvider &&
|
||||
unshareWith.contains(element.share.shareWith))
|
||||
.toList();
|
||||
// list albums with shares identical to any element in [unshareWith]
|
||||
final otherAlbums = (await ListAlbum(fileRepo, albumRepo)(account)
|
||||
.where((event) => event is Album)
|
||||
.cast<Album>()
|
||||
.where((album) =>
|
||||
!album.albumFile!.compareServerIdentity(album.albumFile!) &&
|
||||
album.provider is AlbumStaticProvider &&
|
||||
album.shares?.any((s) => unshareWith.contains(s.userId)) == true)
|
||||
.toList());
|
||||
|
||||
// look for shares that are exclusive to this album
|
||||
final exclusiveShares = <Share>[];
|
||||
|
@ -49,14 +49,18 @@ class UnshareFileFromAlbum {
|
|||
}
|
||||
}
|
||||
for (final a in otherAlbums) {
|
||||
final albumFiles = AlbumStaticProvider.of(a.album)
|
||||
// check if the album is shared with the same users
|
||||
if (!a.shares!
|
||||
.any((as) => exclusiveShares.any((s) => s.shareWith == as.userId))) {
|
||||
continue;
|
||||
}
|
||||
final albumFiles = AlbumStaticProvider.of(a)
|
||||
.items
|
||||
.whereType<AlbumFileItem>()
|
||||
.map((e) => e.file)
|
||||
.toList();
|
||||
exclusiveShares.removeWhere((s) =>
|
||||
a.share.shareWith == s.shareWith &&
|
||||
albumFiles.any((element) => element.fileId == s.itemSource));
|
||||
exclusiveShares.removeWhere(
|
||||
(s) => albumFiles.any((element) => element.fileId == s.itemSource));
|
||||
}
|
||||
|
||||
// unshare them
|
||||
|
|
|
@ -13,8 +13,9 @@ import 'package:nc_photos/entity/album.dart';
|
|||
import 'package:nc_photos/entity/file.dart';
|
||||
import 'package:nc_photos/entity/share.dart';
|
||||
import 'package:nc_photos/entity/share/data_source.dart';
|
||||
import 'package:nc_photos/entity/sharee.dart';
|
||||
import 'package:nc_photos/entity/sharee/data_source.dart';
|
||||
import 'package:nc_photos/exception_util.dart' as exception_util;
|
||||
import 'package:nc_photos/iterable_extension.dart';
|
||||
import 'package:nc_photos/k.dart' as k;
|
||||
import 'package:nc_photos/snack_bar_manager.dart';
|
||||
import 'package:nc_photos/theme.dart';
|
||||
|
@ -219,8 +220,8 @@ class _AlbumShareOutlierBrowserState extends State<AlbumShareOutlierBrowser> {
|
|||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
subtitle: Text(
|
||||
L10n.global().missingShareDescription(item.shareWithDisplayName)),
|
||||
subtitle: Text(L10n.global().missingShareDescription(
|
||||
item.shareWithDisplayName ?? item.shareWith)),
|
||||
trailing: trailing,
|
||||
);
|
||||
}
|
||||
|
@ -321,9 +322,9 @@ class _AlbumShareOutlierBrowserState extends State<AlbumShareOutlierBrowser> {
|
|||
_items = [];
|
||||
} else if (state is ListAlbumShareOutlierBlocSuccess ||
|
||||
state is ListAlbumShareOutlierBlocLoading) {
|
||||
_transformItems(state.albumShares, state.items);
|
||||
_transformItems(state.items);
|
||||
} else if (state is ListAlbumShareOutlierBlocFailure) {
|
||||
_transformItems(state.albumShares, state.items);
|
||||
_transformItems(state.items);
|
||||
SnackBarManager().showSnackBar(SnackBar(
|
||||
content: Text(exception_util.toUserString(state.exception)),
|
||||
duration: k.snackBarDurationNormal,
|
||||
|
@ -362,48 +363,20 @@ class _AlbumShareOutlierBrowserState extends State<AlbumShareOutlierBrowser> {
|
|||
}
|
||||
}
|
||||
|
||||
void _transformItems(
|
||||
List<Share> albumShares, List<ListAlbumShareOutlierItem> items) {
|
||||
final to =
|
||||
albumShares.sorted((a, b) => a.shareWith!.compareTo(b.shareWith!));
|
||||
for (final i in items) {
|
||||
_transformItem(to, i);
|
||||
}
|
||||
}
|
||||
|
||||
void _transformItem(List<Share> albumShares, ListAlbumShareOutlierItem item) {
|
||||
final from =
|
||||
item.shares.sorted((a, b) => a.shareWith!.compareTo(b.shareWith!));
|
||||
final to = albumShares;
|
||||
var fromI = 0, toI = 0;
|
||||
while (fromI < from.length && toI < to.length) {
|
||||
final fromShare = from[fromI];
|
||||
final toShare = to[toI];
|
||||
if (fromShare.shareWith == toShare.shareWith) {
|
||||
++fromI;
|
||||
++toI;
|
||||
} else {
|
||||
final diff = fromShare.shareWith!.compareTo(toShare.shareWith!);
|
||||
if (diff < 0) {
|
||||
// extra element in from
|
||||
_items.add(_ExtraShareItem(item.file, fromShare));
|
||||
++fromI;
|
||||
} else {
|
||||
// extra element in to
|
||||
_items.add(_MissingShareeItem(
|
||||
item.file, toShare.shareWith!, toShare.shareWithDisplayName));
|
||||
++toI;
|
||||
void _transformItems(List<ListAlbumShareOutlierItem> items) {
|
||||
_items = () sync* {
|
||||
for (final item in items) {
|
||||
for (final si in item.shareItems) {
|
||||
if (si is ListAlbumShareOutlierMissingShareItem) {
|
||||
yield _MissingShareeItem(
|
||||
item.file, si.shareWith, si.shareWithDisplayName);
|
||||
} else if (si is ListAlbumShareOutlierExtraShareItem) {
|
||||
yield _ExtraShareItem(item.file, si.share);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = fromI; i < from.length; ++i) {
|
||||
_items.add(_ExtraShareItem(item.file, from[i]));
|
||||
}
|
||||
for (var i = toI; i < to.length; ++i) {
|
||||
_items.add(_MissingShareeItem(
|
||||
item.file, to[i].shareWith!, to[i].shareWithDisplayName));
|
||||
}
|
||||
}()
|
||||
.toList();
|
||||
}
|
||||
|
||||
Future<void> _fixMissingSharee(_MissingShareeItem item) async {
|
||||
|
@ -489,7 +462,8 @@ class _AlbumShareOutlierBrowserState extends State<AlbumShareOutlierBrowser> {
|
|||
_itemStatuses[fileKey]!.remove(shareeKey);
|
||||
}
|
||||
|
||||
late final _bloc = ListAlbumShareOutlierBloc();
|
||||
late final _bloc = ListAlbumShareOutlierBloc(
|
||||
ShareRepo(ShareRemoteDataSource()), ShareeRepo(ShareeRemoteDataSource()));
|
||||
|
||||
var _items = <_ListItem>[];
|
||||
final _itemStatuses = <String, Map<String, _ItemStatus>>{};
|
||||
|
@ -515,7 +489,7 @@ class _MissingShareeItem extends _ListItem {
|
|||
|
||||
final File file;
|
||||
final String shareWith;
|
||||
final String shareWithDisplayName;
|
||||
final String? shareWithDisplayName;
|
||||
}
|
||||
|
||||
enum _ItemStatus {
|
||||
|
|
|
@ -5,7 +5,6 @@ import 'package:logging/logging.dart';
|
|||
import 'package:nc_photos/account.dart';
|
||||
import 'package:nc_photos/app_db.dart';
|
||||
import 'package:nc_photos/app_localizations.dart';
|
||||
import 'package:nc_photos/bloc/list_share.dart';
|
||||
import 'package:nc_photos/bloc/list_sharee.dart';
|
||||
import 'package:nc_photos/entity/album.dart';
|
||||
import 'package:nc_photos/entity/file.dart';
|
||||
|
@ -42,7 +41,6 @@ class _ShareAlbumDialogState extends State<ShareAlbumDialog> {
|
|||
initState() {
|
||||
super.initState();
|
||||
_shareeBloc.add(ListShareeBlocQuery(widget.account));
|
||||
_shareBloc.add(ListShareBlocQuery(widget.account, widget.album.albumFile!));
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -60,12 +58,7 @@ class _ShareAlbumDialogState extends State<ShareAlbumDialog> {
|
|||
_onListShareeBlocStateChanged(context, shareeState),
|
||||
child: BlocBuilder<ListShareeBloc, ListShareeBlocState>(
|
||||
bloc: _shareeBloc,
|
||||
builder: (_, shareeState) =>
|
||||
BlocBuilder<ListShareBloc, ListShareBlocState>(
|
||||
bloc: _shareBloc,
|
||||
builder: (context, shareState) =>
|
||||
_buildContent(context, shareeState, shareState),
|
||||
),
|
||||
builder: (_, shareeState) => _buildContent(context, shareeState),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -73,11 +66,9 @@ class _ShareAlbumDialogState extends State<ShareAlbumDialog> {
|
|||
);
|
||||
}
|
||||
|
||||
Widget _buildContent(BuildContext context, ListShareeBlocState shareeState,
|
||||
ListShareBlocState shareState) {
|
||||
Widget _buildContent(BuildContext context, ListShareeBlocState shareeState) {
|
||||
final List<Widget> children;
|
||||
if (shareeState is ListShareeBlocLoading ||
|
||||
shareState is ListShareBlocLoading) {
|
||||
if (shareeState is ListShareeBlocLoading) {
|
||||
children = [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 24),
|
||||
|
@ -95,7 +86,7 @@ class _ShareAlbumDialogState extends State<ShareAlbumDialog> {
|
|||
children = shareeState.items
|
||||
.where((element) => element.type == ShareeType.user)
|
||||
.sorted((a, b) => a.label.compareTo(b.label))
|
||||
.map((sharee) => _buildItem(context, shareState, sharee))
|
||||
.map((sharee) => _buildItem(context, sharee))
|
||||
.toList();
|
||||
}
|
||||
return GestureDetector(
|
||||
|
@ -107,14 +98,15 @@ class _ShareAlbumDialogState extends State<ShareAlbumDialog> {
|
|||
);
|
||||
}
|
||||
|
||||
Widget _buildItem(
|
||||
BuildContext context, ListShareBlocState shareState, Sharee sharee) {
|
||||
Widget _buildItem(BuildContext context, Sharee sharee) {
|
||||
final bool isShared;
|
||||
if (_overrideSharee.containsKey(sharee.shareWith)) {
|
||||
isShared = _overrideSharee[sharee.shareWith]!;
|
||||
} else {
|
||||
isShared = shareState.items
|
||||
.any((element) => element.shareWith == sharee.shareWith);
|
||||
isShared = widget.album.shares
|
||||
?.map((e) => e.userId)
|
||||
.contains(sharee.shareWith) ??
|
||||
false;
|
||||
}
|
||||
|
||||
final isProcessing =
|
||||
|
@ -185,12 +177,13 @@ class _ShareAlbumDialogState extends State<ShareAlbumDialog> {
|
|||
|
||||
Future<void> _createShare(Sharee sharee) async {
|
||||
final shareRepo = ShareRepo(ShareRemoteDataSource());
|
||||
final albumRepo = AlbumRepo(AlbumCachedDataSource(AppDb()));
|
||||
var hasFailure = false;
|
||||
try {
|
||||
await ShareAlbumWithUser(shareRepo)(
|
||||
await ShareAlbumWithUser(shareRepo, albumRepo)(
|
||||
widget.account,
|
||||
widget.album,
|
||||
sharee.shareWith,
|
||||
sharee,
|
||||
onShareFileFailed: (_) {
|
||||
hasFailure = true;
|
||||
},
|
||||
|
@ -260,7 +253,6 @@ class _ShareAlbumDialogState extends State<ShareAlbumDialog> {
|
|||
}
|
||||
|
||||
final _shareeBloc = ListShareeBloc();
|
||||
final _shareBloc = ListShareBloc();
|
||||
final _processingSharee = <String>[];
|
||||
|
||||
/// Store the modified value of each sharee
|
||||
|
|
|
@ -7,6 +7,7 @@ import 'package:nc_photos/entity/album/provider.dart';
|
|||
import 'package:nc_photos/entity/album/sort_provider.dart';
|
||||
import 'package:nc_photos/entity/album/upgrader.dart';
|
||||
import 'package:nc_photos/entity/file.dart';
|
||||
import 'package:nc_photos/type.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
|
@ -313,6 +314,8 @@ void main() {
|
|||
));
|
||||
});
|
||||
|
||||
test("shares", _fromJsonShares);
|
||||
|
||||
test("albumFile", () {
|
||||
final json = <String, dynamic>{
|
||||
"version": Album.version,
|
||||
|
@ -602,6 +605,8 @@ void main() {
|
|||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("shares", _toRemoteJsonShares);
|
||||
});
|
||||
|
||||
group("toAppDbJson", () {
|
||||
|
@ -852,6 +857,8 @@ void main() {
|
|||
});
|
||||
});
|
||||
|
||||
test("shares", _toAppDbJsonShares);
|
||||
|
||||
test("albumFile", () {
|
||||
final album = Album(
|
||||
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
|
||||
|
@ -1669,3 +1676,121 @@ void main() {
|
|||
});
|
||||
});
|
||||
}
|
||||
|
||||
void _fromJsonShares() {
|
||||
final json = <String, dynamic>{
|
||||
"version": Album.version,
|
||||
"lastUpdated": "2020-01-02T03:04:05.678901Z",
|
||||
"name": "",
|
||||
"provider": <String, dynamic>{
|
||||
"type": "static",
|
||||
"content": <String, dynamic>{
|
||||
"items": [],
|
||||
},
|
||||
},
|
||||
"coverProvider": <String, dynamic>{
|
||||
"type": "auto",
|
||||
"content": <String, dynamic>{},
|
||||
},
|
||||
"sortProvider": <String, dynamic>{
|
||||
"type": "null",
|
||||
"content": <String, dynamic>{},
|
||||
},
|
||||
"shares": <JsonObj>[
|
||||
{"userId": "admin"},
|
||||
],
|
||||
};
|
||||
expect(
|
||||
Album.fromJson(
|
||||
json,
|
||||
upgraderV1: null,
|
||||
upgraderV2: null,
|
||||
upgraderV3: null,
|
||||
upgraderV4: null,
|
||||
upgraderV5: null,
|
||||
),
|
||||
Album(
|
||||
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
|
||||
name: "",
|
||||
provider: AlbumStaticProvider(
|
||||
items: [],
|
||||
),
|
||||
coverProvider: AlbumAutoCoverProvider(),
|
||||
sortProvider: const AlbumNullSortProvider(),
|
||||
shares: [AlbumShare(userId: "admin".toCi())],
|
||||
));
|
||||
}
|
||||
|
||||
void _toRemoteJsonShares() {
|
||||
final album = Album(
|
||||
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
|
||||
name: "",
|
||||
provider: AlbumStaticProvider(
|
||||
items: [],
|
||||
),
|
||||
coverProvider: AlbumAutoCoverProvider(),
|
||||
sortProvider: const AlbumNullSortProvider(),
|
||||
shares: [AlbumShare(userId: "admin".toCi())],
|
||||
);
|
||||
expect(album.toRemoteJson(), <String, dynamic>{
|
||||
"version": Album.version,
|
||||
"lastUpdated": "2020-01-02T03:04:05.678901Z",
|
||||
"name": "",
|
||||
"provider": <String, dynamic>{
|
||||
"type": "static",
|
||||
"content": <String, dynamic>{
|
||||
"items": [],
|
||||
},
|
||||
},
|
||||
"coverProvider": <String, dynamic>{
|
||||
"type": "auto",
|
||||
"content": <String, dynamic>{},
|
||||
},
|
||||
"sortProvider": <String, dynamic>{
|
||||
"type": "null",
|
||||
"content": <String, dynamic>{},
|
||||
},
|
||||
"shares": [
|
||||
<String, dynamic>{
|
||||
"userId": "admin",
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
void _toAppDbJsonShares() {
|
||||
final album = Album(
|
||||
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
|
||||
name: "",
|
||||
provider: AlbumStaticProvider(
|
||||
items: [],
|
||||
),
|
||||
coverProvider: AlbumAutoCoverProvider(),
|
||||
sortProvider: const AlbumNullSortProvider(),
|
||||
shares: [AlbumShare(userId: "admin".toCi())],
|
||||
);
|
||||
expect(album.toAppDbJson(), <String, dynamic>{
|
||||
"version": Album.version,
|
||||
"lastUpdated": "2020-01-02T03:04:05.678901Z",
|
||||
"name": "",
|
||||
"provider": <String, dynamic>{
|
||||
"type": "static",
|
||||
"content": <String, dynamic>{
|
||||
"items": [],
|
||||
},
|
||||
},
|
||||
"coverProvider": <String, dynamic>{
|
||||
"type": "auto",
|
||||
"content": <String, dynamic>{},
|
||||
},
|
||||
"sortProvider": <String, dynamic>{
|
||||
"type": "null",
|
||||
"content": <String, dynamic>{},
|
||||
},
|
||||
"shares": [
|
||||
<String, dynamic>{
|
||||
"userId": "admin",
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue