mirror of
https://gitlab.com/nkming2/nc-photos.git
synced 2025-03-22 15:09:22 +01:00
Merge branch 'query-partial-file' into dev
This commit is contained in:
commit
b84ff7fbf9
78 changed files with 1288 additions and 631 deletions
|
@ -2,7 +2,7 @@
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:nc_photos/account.dart';
|
import 'package:nc_photos/account.dart';
|
||||||
import 'package:nc_photos/api/api.dart';
|
import 'package:nc_photos/api/api.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
||||||
import 'package:nc_photos/exception.dart';
|
import 'package:nc_photos/exception.dart';
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ const reservedFilenameChars = "<>:\"/\\|?*";
|
||||||
/// Return the preview image URL for [file]. See [getFilePreviewUrlRelative]
|
/// Return the preview image URL for [file]. See [getFilePreviewUrlRelative]
|
||||||
String getFilePreviewUrl(
|
String getFilePreviewUrl(
|
||||||
Account account,
|
Account account,
|
||||||
File file, {
|
FileDescriptor file, {
|
||||||
required int width,
|
required int width,
|
||||||
required int height,
|
required int height,
|
||||||
String? mode,
|
String? mode,
|
||||||
|
@ -27,7 +27,7 @@ String getFilePreviewUrl(
|
||||||
/// cropped
|
/// cropped
|
||||||
String getFilePreviewUrlRelative(
|
String getFilePreviewUrlRelative(
|
||||||
Account account,
|
Account account,
|
||||||
File file, {
|
FileDescriptor file, {
|
||||||
required int width,
|
required int width,
|
||||||
required int height,
|
required int height,
|
||||||
String? mode,
|
String? mode,
|
||||||
|
@ -36,14 +36,9 @@ String getFilePreviewUrlRelative(
|
||||||
String url;
|
String url;
|
||||||
if (file_util.isTrash(account, file)) {
|
if (file_util.isTrash(account, file)) {
|
||||||
// trashbin does not support preview.png endpoint
|
// trashbin does not support preview.png endpoint
|
||||||
url = "index.php/apps/files_trashbin/preview?fileId=${file.fileId}";
|
url = "index.php/apps/files_trashbin/preview?fileId=${file.fdId}";
|
||||||
} else {
|
} else {
|
||||||
if (file.fileId != null) {
|
url = "index.php/core/preview?fileId=${file.fdId}";
|
||||||
url = "index.php/core/preview?fileId=${file.fileId}";
|
|
||||||
} else {
|
|
||||||
final filePath = Uri.encodeQueryComponent(file.strippedPath);
|
|
||||||
url = "index.php/core/preview.png?file=$filePath";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
url = "$url&x=$width&y=$height";
|
url = "$url&x=$width&y=$height";
|
||||||
|
@ -76,12 +71,12 @@ String getFilePreviewUrlByFileId(
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
String getFileUrl(Account account, File file) {
|
String getFileUrl(Account account, FileDescriptor file) {
|
||||||
return "${account.url}/${getFileUrlRelative(file)}";
|
return "${account.url}/${getFileUrlRelative(file)}";
|
||||||
}
|
}
|
||||||
|
|
||||||
String getFileUrlRelative(File file) {
|
String getFileUrlRelative(FileDescriptor file) {
|
||||||
return file.path;
|
return file.fdPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
String getWebdavRootUrlRelative(Account account) =>
|
String getWebdavRootUrlRelative(Account account) =>
|
||||||
|
|
|
@ -6,6 +6,7 @@ import 'package:nc_photos/bloc/bloc_util.dart' as bloc_util;
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/entity/album.dart';
|
import 'package:nc_photos/entity/album.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
||||||
import 'package:nc_photos/entity/share.dart';
|
import 'package:nc_photos/entity/share.dart';
|
||||||
import 'package:nc_photos/event/event.dart';
|
import 'package:nc_photos/event/event.dart';
|
||||||
|
|
|
@ -8,17 +8,17 @@ import 'package:nc_photos/bloc/bloc_util.dart' as bloc_util;
|
||||||
import 'package:nc_photos/debug_util.dart';
|
import 'package:nc_photos/debug_util.dart';
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
import 'package:nc_photos/entity/file/data_source.dart';
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
||||||
import 'package:nc_photos/event/event.dart';
|
import 'package:nc_photos/event/event.dart';
|
||||||
import 'package:nc_photos/event/native_event.dart';
|
import 'package:nc_photos/event/native_event.dart';
|
||||||
import 'package:nc_photos/exception_event.dart';
|
|
||||||
import 'package:nc_photos/platform/k.dart' as platform_k;
|
import 'package:nc_photos/platform/k.dart' as platform_k;
|
||||||
import 'package:nc_photos/pref.dart';
|
import 'package:nc_photos/pref.dart';
|
||||||
import 'package:nc_photos/throttler.dart';
|
import 'package:nc_photos/throttler.dart';
|
||||||
import 'package:nc_photos/use_case/ls.dart';
|
import 'package:nc_photos/use_case/ls.dart';
|
||||||
import 'package:nc_photos/use_case/scan_dir.dart';
|
import 'package:nc_photos/use_case/scan_dir.dart';
|
||||||
import 'package:nc_photos/use_case/scan_dir_offline.dart';
|
import 'package:nc_photos/use_case/scan_dir_offline.dart';
|
||||||
|
import 'package:nc_photos/use_case/sync_dir.dart';
|
||||||
|
|
||||||
abstract class ScanAccountDirBlocEvent {
|
abstract class ScanAccountDirBlocEvent {
|
||||||
const ScanAccountDirBlocEvent();
|
const ScanAccountDirBlocEvent();
|
||||||
|
@ -63,7 +63,7 @@ abstract class ScanAccountDirBlocState {
|
||||||
"}";
|
"}";
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<File> files;
|
final List<FileDescriptor> files;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ScanAccountDirBlocInit extends ScanAccountDirBlocState {
|
class ScanAccountDirBlocInit extends ScanAccountDirBlocState {
|
||||||
|
@ -71,15 +71,15 @@ class ScanAccountDirBlocInit extends ScanAccountDirBlocState {
|
||||||
}
|
}
|
||||||
|
|
||||||
class ScanAccountDirBlocLoading extends ScanAccountDirBlocState {
|
class ScanAccountDirBlocLoading extends ScanAccountDirBlocState {
|
||||||
const ScanAccountDirBlocLoading(List<File> files) : super(files);
|
const ScanAccountDirBlocLoading(List<FileDescriptor> files) : super(files);
|
||||||
}
|
}
|
||||||
|
|
||||||
class ScanAccountDirBlocSuccess extends ScanAccountDirBlocState {
|
class ScanAccountDirBlocSuccess extends ScanAccountDirBlocState {
|
||||||
const ScanAccountDirBlocSuccess(List<File> files) : super(files);
|
const ScanAccountDirBlocSuccess(List<FileDescriptor> files) : super(files);
|
||||||
}
|
}
|
||||||
|
|
||||||
class ScanAccountDirBlocFailure extends ScanAccountDirBlocState {
|
class ScanAccountDirBlocFailure extends ScanAccountDirBlocState {
|
||||||
const ScanAccountDirBlocFailure(List<File> files, this.exception)
|
const ScanAccountDirBlocFailure(List<FileDescriptor> files, this.exception)
|
||||||
: super(files);
|
: super(files);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -96,7 +96,8 @@ class ScanAccountDirBlocFailure extends ScanAccountDirBlocState {
|
||||||
/// The state of this bloc is inconsistent. This typically means that the data
|
/// The state of this bloc is inconsistent. This typically means that the data
|
||||||
/// may have been changed externally
|
/// may have been changed externally
|
||||||
class ScanAccountDirBlocInconsistent extends ScanAccountDirBlocState {
|
class ScanAccountDirBlocInconsistent extends ScanAccountDirBlocState {
|
||||||
const ScanAccountDirBlocInconsistent(List<File> files) : super(files);
|
const ScanAccountDirBlocInconsistent(List<FileDescriptor> files)
|
||||||
|
: super(files);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A bloc that return all files under a dir recursively
|
/// A bloc that return all files under a dir recursively
|
||||||
|
@ -206,7 +207,45 @@ class ScanAccountDirBloc
|
||||||
cacheFiles.where((f) => file_util.isSupportedFormat(f)).toList()));
|
cacheFiles.where((f) => file_util.isSupportedFormat(f)).toList()));
|
||||||
}
|
}
|
||||||
|
|
||||||
await _queryOnline(ev, emit, cacheFiles);
|
stopwatch.reset();
|
||||||
|
final hasUpdate = await _syncOnline(ev);
|
||||||
|
_log.info(
|
||||||
|
"[_onEventQuery] Elapsed time (_syncOnline): ${stopwatch.elapsedMilliseconds}ms, hasUpdate: $hasUpdate");
|
||||||
|
if (hasUpdate) {
|
||||||
|
// content updated, reload from db
|
||||||
|
stopwatch.reset();
|
||||||
|
final newFiles = await _queryOffline(ev);
|
||||||
|
_log.info(
|
||||||
|
"[_onEventQuery] Elapsed time (_queryOffline) 2nd pass: ${stopwatch.elapsedMilliseconds}ms, ${newFiles.length} files");
|
||||||
|
emit(ScanAccountDirBlocSuccess(newFiles));
|
||||||
|
} else {
|
||||||
|
emit(ScanAccountDirBlocSuccess(cacheFiles));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> _syncOnline(ScanAccountDirBlocQueryBase ev) async {
|
||||||
|
final settings = AccountPref.of(account);
|
||||||
|
final shareDir =
|
||||||
|
File(path: file_util.unstripPath(account, settings.getShareFolderOr()));
|
||||||
|
bool isShareDirIncluded = false;
|
||||||
|
|
||||||
|
bool hasUpdate = false;
|
||||||
|
for (final r in account.roots) {
|
||||||
|
final dirPath = file_util.unstripPath(account, r);
|
||||||
|
hasUpdate |= await SyncDir(_c)(account, dirPath);
|
||||||
|
isShareDirIncluded |=
|
||||||
|
file_util.isOrUnderDir(shareDir, File(path: dirPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isShareDirIncluded) {
|
||||||
|
_log.info("[_syncOnline] Explicitly scanning share folder");
|
||||||
|
hasUpdate |= await SyncDir(_c)(
|
||||||
|
account,
|
||||||
|
file_util.unstripPath(account, settings.getShareFolderOr()),
|
||||||
|
isRecursive: false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return hasUpdate;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onExternalEvent(_ScanAccountDirBlocExternalEvent ev,
|
Future<void> _onExternalEvent(_ScanAccountDirBlocExternalEvent ev,
|
||||||
|
@ -360,13 +399,20 @@ class ScanAccountDirBloc
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<File>> _queryOffline(ScanAccountDirBlocQueryBase ev) async {
|
Future<List<FileDescriptor>> _queryOffline(
|
||||||
final files = <File>[];
|
ScanAccountDirBlocQueryBase ev) async {
|
||||||
|
final settings = AccountPref.of(account);
|
||||||
|
final shareDir =
|
||||||
|
File(path: file_util.unstripPath(account, settings.getShareFolderOr()));
|
||||||
|
bool isShareDirIncluded = false;
|
||||||
|
|
||||||
|
final files = <FileDescriptor>[];
|
||||||
for (final r in account.roots) {
|
for (final r in account.roots) {
|
||||||
try {
|
try {
|
||||||
final dir = File(path: file_util.unstripPath(account, r));
|
final dir = File(path: file_util.unstripPath(account, r));
|
||||||
files.addAll(await ScanDirOffline(_c)(account, dir,
|
files.addAll(await ScanDirOffline(_c)(account, dir,
|
||||||
isOnlySupportedFormat: false));
|
isOnlySupportedFormat: true));
|
||||||
|
isShareDirIncluded |= file_util.isOrUnderDir(shareDir, dir);
|
||||||
} catch (e, stackTrace) {
|
} catch (e, stackTrace) {
|
||||||
_log.shout(
|
_log.shout(
|
||||||
"[_queryOffline] Failed while ScanDirOffline: ${logFilename(r)}",
|
"[_queryOffline] Failed while ScanDirOffline: ${logFilename(r)}",
|
||||||
|
@ -374,81 +420,12 @@ class ScanAccountDirBloc
|
||||||
stackTrace);
|
stackTrace);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return files;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _queryOnline(ScanAccountDirBlocQueryBase ev,
|
|
||||||
Emitter<ScanAccountDirBlocState> emit, List<File> cache) async {
|
|
||||||
// 1st pass: scan for new files
|
|
||||||
var files = <File>[];
|
|
||||||
final cacheMap = FileForwardCacheManager.prepareFileMap(cache);
|
|
||||||
final stopwatch = Stopwatch()..start();
|
|
||||||
_c.touchManager.clearTouchCache();
|
|
||||||
final fileRepo = FileRepo(FileCachedDataSource(
|
|
||||||
_c,
|
|
||||||
forwardCacheManager: FileForwardCacheManager(_c, cacheMap),
|
|
||||||
shouldCheckCache: true,
|
|
||||||
));
|
|
||||||
await for (final event
|
|
||||||
in _queryWithFileRepo(fileRepo, ev, fileRepoForShareDir: _c.fileRepo)) {
|
|
||||||
if (event is ExceptionEvent) {
|
|
||||||
_log.shout("[_queryOnline] Exception while request", event.error,
|
|
||||||
event.stackTrace);
|
|
||||||
emit(ScanAccountDirBlocFailure(
|
|
||||||
cache.isEmpty
|
|
||||||
? files
|
|
||||||
: cache.where((f) => file_util.isSupportedFormat(f)).toList(),
|
|
||||||
event.error));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
files.addAll(event);
|
|
||||||
if (cache.isEmpty) {
|
|
||||||
// only emit partial results if there's no cache
|
|
||||||
emit(ScanAccountDirBlocLoading(files.toList()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_log.info(
|
|
||||||
"[_queryOnline] Elapsed time (_queryOnline): ${stopwatch.elapsedMilliseconds}ms, ${files.length} files");
|
|
||||||
|
|
||||||
emit(ScanAccountDirBlocSuccess(files));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Emit all files under this account
|
|
||||||
///
|
|
||||||
/// Emit List<File> or ExceptionEvent
|
|
||||||
Stream<dynamic> _queryWithFileRepo(
|
|
||||||
FileRepo fileRepo,
|
|
||||||
ScanAccountDirBlocQueryBase ev, {
|
|
||||||
FileRepo? fileRepoForShareDir,
|
|
||||||
}) async* {
|
|
||||||
final settings = AccountPref.of(account);
|
|
||||||
final shareDir =
|
|
||||||
File(path: file_util.unstripPath(account, settings.getShareFolderOr()));
|
|
||||||
bool isShareDirIncluded = false;
|
|
||||||
|
|
||||||
for (final r in account.roots) {
|
|
||||||
final dir = File(path: file_util.unstripPath(account, r));
|
|
||||||
yield* ScanDir(fileRepo)(account, dir);
|
|
||||||
isShareDirIncluded |= file_util.isOrUnderDir(shareDir, dir);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isShareDirIncluded) {
|
if (!isShareDirIncluded) {
|
||||||
_log.info("[_queryWithFileRepo] Explicitly scanning share folder");
|
_log.info("[_queryOffline] Explicitly scanning share folder");
|
||||||
try {
|
files.addAll(await Ls(_c.fileRepoLocal)(account, shareDir));
|
||||||
final files = await Ls(fileRepoForShareDir ?? fileRepo)(
|
|
||||||
account,
|
|
||||||
File(
|
|
||||||
path: file_util.unstripPath(account, settings.getShareFolderOr()),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
yield files
|
|
||||||
.where((f) =>
|
|
||||||
file_util.isSupportedFormat(f) && !f.isOwned(account.userId))
|
|
||||||
.toList();
|
|
||||||
} catch (e, stackTrace) {
|
|
||||||
yield ExceptionEvent(e, stackTrace);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return files;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _isFileOfInterest(File file) {
|
bool _isFileOfInterest(File file) {
|
||||||
|
|
|
@ -5,7 +5,9 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:nc_photos/account.dart';
|
import 'package:nc_photos/account.dart';
|
||||||
import 'package:nc_photos/app_localizations.dart';
|
import 'package:nc_photos/app_localizations.dart';
|
||||||
|
import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/exception.dart';
|
import 'package:nc_photos/exception.dart';
|
||||||
import 'package:nc_photos/exception_util.dart' as exception_util;
|
import 'package:nc_photos/exception_util.dart' as exception_util;
|
||||||
import 'package:nc_photos/k.dart' as k;
|
import 'package:nc_photos/k.dart' as k;
|
||||||
|
@ -16,15 +18,23 @@ import 'package:nc_photos/mobile/platform.dart'
|
||||||
import 'package:nc_photos/platform/k.dart' as platform_k;
|
import 'package:nc_photos/platform/k.dart' as platform_k;
|
||||||
import 'package:nc_photos/snack_bar_manager.dart';
|
import 'package:nc_photos/snack_bar_manager.dart';
|
||||||
import 'package:nc_photos/use_case/download_file.dart';
|
import 'package:nc_photos/use_case/download_file.dart';
|
||||||
|
import 'package:nc_photos/use_case/inflate_file_descriptor.dart';
|
||||||
import 'package:nc_photos_plugin/nc_photos_plugin.dart';
|
import 'package:nc_photos_plugin/nc_photos_plugin.dart';
|
||||||
import 'package:tuple/tuple.dart';
|
import 'package:tuple/tuple.dart';
|
||||||
|
|
||||||
class DownloadHandler {
|
class DownloadHandler {
|
||||||
|
DownloadHandler(this._c)
|
||||||
|
: assert(require(_c)),
|
||||||
|
assert(InflateFileDescriptor.require(_c));
|
||||||
|
|
||||||
|
static bool require(DiContainer c) => true;
|
||||||
|
|
||||||
Future<void> downloadFiles(
|
Future<void> downloadFiles(
|
||||||
Account account,
|
Account account,
|
||||||
List<File> files, {
|
List<FileDescriptor> fds, {
|
||||||
String? parentDir,
|
String? parentDir,
|
||||||
}) {
|
}) async {
|
||||||
|
final files = await InflateFileDescriptor(_c)(account, fds);
|
||||||
final _DownloadHandlerBase handler;
|
final _DownloadHandlerBase handler;
|
||||||
if (platform_k.isAndroid) {
|
if (platform_k.isAndroid) {
|
||||||
handler = _DownlaodHandlerAndroid();
|
handler = _DownlaodHandlerAndroid();
|
||||||
|
@ -37,6 +47,8 @@ class DownloadHandler {
|
||||||
parentDir: parentDir,
|
parentDir: parentDir,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final DiContainer _c;
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class _DownloadHandlerBase {
|
abstract class _DownloadHandlerBase {
|
||||||
|
|
|
@ -5,6 +5,7 @@ import 'package:nc_photos/entity/album.dart';
|
||||||
import 'package:nc_photos/entity/album/item.dart';
|
import 'package:nc_photos/entity/album/item.dart';
|
||||||
import 'package:nc_photos/entity/album/provider.dart';
|
import 'package:nc_photos/entity/album/provider.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
||||||
import 'package:nc_photos/type.dart';
|
import 'package:nc_photos/type.dart';
|
||||||
|
|
||||||
|
@ -20,6 +21,9 @@ abstract class AlbumCoverProvider with EquatableMixin {
|
||||||
case AlbumManualCoverProvider._type:
|
case AlbumManualCoverProvider._type:
|
||||||
return AlbumManualCoverProvider.fromJson(
|
return AlbumManualCoverProvider.fromJson(
|
||||||
content.cast<String, dynamic>());
|
content.cast<String, dynamic>());
|
||||||
|
case AlbumMemoryCoverProvider._type:
|
||||||
|
return AlbumMemoryCoverProvider.fromJson(
|
||||||
|
content.cast<String, dynamic>());
|
||||||
default:
|
default:
|
||||||
_log.shout("[fromJson] Unknown type: $type");
|
_log.shout("[fromJson] Unknown type: $type");
|
||||||
throw ArgumentError.value(type, "type");
|
throw ArgumentError.value(type, "type");
|
||||||
|
@ -46,7 +50,7 @@ abstract class AlbumCoverProvider with EquatableMixin {
|
||||||
@override
|
@override
|
||||||
toString();
|
toString();
|
||||||
|
|
||||||
File? getCover(Album album);
|
FileDescriptor? getCover(Album album);
|
||||||
|
|
||||||
JsonObj _toContentJson();
|
JsonObj _toContentJson();
|
||||||
|
|
||||||
|
@ -151,3 +155,39 @@ class AlbumManualCoverProvider extends AlbumCoverProvider {
|
||||||
|
|
||||||
static const _type = "manual";
|
static const _type = "manual";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Cover selected when building a Memory album
|
||||||
|
class AlbumMemoryCoverProvider extends AlbumCoverProvider {
|
||||||
|
AlbumMemoryCoverProvider({
|
||||||
|
required this.coverFile,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory AlbumMemoryCoverProvider.fromJson(JsonObj json) {
|
||||||
|
return AlbumMemoryCoverProvider(
|
||||||
|
coverFile:
|
||||||
|
FileDescriptor.fromJson(json["coverFile"].cast<String, dynamic>()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
toString() => "$runtimeType {"
|
||||||
|
"coverFile: '${coverFile.fdPath}', "
|
||||||
|
"}";
|
||||||
|
|
||||||
|
@override
|
||||||
|
getCover(Album album) => coverFile;
|
||||||
|
|
||||||
|
@override
|
||||||
|
get props => [
|
||||||
|
coverFile,
|
||||||
|
];
|
||||||
|
|
||||||
|
@override
|
||||||
|
_toContentJson() => {
|
||||||
|
"coverFile": coverFile.toJson(),
|
||||||
|
};
|
||||||
|
|
||||||
|
final FileDescriptor coverFile;
|
||||||
|
|
||||||
|
static const _type = "memory";
|
||||||
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import 'package:nc_photos/entity/album.dart';
|
||||||
import 'package:nc_photos/entity/album/upgrader.dart';
|
import 'package:nc_photos/entity/album/upgrader.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
import 'package:nc_photos/entity/file/data_source.dart';
|
import 'package:nc_photos/entity/file/data_source.dart';
|
||||||
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/entity/sqlite_table.dart' as sql;
|
import 'package:nc_photos/entity/sqlite_table.dart' as sql;
|
||||||
import 'package:nc_photos/entity/sqlite_table_converter.dart';
|
import 'package:nc_photos/entity/sqlite_table_converter.dart';
|
||||||
import 'package:nc_photos/entity/sqlite_table_extension.dart' as sql;
|
import 'package:nc_photos/entity/sqlite_table_extension.dart' as sql;
|
||||||
|
|
|
@ -3,6 +3,7 @@ import 'package:equatable/equatable.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:nc_photos/entity/album/item.dart';
|
import 'package:nc_photos/entity/album/item.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/iterable_extension.dart';
|
import 'package:nc_photos/iterable_extension.dart';
|
||||||
import 'package:nc_photos/type.dart';
|
import 'package:nc_photos/type.dart';
|
||||||
import 'package:tuple/tuple.dart';
|
import 'package:tuple/tuple.dart';
|
||||||
|
|
|
@ -5,21 +5,14 @@ import 'package:logging/logging.dart';
|
||||||
import 'package:nc_photos/account.dart';
|
import 'package:nc_photos/account.dart';
|
||||||
import 'package:nc_photos/ci_string.dart';
|
import 'package:nc_photos/ci_string.dart';
|
||||||
import 'package:nc_photos/entity/exif.dart';
|
import 'package:nc_photos/entity/exif.dart';
|
||||||
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/json_util.dart' as json_util;
|
import 'package:nc_photos/json_util.dart' as json_util;
|
||||||
import 'package:nc_photos/or_null.dart';
|
import 'package:nc_photos/or_null.dart';
|
||||||
import 'package:nc_photos/string_extension.dart';
|
import 'package:nc_photos/string_extension.dart';
|
||||||
import 'package:nc_photos/type.dart';
|
import 'package:nc_photos/type.dart';
|
||||||
import 'package:path/path.dart' as path_lib;
|
|
||||||
|
|
||||||
int compareFileDateTimeDescending(File x, File y) {
|
int compareFileDateTimeDescending(File x, File y) =>
|
||||||
final tmp = y.bestDateTime.compareTo(x.bestDateTime);
|
compareFileDescriptorDateTimeDescending(x, y);
|
||||||
if (tmp != 0) {
|
|
||||||
return tmp;
|
|
||||||
} else {
|
|
||||||
// compare file name if files are modified at the same time
|
|
||||||
return x.path.compareTo(y.path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ImageLocation with EquatableMixin {
|
class ImageLocation with EquatableMixin {
|
||||||
const ImageLocation({
|
const ImageLocation({
|
||||||
|
@ -293,7 +286,7 @@ class MetadataUpgraderV2 implements MetadataUpgrader {
|
||||||
static final _log = Logger("entity.file.MetadataUpgraderV2");
|
static final _log = Logger("entity.file.MetadataUpgraderV2");
|
||||||
}
|
}
|
||||||
|
|
||||||
class File with EquatableMixin {
|
class File with EquatableMixin implements FileDescriptor {
|
||||||
File({
|
File({
|
||||||
required String path,
|
required String path,
|
||||||
this.contentLength,
|
this.contentLength,
|
||||||
|
@ -435,6 +428,7 @@ class File with EquatableMixin {
|
||||||
return product + "}";
|
return product + "}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
JsonObj toJson() {
|
JsonObj toJson() {
|
||||||
return {
|
return {
|
||||||
"path": path,
|
"path": path,
|
||||||
|
@ -533,6 +527,24 @@ class File with EquatableMixin {
|
||||||
location,
|
location,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@override
|
||||||
|
get fdPath => path;
|
||||||
|
|
||||||
|
@override
|
||||||
|
get fdId => fileId!;
|
||||||
|
|
||||||
|
@override
|
||||||
|
get fdMime => contentType;
|
||||||
|
|
||||||
|
@override
|
||||||
|
get fdIsArchived => isArchived ?? false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
get fdIsFavorite => isFavorite ?? false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
get fdDateTime => bestDateTime;
|
||||||
|
|
||||||
final String path;
|
final String path;
|
||||||
final int? contentLength;
|
final int? contentLength;
|
||||||
final String? contentType;
|
final String? contentType;
|
||||||
|
@ -563,54 +575,6 @@ extension FileExtension on File {
|
||||||
DateTime.now().toUtc();
|
DateTime.now().toUtc();
|
||||||
|
|
||||||
bool isOwned(CiString userId) => ownerId == null || ownerId == userId;
|
bool isOwned(CiString userId) => ownerId == null || ownerId == userId;
|
||||||
|
|
||||||
/// Return the path of this file with the DAV part stripped
|
|
||||||
///
|
|
||||||
/// WebDAV file path: remote.php/dav/files/{username}/{strippedPath}. If this
|
|
||||||
/// file points to the user's root dir, return "."
|
|
||||||
///
|
|
||||||
/// See: [strippedPathWithEmpty]
|
|
||||||
String get strippedPath {
|
|
||||||
if (path.contains("remote.php/dav/files")) {
|
|
||||||
final position = path.indexOf("/", "remote.php/dav/files/".length) + 1;
|
|
||||||
if (position == 0) {
|
|
||||||
// root dir path
|
|
||||||
return ".";
|
|
||||||
} else {
|
|
||||||
return path.substring(position);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return the path of this file with the DAV part stripped
|
|
||||||
///
|
|
||||||
/// WebDAV file path: remote.php/dav/files/{username}/{strippedPath}. If this
|
|
||||||
/// file points to the user's root dir, return an empty string
|
|
||||||
///
|
|
||||||
/// See: [strippedPath]
|
|
||||||
String get strippedPathWithEmpty {
|
|
||||||
final path = strippedPath;
|
|
||||||
return path == "." ? "" : path;
|
|
||||||
}
|
|
||||||
|
|
||||||
String get filename => path_lib.basename(path);
|
|
||||||
|
|
||||||
/// Compare the server identity of two Files
|
|
||||||
///
|
|
||||||
/// Return true if two Files point to the same file on server. Be careful that
|
|
||||||
/// this does NOT mean that the two Files are identical
|
|
||||||
bool compareServerIdentity(File other) {
|
|
||||||
if (fileId != null && other.fileId != null) {
|
|
||||||
return fileId == other.fileId;
|
|
||||||
} else {
|
|
||||||
return path == other.path;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// hashCode to be used with [compareServerIdentity]
|
|
||||||
int get identityHashCode => (fileId ?? path).hashCode;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class FileServerIdentityComparator {
|
class FileServerIdentityComparator {
|
||||||
|
|
|
@ -9,6 +9,7 @@ import 'package:nc_photos/debug_util.dart';
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
import 'package:nc_photos/entity/file/file_cache_manager.dart';
|
import 'package:nc_photos/entity/file/file_cache_manager.dart';
|
||||||
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
||||||
import 'package:nc_photos/entity/sqlite_table.dart' as sql;
|
import 'package:nc_photos/entity/sqlite_table.dart' as sql;
|
||||||
import 'package:nc_photos/entity/sqlite_table_extension.dart' as sql;
|
import 'package:nc_photos/entity/sqlite_table_extension.dart' as sql;
|
||||||
|
@ -575,6 +576,17 @@ class FileCachedDataSource implements FileDataSource {
|
||||||
}
|
}
|
||||||
|
|
||||||
// no cache or outdated
|
// no cache or outdated
|
||||||
|
return await sync(account, dir,
|
||||||
|
remoteTouchEtag: cacheLoader.remoteTouchEtag);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sync [dir] with remote content, and set the local touch etag as
|
||||||
|
/// [remoteTouchEtag]
|
||||||
|
Future<List<File>> sync(
|
||||||
|
Account account,
|
||||||
|
File dir, {
|
||||||
|
required String? remoteTouchEtag,
|
||||||
|
}) async {
|
||||||
try {
|
try {
|
||||||
final remote = await _remoteSrc.list(account, dir);
|
final remote = await _remoteSrc.list(account, dir);
|
||||||
await FileSqliteCacheUpdater(_c)(account, dir, remote: remote);
|
await FileSqliteCacheUpdater(_c)(account, dir, remote: remote);
|
||||||
|
@ -582,8 +594,7 @@ class FileCachedDataSource implements FileDataSource {
|
||||||
// update our local touch token to match the remote one
|
// update our local touch token to match the remote one
|
||||||
try {
|
try {
|
||||||
_log.info("[list] Update outdated local etag: ${dir.path}");
|
_log.info("[list] Update outdated local etag: ${dir.path}");
|
||||||
await _c.touchManager
|
await _c.touchManager.setLocalEtag(account, dir, remoteTouchEtag);
|
||||||
.setLocalEtag(account, dir, cacheLoader.remoteTouchEtag);
|
|
||||||
} catch (e, stacktrace) {
|
} catch (e, stacktrace) {
|
||||||
_log.shout("[list] Failed while setLocalToken", e, stacktrace);
|
_log.shout("[list] Failed while setLocalToken", e, stacktrace);
|
||||||
// ignore error
|
// ignore error
|
||||||
|
@ -593,16 +604,22 @@ class FileCachedDataSource implements FileDataSource {
|
||||||
} on ApiException catch (e) {
|
} on ApiException catch (e) {
|
||||||
if (e.response.statusCode == 404) {
|
if (e.response.statusCode == 404) {
|
||||||
_log.info("[list] File removed: $dir");
|
_log.info("[list] File removed: $dir");
|
||||||
if (cache != null) {
|
try {
|
||||||
await _sqliteDbSrc.remove(account, dir);
|
await _sqliteDbSrc.remove(account, dir);
|
||||||
|
} catch (e) {
|
||||||
|
_log.warning(
|
||||||
|
"[list] Failed while remove from db, file not cached?", e);
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
} else if (e.response.statusCode == 403) {
|
} else if (e.response.statusCode == 403) {
|
||||||
_log.info("[list] E2E encrypted dir: $dir");
|
_log.info("[list] E2E encrypted dir: $dir");
|
||||||
if (cache != null) {
|
try {
|
||||||
// we need to keep the dir itself as it'll be inserted again on next
|
// we need to keep the dir itself as it'll be inserted again on next
|
||||||
// listing of its parent
|
// listing of its parent
|
||||||
await _sqliteDbSrc.emptyDir(account, dir);
|
await _sqliteDbSrc.emptyDir(account, dir);
|
||||||
|
} catch (e) {
|
||||||
|
_log.warning(
|
||||||
|
"[list] Failed while emptying from db, file not cached?", e);
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -6,6 +6,7 @@ import 'package:nc_photos/debug_util.dart';
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
import 'package:nc_photos/entity/file/data_source.dart';
|
import 'package:nc_photos/entity/file/data_source.dart';
|
||||||
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/entity/sqlite_table.dart' as sql;
|
import 'package:nc_photos/entity/sqlite_table.dart' as sql;
|
||||||
import 'package:nc_photos/entity/sqlite_table_converter.dart';
|
import 'package:nc_photos/entity/sqlite_table_converter.dart';
|
||||||
import 'package:nc_photos/entity/sqlite_table_extension.dart' as sql;
|
import 'package:nc_photos/entity/sqlite_table_extension.dart' as sql;
|
||||||
|
|
110
app/lib/entity/file_descriptor.dart
Normal file
110
app/lib/entity/file_descriptor.dart
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:nc_photos/type.dart';
|
||||||
|
import 'package:path/path.dart' as path_lib;
|
||||||
|
|
||||||
|
int compareFileDescriptorDateTimeDescending(
|
||||||
|
FileDescriptor x, FileDescriptor y) {
|
||||||
|
final tmp = y.fdDateTime.compareTo(x.fdDateTime);
|
||||||
|
if (tmp != 0) {
|
||||||
|
return tmp;
|
||||||
|
} else {
|
||||||
|
// compare file name if files are modified at the same time
|
||||||
|
return x.fdPath.compareTo(y.fdPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FileDescriptor with EquatableMixin {
|
||||||
|
const FileDescriptor({
|
||||||
|
required this.fdPath,
|
||||||
|
required this.fdId,
|
||||||
|
required this.fdMime,
|
||||||
|
required this.fdIsArchived,
|
||||||
|
required this.fdIsFavorite,
|
||||||
|
required this.fdDateTime,
|
||||||
|
});
|
||||||
|
|
||||||
|
static FileDescriptor fromJson(JsonObj json) => FileDescriptor(
|
||||||
|
fdPath: json["fdPath"],
|
||||||
|
fdId: json["fdId"],
|
||||||
|
fdMime: json["fdMime"],
|
||||||
|
fdIsArchived: json["fdIsArchived"],
|
||||||
|
fdIsFavorite: json["fdIsFavorite"],
|
||||||
|
fdDateTime: json["fdDateTime"],
|
||||||
|
);
|
||||||
|
|
||||||
|
JsonObj toJson() => {
|
||||||
|
"fdPath": fdPath,
|
||||||
|
"fdId": fdId,
|
||||||
|
"fdMime": fdMime,
|
||||||
|
"fdIsArchived": fdIsArchived,
|
||||||
|
"fdIsFavorite": fdIsFavorite,
|
||||||
|
"fdDateTime": fdDateTime,
|
||||||
|
};
|
||||||
|
|
||||||
|
@override
|
||||||
|
get props => [
|
||||||
|
fdPath,
|
||||||
|
fdId,
|
||||||
|
fdMime,
|
||||||
|
fdIsArchived,
|
||||||
|
fdIsFavorite,
|
||||||
|
fdDateTime,
|
||||||
|
];
|
||||||
|
|
||||||
|
final String fdPath;
|
||||||
|
final int fdId;
|
||||||
|
final String? fdMime;
|
||||||
|
final bool fdIsArchived;
|
||||||
|
final bool fdIsFavorite;
|
||||||
|
final DateTime fdDateTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
extension FileDescriptorExtension on FileDescriptor {
|
||||||
|
/// Return the path of this file with the DAV part stripped
|
||||||
|
///
|
||||||
|
/// WebDAV file path: remote.php/dav/files/{username}/{strippedPath}. If this
|
||||||
|
/// file points to the user's root dir, return "."
|
||||||
|
///
|
||||||
|
/// See: [strippedPathWithEmpty]
|
||||||
|
String get strippedPath {
|
||||||
|
if (fdPath.contains("remote.php/dav/files")) {
|
||||||
|
final position = fdPath.indexOf("/", "remote.php/dav/files/".length) + 1;
|
||||||
|
if (position == 0) {
|
||||||
|
// root dir path
|
||||||
|
return ".";
|
||||||
|
} else {
|
||||||
|
return fdPath.substring(position);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return fdPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the path of this file with the DAV part stripped
|
||||||
|
///
|
||||||
|
/// WebDAV file path: remote.php/dav/files/{username}/{strippedPath}. If this
|
||||||
|
/// file points to the user's root dir, return an empty string
|
||||||
|
///
|
||||||
|
/// See: [strippedPath]
|
||||||
|
String get strippedPathWithEmpty {
|
||||||
|
final path = strippedPath;
|
||||||
|
return path == "." ? "" : path;
|
||||||
|
}
|
||||||
|
|
||||||
|
String get filename => path_lib.basename(fdPath);
|
||||||
|
|
||||||
|
/// Compare the server identity of two Files
|
||||||
|
///
|
||||||
|
/// Return true if two Files point to the same file on server. Be careful that
|
||||||
|
/// this does NOT mean that the two Files are identical
|
||||||
|
bool compareServerIdentity(FileDescriptor other) {
|
||||||
|
try {
|
||||||
|
return fdId == other.fdId;
|
||||||
|
} catch (_) {
|
||||||
|
return fdPath == other.fdPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// hashCode to be used with [compareServerIdentity]
|
||||||
|
int get identityHashCode => fdId.hashCode;
|
||||||
|
}
|
|
@ -2,32 +2,34 @@ import 'package:nc_photos/account.dart';
|
||||||
import 'package:nc_photos/api/api_util.dart' as api_util;
|
import 'package:nc_photos/api/api_util.dart' as api_util;
|
||||||
import 'package:nc_photos/ci_string.dart';
|
import 'package:nc_photos/ci_string.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/platform/k.dart' as platform_k;
|
import 'package:nc_photos/platform/k.dart' as platform_k;
|
||||||
import 'package:nc_photos/remote_storage_util.dart' as remote_storage_util;
|
import 'package:nc_photos/remote_storage_util.dart' as remote_storage_util;
|
||||||
import 'package:nc_photos/string_extension.dart';
|
import 'package:nc_photos/string_extension.dart';
|
||||||
import 'package:path/path.dart' as path_lib;
|
import 'package:path/path.dart' as path_lib;
|
||||||
|
|
||||||
bool isSupportedMime(String mime) => _supportedFormatMimes.contains(mime);
|
bool isSupportedMime(String mime) => supportedFormatMimes.contains(mime);
|
||||||
|
|
||||||
bool isSupportedFormat(File file) => isSupportedMime(file.contentType ?? "");
|
bool isSupportedFormat(FileDescriptor file) =>
|
||||||
|
isSupportedMime(file.fdMime ?? "");
|
||||||
|
|
||||||
bool isSupportedImageMime(String mime) =>
|
bool isSupportedImageMime(String mime) =>
|
||||||
isSupportedMime(mime) && mime.startsWith("image/") == true;
|
supportedImageFormatMimes.contains(mime);
|
||||||
|
|
||||||
bool isSupportedImageFormat(File file) =>
|
bool isSupportedImageFormat(FileDescriptor file) =>
|
||||||
isSupportedImageMime(file.contentType ?? "");
|
isSupportedImageMime(file.fdMime ?? "");
|
||||||
|
|
||||||
bool isSupportedVideoFormat(File file) =>
|
bool isSupportedVideoFormat(FileDescriptor file) =>
|
||||||
isSupportedFormat(file) && file.contentType?.startsWith("video/") == true;
|
isSupportedFormat(file) && file.fdMime?.startsWith("video/") == true;
|
||||||
|
|
||||||
bool isMetadataSupportedMime(String mime) =>
|
bool isMetadataSupportedMime(String mime) =>
|
||||||
_metadataSupportedFormatMimes.contains(mime);
|
_metadataSupportedFormatMimes.contains(mime);
|
||||||
|
|
||||||
bool isMetadataSupportedFormat(File file) =>
|
bool isMetadataSupportedFormat(FileDescriptor file) =>
|
||||||
isMetadataSupportedMime(file.contentType ?? "");
|
isMetadataSupportedMime(file.fdMime ?? "");
|
||||||
|
|
||||||
bool isTrash(Account account, File file) =>
|
bool isTrash(Account account, FileDescriptor file) =>
|
||||||
file.path.startsWith(api_util.getTrashbinPath(account));
|
file.fdPath.startsWith(api_util.getTrashbinPath(account));
|
||||||
|
|
||||||
bool isAlbumFile(Account account, File file) =>
|
bool isAlbumFile(Account account, File file) =>
|
||||||
file.path.startsWith(remote_storage_util.getRemoteAlbumsDir(account));
|
file.path.startsWith(remote_storage_util.getRemoteAlbumsDir(account));
|
||||||
|
@ -94,7 +96,7 @@ bool isMissingMetadata(File file) =>
|
||||||
isSupportedImageFormat(file) &&
|
isSupportedImageFormat(file) &&
|
||||||
(file.metadata == null || file.location == null);
|
(file.metadata == null || file.location == null);
|
||||||
|
|
||||||
final _supportedFormatMimes = [
|
final supportedFormatMimes = [
|
||||||
"image/jpeg",
|
"image/jpeg",
|
||||||
"image/png",
|
"image/png",
|
||||||
"image/webp",
|
"image/webp",
|
||||||
|
@ -105,6 +107,9 @@ final _supportedFormatMimes = [
|
||||||
if (platform_k.isAndroid || platform_k.isWeb) "video/webm",
|
if (platform_k.isAndroid || platform_k.isWeb) "video/webm",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
final supportedImageFormatMimes =
|
||||||
|
supportedFormatMimes.where((f) => f.startsWith("image/")).toList();
|
||||||
|
|
||||||
const _metadataSupportedFormatMimes = [
|
const _metadataSupportedFormatMimes = [
|
||||||
"image/jpeg",
|
"image/jpeg",
|
||||||
"image/heic",
|
"image/heic",
|
||||||
|
|
|
@ -4,6 +4,7 @@ import 'package:nc_photos/account.dart';
|
||||||
import 'package:nc_photos/ci_string.dart';
|
import 'package:nc_photos/ci_string.dart';
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/entity/search.dart';
|
import 'package:nc_photos/entity/search.dart';
|
||||||
import 'package:nc_photos/entity/search_util.dart' as search_util;
|
import 'package:nc_photos/entity/search_util.dart' as search_util;
|
||||||
import 'package:nc_photos/entity/sqlite_table_converter.dart';
|
import 'package:nc_photos/entity/sqlite_table_converter.dart';
|
||||||
|
|
|
@ -5,6 +5,7 @@ import 'package:nc_photos/account.dart';
|
||||||
import 'package:nc_photos/api/api.dart';
|
import 'package:nc_photos/api/api.dart';
|
||||||
import 'package:nc_photos/ci_string.dart';
|
import 'package:nc_photos/ci_string.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/entity/share.dart';
|
import 'package:nc_photos/entity/share.dart';
|
||||||
import 'package:nc_photos/exception.dart';
|
import 'package:nc_photos/exception.dart';
|
||||||
import 'package:nc_photos/type.dart';
|
import 'package:nc_photos/type.dart';
|
||||||
|
|
|
@ -8,6 +8,7 @@ import 'package:nc_photos/entity/album/provider.dart';
|
||||||
import 'package:nc_photos/entity/album/sort_provider.dart';
|
import 'package:nc_photos/entity/album/sort_provider.dart';
|
||||||
import 'package:nc_photos/entity/exif.dart';
|
import 'package:nc_photos/entity/exif.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/entity/person.dart';
|
import 'package:nc_photos/entity/person.dart';
|
||||||
import 'package:nc_photos/entity/sqlite_table.dart' as sql;
|
import 'package:nc_photos/entity/sqlite_table.dart' as sql;
|
||||||
import 'package:nc_photos/entity/sqlite_table_extension.dart' as sql;
|
import 'package:nc_photos/entity/sqlite_table_extension.dart' as sql;
|
||||||
|
|
|
@ -3,6 +3,8 @@ import 'package:logging/logging.dart';
|
||||||
import 'package:nc_photos/account.dart' as app;
|
import 'package:nc_photos/account.dart' as app;
|
||||||
import 'package:nc_photos/ci_string.dart';
|
import 'package:nc_photos/ci_string.dart';
|
||||||
import 'package:nc_photos/entity/file.dart' as app;
|
import 'package:nc_photos/entity/file.dart' as app;
|
||||||
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
|
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
||||||
import 'package:nc_photos/entity/sqlite_table.dart';
|
import 'package:nc_photos/entity/sqlite_table.dart';
|
||||||
import 'package:nc_photos/entity/sqlite_table_converter.dart';
|
import 'package:nc_photos/entity/sqlite_table_converter.dart';
|
||||||
import 'package:nc_photos/entity/sqlite_table_isolate.dart';
|
import 'package:nc_photos/entity/sqlite_table_isolate.dart';
|
||||||
|
@ -508,6 +510,48 @@ extension SqliteDbExtension on SqliteDb {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<int> countMissingMetadataByFileIds({
|
||||||
|
Account? sqlAccount,
|
||||||
|
app.Account? appAccount,
|
||||||
|
required List<int> fileIds,
|
||||||
|
}) async {
|
||||||
|
assert((sqlAccount != null) != (appAccount != null));
|
||||||
|
final counts = await fileIds.withPartition((sublist) async {
|
||||||
|
final count = countAll(
|
||||||
|
filter:
|
||||||
|
images.lastUpdated.isNull() | imageLocations.version.isNull());
|
||||||
|
final query = selectOnly(files).join([
|
||||||
|
innerJoin(accountFiles, accountFiles.file.equalsExp(files.rowId),
|
||||||
|
useColumns: false),
|
||||||
|
if (appAccount != null) ...[
|
||||||
|
innerJoin(accounts, accounts.rowId.equalsExp(accountFiles.account),
|
||||||
|
useColumns: false),
|
||||||
|
innerJoin(servers, servers.rowId.equalsExp(accounts.server),
|
||||||
|
useColumns: false),
|
||||||
|
],
|
||||||
|
leftOuterJoin(images, images.accountFile.equalsExp(accountFiles.rowId),
|
||||||
|
useColumns: false),
|
||||||
|
leftOuterJoin(imageLocations,
|
||||||
|
imageLocations.accountFile.equalsExp(accountFiles.rowId),
|
||||||
|
useColumns: false),
|
||||||
|
]);
|
||||||
|
query.addColumns([count]);
|
||||||
|
if (sqlAccount != null) {
|
||||||
|
query.where(accountFiles.account.equals(sqlAccount.rowId));
|
||||||
|
} else if (appAccount != null) {
|
||||||
|
query
|
||||||
|
..where(servers.address.equals(appAccount.url))
|
||||||
|
..where(accounts.userId
|
||||||
|
.equals(appAccount.userId.toCaseInsensitiveString()));
|
||||||
|
}
|
||||||
|
query
|
||||||
|
..where(files.fileId.isIn(sublist))
|
||||||
|
..where(whereFileIsSupportedImageMime());
|
||||||
|
return [await query.map((r) => r.read(count)).getSingle()];
|
||||||
|
}, maxByFileIdsSize);
|
||||||
|
return counts.reduce((value, element) => value + element);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> truncate() async {
|
Future<void> truncate() async {
|
||||||
await delete(servers).go();
|
await delete(servers).go();
|
||||||
// technically deleting Servers table is enough to clear the followings, but
|
// technically deleting Servers table is enough to clear the followings, but
|
||||||
|
@ -528,6 +572,18 @@ extension SqliteDbExtension on SqliteDb {
|
||||||
await customStatement("UPDATE sqlite_sequence SET seq=0;");
|
await customStatement("UPDATE sqlite_sequence SET seq=0;");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Expression<bool?> whereFileIsSupportedMime() {
|
||||||
|
return file_util.supportedFormatMimes
|
||||||
|
.map<Expression<bool?>>((m) => files.contentType.equals(m))
|
||||||
|
.reduce((value, element) => value | element);
|
||||||
|
}
|
||||||
|
|
||||||
|
Expression<bool?> whereFileIsSupportedImageMime() {
|
||||||
|
return file_util.supportedImageFormatMimes
|
||||||
|
.map<Expression<bool?>>((m) => files.contentType.equals(m))
|
||||||
|
.reduce((value, element) => value | element);
|
||||||
|
}
|
||||||
|
|
||||||
static final _log = Logger("entity.sqlite_table_extension.SqliteDbExtension");
|
static final _log = Logger("entity.sqlite_table_extension.SqliteDbExtension");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
|
|
||||||
class CustomizableMaterialPageRoute extends MaterialPageRoute {
|
class CustomizableMaterialPageRoute extends MaterialPageRoute {
|
||||||
CustomizableMaterialPageRoute({
|
CustomizableMaterialPageRoute({
|
||||||
|
@ -18,4 +18,4 @@ class CustomizableMaterialPageRoute extends MaterialPageRoute {
|
||||||
final Duration reverseTransitionDuration;
|
final Duration reverseTransitionDuration;
|
||||||
}
|
}
|
||||||
|
|
||||||
String getImageHeroTag(File file) => "imageHero(${file.path})";
|
String getImageHeroTag(FileDescriptor file) => "imageHero(${file.fdPath})";
|
||||||
|
|
|
@ -14,4 +14,6 @@ String getRemoteLinkSharesDir(Account account) =>
|
||||||
"${getRemoteStorageDir(account)}/link_shares";
|
"${getRemoteStorageDir(account)}/link_shares";
|
||||||
|
|
||||||
String getRemoteStorageDir(Account account) =>
|
String getRemoteStorageDir(Account account) =>
|
||||||
"${api_util.getWebdavRootUrlRelative(account)}/.com.nkming.nc_photos";
|
"${api_util.getWebdavRootUrlRelative(account)}/$remoteStorageDirRelativePath";
|
||||||
|
|
||||||
|
const remoteStorageDirRelativePath = ".com.nkming.nc_photos";
|
||||||
|
|
|
@ -13,6 +13,7 @@ import 'package:nc_photos/app_localizations.dart';
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
import 'package:nc_photos/entity/file/data_source.dart';
|
import 'package:nc_photos/entity/file/data_source.dart';
|
||||||
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
||||||
import 'package:nc_photos/event/event.dart';
|
import 'package:nc_photos/event/event.dart';
|
||||||
import 'package:nc_photos/event/native_event.dart';
|
import 'package:nc_photos/event/native_event.dart';
|
||||||
|
|
|
@ -10,6 +10,7 @@ import 'package:nc_photos/app_localizations.dart';
|
||||||
import 'package:nc_photos/debug_util.dart';
|
import 'package:nc_photos/debug_util.dart';
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
||||||
import 'package:nc_photos/entity/local_file.dart';
|
import 'package:nc_photos/entity/local_file.dart';
|
||||||
import 'package:nc_photos/entity/share.dart';
|
import 'package:nc_photos/entity/share.dart';
|
||||||
|
@ -26,6 +27,7 @@ import 'package:nc_photos/use_case/create_dir.dart';
|
||||||
import 'package:nc_photos/use_case/create_share.dart';
|
import 'package:nc_photos/use_case/create_share.dart';
|
||||||
import 'package:nc_photos/use_case/download_file.dart';
|
import 'package:nc_photos/use_case/download_file.dart';
|
||||||
import 'package:nc_photos/use_case/download_preview.dart';
|
import 'package:nc_photos/use_case/download_preview.dart';
|
||||||
|
import 'package:nc_photos/use_case/inflate_file_descriptor.dart';
|
||||||
import 'package:nc_photos/use_case/share_local.dart';
|
import 'package:nc_photos/use_case/share_local.dart';
|
||||||
import 'package:nc_photos/widget/processing_dialog.dart';
|
import 'package:nc_photos/widget/processing_dialog.dart';
|
||||||
import 'package:nc_photos/widget/share_link_multiple_files_dialog.dart';
|
import 'package:nc_photos/widget/share_link_multiple_files_dialog.dart';
|
||||||
|
@ -36,10 +38,14 @@ import 'package:tuple/tuple.dart';
|
||||||
|
|
||||||
/// Handle sharing to other apps
|
/// Handle sharing to other apps
|
||||||
class ShareHandler {
|
class ShareHandler {
|
||||||
ShareHandler({
|
ShareHandler(
|
||||||
|
this._c, {
|
||||||
required this.context,
|
required this.context,
|
||||||
this.clearSelection,
|
this.clearSelection,
|
||||||
});
|
}) : assert(require(_c)),
|
||||||
|
assert(InflateFileDescriptor.require(_c));
|
||||||
|
|
||||||
|
static bool require(DiContainer c) => true;
|
||||||
|
|
||||||
Future<void> shareLocalFiles(List<LocalFile> files) async {
|
Future<void> shareLocalFiles(List<LocalFile> files) async {
|
||||||
if (!isSelectionCleared) {
|
if (!isSelectionCleared) {
|
||||||
|
@ -67,7 +73,8 @@ class ShareHandler {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> shareFiles(Account account, List<File> files) async {
|
Future<void> shareFiles(Account account, List<FileDescriptor> fds) async {
|
||||||
|
final files = await InflateFileDescriptor(_c)(account, fds);
|
||||||
try {
|
try {
|
||||||
final method = await _askShareMethod(files);
|
final method = await _askShareMethod(files);
|
||||||
if (method == null) {
|
if (method == null) {
|
||||||
|
@ -336,6 +343,7 @@ class ShareHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final DiContainer _c;
|
||||||
final BuildContext context;
|
final BuildContext context;
|
||||||
final VoidCallback? clearSelection;
|
final VoidCallback? clearSelection;
|
||||||
var isSelectionCleared = false;
|
var isSelectionCleared = false;
|
||||||
|
|
|
@ -4,6 +4,7 @@ import 'package:logging/logging.dart';
|
||||||
import 'package:nc_photos/account.dart';
|
import 'package:nc_photos/account.dart';
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/exception.dart';
|
import 'package:nc_photos/exception.dart';
|
||||||
import 'package:nc_photos/mobile/platform.dart'
|
import 'package:nc_photos/mobile/platform.dart'
|
||||||
if (dart.library.html) 'package:nc_photos/web/platform.dart' as platform;
|
if (dart.library.html) 'package:nc_photos/web/platform.dart' as platform;
|
||||||
|
|
|
@ -6,6 +6,7 @@ import 'package:nc_photos/entity/album.dart';
|
||||||
import 'package:nc_photos/entity/album/item.dart';
|
import 'package:nc_photos/entity/album/item.dart';
|
||||||
import 'package:nc_photos/entity/album/provider.dart';
|
import 'package:nc_photos/entity/album/provider.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/entity/share.dart';
|
import 'package:nc_photos/entity/share.dart';
|
||||||
import 'package:nc_photos/override_comparator.dart';
|
import 'package:nc_photos/override_comparator.dart';
|
||||||
import 'package:nc_photos/use_case/create_share.dart';
|
import 'package:nc_photos/use_case/create_share.dart';
|
||||||
|
|
|
@ -2,6 +2,7 @@ import 'package:logging/logging.dart';
|
||||||
import 'package:nc_photos/account.dart';
|
import 'package:nc_photos/account.dart';
|
||||||
import 'package:nc_photos/api/api_util.dart' as api_util;
|
import 'package:nc_photos/api/api_util.dart' as api_util;
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/exception.dart';
|
import 'package:nc_photos/exception.dart';
|
||||||
import 'package:nc_photos/remote_storage_util.dart' as remote_storage_util;
|
import 'package:nc_photos/remote_storage_util.dart' as remote_storage_util;
|
||||||
import 'package:nc_photos/use_case/create_dir.dart';
|
import 'package:nc_photos/use_case/create_dir.dart';
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import 'package:nc_photos/account.dart';
|
import 'package:nc_photos/account.dart';
|
||||||
import 'package:nc_photos/api/api.dart';
|
import 'package:nc_photos/api/api.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/mobile/platform.dart'
|
import 'package:nc_photos/mobile/platform.dart'
|
||||||
if (dart.library.html) 'package:nc_photos/web/platform.dart' as platform;
|
if (dart.library.html) 'package:nc_photos/web/platform.dart' as platform;
|
||||||
import 'package:nc_photos/platform/download.dart';
|
import 'package:nc_photos/platform/download.dart';
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import 'package:nc_photos/account.dart';
|
import 'package:nc_photos/account.dart';
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/entity/album.dart';
|
import 'package:nc_photos/entity/album.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/remote_storage_util.dart' as remote_storage_util;
|
import 'package:nc_photos/remote_storage_util.dart' as remote_storage_util;
|
||||||
import 'package:nc_photos/use_case/ls_single_file.dart';
|
import 'package:nc_photos/use_case/ls_single_file.dart';
|
||||||
import 'package:nc_photos/use_case/move.dart';
|
import 'package:nc_photos/use_case/move.dart';
|
||||||
|
|
|
@ -2,7 +2,7 @@ import 'package:logging/logging.dart';
|
||||||
import 'package:nc_photos/account.dart';
|
import 'package:nc_photos/account.dart';
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/entity/album.dart';
|
import 'package:nc_photos/entity/album.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/pref.dart';
|
import 'package:nc_photos/pref.dart';
|
||||||
import 'package:nc_photos/remote_storage_util.dart' as remote_storage_util;
|
import 'package:nc_photos/remote_storage_util.dart' as remote_storage_util;
|
||||||
import 'package:nc_photos/use_case/list_potential_shared_album.dart';
|
import 'package:nc_photos/use_case/list_potential_shared_album.dart';
|
||||||
|
|
32
app/lib/use_case/inflate_file_descriptor.dart
Normal file
32
app/lib/use_case/inflate_file_descriptor.dart
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
import 'package:nc_photos/account.dart';
|
||||||
|
import 'package:nc_photos/di_container.dart';
|
||||||
|
import 'package:nc_photos/entity/file.dart';
|
||||||
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
|
import 'package:nc_photos/use_case/find_file.dart';
|
||||||
|
|
||||||
|
class InflateFileDescriptor {
|
||||||
|
InflateFileDescriptor(this._c)
|
||||||
|
: assert(require(_c)),
|
||||||
|
assert(FindFile.require(_c));
|
||||||
|
|
||||||
|
static bool require(DiContainer c) => true;
|
||||||
|
|
||||||
|
/// Turn a list of FileDescriptors to the corresponding Files
|
||||||
|
///
|
||||||
|
/// The conversion is done by looking up the files in the database. No lookup
|
||||||
|
/// will be done for File objects in [fds]
|
||||||
|
Future<List<File>> call(Account account, List<FileDescriptor> fds) async {
|
||||||
|
final found = await FindFile(_c)(
|
||||||
|
account, fds.where((e) => e is! File).map((e) => e.fdId).toList());
|
||||||
|
final foundMap = Map.fromEntries(found.map((e) => MapEntry(e.fileId!, e)));
|
||||||
|
return fds.map((e) {
|
||||||
|
if (e is File) {
|
||||||
|
return e;
|
||||||
|
} else {
|
||||||
|
return foundMap[e.fdId]!;
|
||||||
|
}
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
final DiContainer _c;
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ import 'package:drift/drift.dart' as sql;
|
||||||
import 'package:nc_photos/account.dart';
|
import 'package:nc_photos/account.dart';
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/entity/sqlite_table_converter.dart';
|
import 'package:nc_photos/entity/sqlite_table_converter.dart';
|
||||||
import 'package:nc_photos/entity/sqlite_table_extension.dart' as sql;
|
import 'package:nc_photos/entity/sqlite_table_extension.dart' as sql;
|
||||||
import 'package:nc_photos/location_util.dart' as location_util;
|
import 'package:nc_photos/location_util.dart' as location_util;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:nc_photos/account.dart';
|
import 'package:nc_photos/account.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
||||||
import 'package:nc_photos/pref.dart';
|
import 'package:nc_photos/pref.dart';
|
||||||
import 'package:nc_photos/use_case/ls.dart';
|
import 'package:nc_photos/use_case/ls.dart';
|
||||||
|
|
|
@ -9,6 +9,7 @@ import 'package:nc_photos/entity/album.dart';
|
||||||
import 'package:nc_photos/entity/album/item.dart';
|
import 'package:nc_photos/entity/album/item.dart';
|
||||||
import 'package:nc_photos/entity/album/provider.dart';
|
import 'package:nc_photos/entity/album/provider.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/event/event.dart';
|
import 'package:nc_photos/event/event.dart';
|
||||||
import 'package:nc_photos/iterable_extension.dart';
|
import 'package:nc_photos/iterable_extension.dart';
|
||||||
import 'package:nc_photos/stream_extension.dart';
|
import 'package:nc_photos/stream_extension.dart';
|
||||||
|
|
|
@ -6,6 +6,7 @@ import 'package:nc_photos/entity/album/cover_provider.dart';
|
||||||
import 'package:nc_photos/entity/album/item.dart';
|
import 'package:nc_photos/entity/album/item.dart';
|
||||||
import 'package:nc_photos/entity/album/provider.dart';
|
import 'package:nc_photos/entity/album/provider.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/iterable_extension.dart';
|
import 'package:nc_photos/iterable_extension.dart';
|
||||||
import 'package:nc_photos/use_case/preprocess_album.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/unshare_file_from_album.dart';
|
||||||
|
|
|
@ -3,15 +3,15 @@ import 'dart:convert';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:nc_photos/account.dart';
|
import 'package:nc_photos/account.dart';
|
||||||
import 'package:nc_photos/api/api.dart';
|
import 'package:nc_photos/api/api.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/exception.dart';
|
import 'package:nc_photos/exception.dart';
|
||||||
import 'package:nc_photos/type.dart';
|
import 'package:nc_photos/type.dart';
|
||||||
|
|
||||||
class RequestPublicLink {
|
class RequestPublicLink {
|
||||||
/// Request a temporary unique public link to [file]
|
/// Request a temporary unique public link to [file]
|
||||||
Future<String> call(Account account, File file) async {
|
Future<String> call(Account account, FileDescriptor file) async {
|
||||||
final response =
|
final response =
|
||||||
await Api(account).ocs().dav().direct().post(fileId: file.fileId!);
|
await Api(account).ocs().dav().direct().post(fileId: file.fdId);
|
||||||
if (!response.isGood) {
|
if (!response.isGood) {
|
||||||
_log.severe("[call] Failed requesting server: $response");
|
_log.severe("[call] Failed requesting server: $response");
|
||||||
throw ApiException(
|
throw ApiException(
|
||||||
|
|
|
@ -3,6 +3,7 @@ import 'package:kiwi/kiwi.dart';
|
||||||
import 'package:nc_photos/account.dart';
|
import 'package:nc_photos/account.dart';
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/event/event.dart';
|
import 'package:nc_photos/event/event.dart';
|
||||||
import 'package:nc_photos/use_case/move.dart';
|
import 'package:nc_photos/use_case/move.dart';
|
||||||
|
|
||||||
|
|
|
@ -2,16 +2,18 @@ import 'package:drift/drift.dart' as sql;
|
||||||
import 'package:nc_photos/account.dart';
|
import 'package:nc_photos/account.dart';
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/entity/sqlite_table_converter.dart';
|
import 'package:nc_photos/entity/sqlite_table_converter.dart';
|
||||||
import 'package:nc_photos/entity/sqlite_table_extension.dart' as sql;
|
import 'package:nc_photos/entity/sqlite_table_extension.dart' as sql;
|
||||||
import 'package:nc_photos/object_extension.dart';
|
import 'package:nc_photos/object_extension.dart';
|
||||||
|
import 'package:nc_photos/remote_storage_util.dart' as remote_storage_util;
|
||||||
|
|
||||||
class ScanDirOffline {
|
class ScanDirOffline {
|
||||||
ScanDirOffline(this._c) : assert(require(_c));
|
ScanDirOffline(this._c) : assert(require(_c));
|
||||||
|
|
||||||
static bool require(DiContainer c) => DiContainer.has(c, DiType.sqliteDb);
|
static bool require(DiContainer c) => DiContainer.has(c, DiType.sqliteDb);
|
||||||
|
|
||||||
Future<List<File>> call(
|
Future<List<FileDescriptor>> call(
|
||||||
Account account,
|
Account account,
|
||||||
File root, {
|
File root, {
|
||||||
bool isOnlySupportedFormat = true,
|
bool isOnlySupportedFormat = true,
|
||||||
|
@ -23,36 +25,57 @@ class ScanDirOffline {
|
||||||
}, (db, Map args) async {
|
}, (db, Map args) async {
|
||||||
final Account account = args["account"];
|
final Account account = args["account"];
|
||||||
final File root = args["root"];
|
final File root = args["root"];
|
||||||
|
final strippedPath = root.strippedPathWithEmpty;
|
||||||
final bool isOnlySupportedFormat = args["isOnlySupportedFormat"];
|
final bool isOnlySupportedFormat = args["isOnlySupportedFormat"];
|
||||||
final dbFiles = await db.useInIsolate((db) async {
|
final dbFiles = await db.useInIsolate((db) async {
|
||||||
final query = db.queryFiles().run((q) {
|
final query = db.queryFiles().run((q) {
|
||||||
q
|
q
|
||||||
..setQueryMode(sql.FilesQueryMode.completeFile)
|
..setQueryMode(
|
||||||
|
sql.FilesQueryMode.expression,
|
||||||
|
expressions: [
|
||||||
|
db.accountFiles.relativePath,
|
||||||
|
db.files.fileId,
|
||||||
|
db.files.contentType,
|
||||||
|
db.accountFiles.isArchived,
|
||||||
|
db.accountFiles.isFavorite,
|
||||||
|
db.accountFiles.bestDateTime,
|
||||||
|
],
|
||||||
|
)
|
||||||
..setAppAccount(account);
|
..setAppAccount(account);
|
||||||
root.strippedPathWithEmpty.run((p) {
|
if (strippedPath.isNotEmpty) {
|
||||||
if (p.isNotEmpty) {
|
q.byOrRelativePathPattern("$strippedPath/%");
|
||||||
q.byOrRelativePathPattern("$p/%");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (isOnlySupportedFormat) {
|
|
||||||
q
|
|
||||||
..byMimePattern("image/%")
|
|
||||||
..byMimePattern("video/%");
|
|
||||||
}
|
}
|
||||||
return q.build();
|
return q.build();
|
||||||
});
|
});
|
||||||
|
if (isOnlySupportedFormat) {
|
||||||
|
query.where(db.whereFileIsSupportedMime());
|
||||||
|
}
|
||||||
|
if (strippedPath.isEmpty) {
|
||||||
|
query.where(db.accountFiles.relativePath
|
||||||
|
.like("${remote_storage_util.remoteStorageDirRelativePath}/%")
|
||||||
|
.not());
|
||||||
|
}
|
||||||
return await query
|
return await query
|
||||||
.map((r) => sql.CompleteFile(
|
.map((r) => <String, dynamic>{
|
||||||
r.readTable(db.files),
|
"relativePath": r.read(db.accountFiles.relativePath)!,
|
||||||
r.readTable(db.accountFiles),
|
"fileId": r.read(db.files.fileId)!,
|
||||||
r.readTableOrNull(db.images),
|
"contentType": r.read(db.files.contentType)!,
|
||||||
r.readTableOrNull(db.imageLocations),
|
"isArchived": r.read(db.accountFiles.isArchived),
|
||||||
r.readTableOrNull(db.trashes),
|
"isFavorite": r.read(db.accountFiles.isFavorite),
|
||||||
))
|
"bestDateTime": r.read(db.accountFiles.bestDateTime)!.toUtc(),
|
||||||
|
})
|
||||||
.get();
|
.get();
|
||||||
});
|
});
|
||||||
return dbFiles
|
return dbFiles
|
||||||
.map((f) => SqliteFileConverter.fromSql(account.userId.toString(), f))
|
.map((f) => FileDescriptor(
|
||||||
|
fdPath:
|
||||||
|
"remote.php/dav/files/${account.userId.toString()}/${f["relativePath"]}",
|
||||||
|
fdId: f["fileId"],
|
||||||
|
fdMime: f["contentType"],
|
||||||
|
fdIsArchived: f["isArchived"] ?? false,
|
||||||
|
fdIsFavorite: f["isFavorite"] ?? false,
|
||||||
|
fdDateTime: f["bestDateTime"],
|
||||||
|
))
|
||||||
.toList();
|
.toList();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -83,13 +106,11 @@ class ScanDirOfflineMini {
|
||||||
}
|
}
|
||||||
q.byOrRelativePathPattern("$path/%");
|
q.byOrRelativePathPattern("$path/%");
|
||||||
}
|
}
|
||||||
if (isOnlySupportedFormat) {
|
|
||||||
q
|
|
||||||
..byMimePattern("image/%")
|
|
||||||
..byMimePattern("video/%");
|
|
||||||
}
|
|
||||||
return q.build();
|
return q.build();
|
||||||
});
|
});
|
||||||
|
if (isOnlySupportedFormat) {
|
||||||
|
query.where(db.whereFileIsSupportedMime());
|
||||||
|
}
|
||||||
query
|
query
|
||||||
..orderBy([sql.OrderingTerm.desc(db.accountFiles.bestDateTime)])
|
..orderBy([sql.OrderingTerm.desc(db.accountFiles.bestDateTime)])
|
||||||
..limit(limit);
|
..limit(limit);
|
||||||
|
|
109
app/lib/use_case/sync_dir.dart
Normal file
109
app/lib/use_case/sync_dir.dart
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:nc_photos/account.dart';
|
||||||
|
import 'package:nc_photos/debug_util.dart';
|
||||||
|
import 'package:nc_photos/di_container.dart';
|
||||||
|
import 'package:nc_photos/entity/file.dart';
|
||||||
|
import 'package:nc_photos/entity/file/data_source.dart';
|
||||||
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
|
import 'package:nc_photos/entity/sqlite_table_extension.dart' as sql;
|
||||||
|
import 'package:nc_photos/object_extension.dart';
|
||||||
|
import 'package:nc_photos/remote_storage_util.dart' as remote_storage_util;
|
||||||
|
import 'package:nc_photos/use_case/ls_single_file.dart';
|
||||||
|
import 'package:tuple/tuple.dart';
|
||||||
|
|
||||||
|
class SyncDir {
|
||||||
|
SyncDir(this._c) : assert(require(_c));
|
||||||
|
|
||||||
|
static bool require(DiContainer c) =>
|
||||||
|
DiContainer.has(c, DiType.fileRepoRemote) &&
|
||||||
|
DiContainer.has(c, DiType.sqliteDb) &&
|
||||||
|
DiContainer.has(c, DiType.touchManager);
|
||||||
|
|
||||||
|
/// Sync local SQLite DB with remote content
|
||||||
|
///
|
||||||
|
/// Return true if some of the files have changed
|
||||||
|
Future<bool> call(
|
||||||
|
Account account,
|
||||||
|
String dirPath, {
|
||||||
|
bool isRecursive = true,
|
||||||
|
}) async {
|
||||||
|
final dirCache = await _queryAllSubDirEtags(account, dirPath);
|
||||||
|
final remoteRoot =
|
||||||
|
await LsSingleFile(_c.withRemoteFileRepo())(account, dirPath);
|
||||||
|
return await _syncDir(account, remoteRoot, dirCache,
|
||||||
|
isRecursive: isRecursive);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> _syncDir(
|
||||||
|
Account account,
|
||||||
|
File remoteDir,
|
||||||
|
Map<int, String> dirCache, {
|
||||||
|
required bool isRecursive,
|
||||||
|
}) async {
|
||||||
|
final status = await _checkContentUpdated(account, remoteDir, dirCache);
|
||||||
|
if (!status.item1) {
|
||||||
|
_log.finer("[_syncDir] Dir unchanged: ${remoteDir.path}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_log.info("[_syncDir] Dir changed: ${remoteDir.path}");
|
||||||
|
|
||||||
|
final children = await FileCachedDataSource(_c, shouldCheckCache: true)
|
||||||
|
.sync(account, remoteDir, remoteTouchEtag: status.item2);
|
||||||
|
if (!isRecursive) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
for (final d in children.where((c) =>
|
||||||
|
c.isCollection == true &&
|
||||||
|
!remoteDir.compareServerIdentity(c) &&
|
||||||
|
!c.path.endsWith(remote_storage_util.getRemoteStorageDir(account)))) {
|
||||||
|
try {
|
||||||
|
await _syncDir(account, d, dirCache, isRecursive: isRecursive);
|
||||||
|
} catch (e, stackTrace) {
|
||||||
|
_log.severe("[_syncDir] Failed while _syncDir: ${logFilename(d.path)}",
|
||||||
|
e, stackTrace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Tuple2<bool, String?>> _checkContentUpdated(
|
||||||
|
Account account, File remoteDir, Map<int, String> dirCache) async {
|
||||||
|
String? touchResult;
|
||||||
|
try {
|
||||||
|
touchResult = await _c.touchManager.checkTouchEtag(account, remoteDir);
|
||||||
|
if (touchResult == null &&
|
||||||
|
dirCache[remoteDir.fileId!] == remoteDir.etag!) {
|
||||||
|
return const Tuple2(false, null);
|
||||||
|
}
|
||||||
|
} catch (e, stackTrace) {
|
||||||
|
_log.severe("[_isContentUpdated] Uncaught exception", e, stackTrace);
|
||||||
|
}
|
||||||
|
return Tuple2(true, touchResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Map<int, String>> _queryAllSubDirEtags(
|
||||||
|
Account account, String dirPath) async {
|
||||||
|
final dir = File(path: dirPath);
|
||||||
|
return await _c.sqliteDb.use((db) async {
|
||||||
|
final query = db.queryFiles().run((q) {
|
||||||
|
q
|
||||||
|
..setQueryMode(sql.FilesQueryMode.expression,
|
||||||
|
expressions: [db.files.fileId, db.files.etag])
|
||||||
|
..setAppAccount(account);
|
||||||
|
if (dir.strippedPathWithEmpty.isNotEmpty) {
|
||||||
|
q.byOrRelativePathPattern("${dir.strippedPathWithEmpty}/%");
|
||||||
|
}
|
||||||
|
return q.build();
|
||||||
|
});
|
||||||
|
query.where(db.files.isCollection.equals(true));
|
||||||
|
return Map.fromEntries(await query
|
||||||
|
.map(
|
||||||
|
(r) => MapEntry(r.read(db.files.fileId)!, r.read(db.files.etag)!))
|
||||||
|
.get());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
final DiContainer _c;
|
||||||
|
|
||||||
|
static final _log = Logger("use_case.sync_dir.SyncDir");
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
import 'package:nc_photos/account.dart';
|
import 'package:nc_photos/account.dart';
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/entity/album.dart';
|
import 'package:nc_photos/entity/album.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/remote_storage_util.dart' as remote_storage_util;
|
import 'package:nc_photos/remote_storage_util.dart' as remote_storage_util;
|
||||||
import 'package:nc_photos/use_case/move.dart';
|
import 'package:nc_photos/use_case/move.dart';
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ import 'package:nc_photos/entity/album.dart';
|
||||||
import 'package:nc_photos/entity/album/item.dart';
|
import 'package:nc_photos/entity/album/item.dart';
|
||||||
import 'package:nc_photos/entity/album/provider.dart';
|
import 'package:nc_photos/entity/album/provider.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/entity/share.dart';
|
import 'package:nc_photos/entity/share.dart';
|
||||||
import 'package:nc_photos/stream_extension.dart';
|
import 'package:nc_photos/stream_extension.dart';
|
||||||
import 'package:nc_photos/use_case/list_album.dart';
|
import 'package:nc_photos/use_case/list_album.dart';
|
||||||
|
|
|
@ -2,7 +2,7 @@ import 'package:nc_photos/entity/album.dart';
|
||||||
import 'package:nc_photos/entity/album/cover_provider.dart';
|
import 'package:nc_photos/entity/album/cover_provider.dart';
|
||||||
import 'package:nc_photos/entity/album/item.dart';
|
import 'package:nc_photos/entity/album/item.dart';
|
||||||
import 'package:nc_photos/entity/album/sort_provider.dart';
|
import 'package:nc_photos/entity/album/sort_provider.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
||||||
|
|
||||||
class UpdateAutoAlbumCover {
|
class UpdateAutoAlbumCover {
|
||||||
|
|
|
@ -389,7 +389,8 @@ class _AlbumBrowserState extends State<AlbumBrowser>
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onDownloadPressed() {
|
void _onDownloadPressed() {
|
||||||
DownloadHandler().downloadFiles(
|
final c = KiwiContainer().resolve<DiContainer>();
|
||||||
|
DownloadHandler(c).downloadFiles(
|
||||||
widget.account,
|
widget.account,
|
||||||
_sortedItems.whereType<AlbumFileItem>().map((e) => e.file).toList(),
|
_sortedItems.whereType<AlbumFileItem>().map((e) => e.file).toList(),
|
||||||
parentDir: _album!.name,
|
parentDir: _album!.name,
|
||||||
|
@ -419,6 +420,7 @@ class _AlbumBrowserState extends State<AlbumBrowser>
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onSelectionSharePressed(BuildContext context) {
|
void _onSelectionSharePressed(BuildContext context) {
|
||||||
|
final c = KiwiContainer().resolve<DiContainer>();
|
||||||
final selected = selectedListItems
|
final selected = selectedListItems
|
||||||
.whereType<_FileListItem>()
|
.whereType<_FileListItem>()
|
||||||
.map((e) => e.file)
|
.map((e) => e.file)
|
||||||
|
@ -431,6 +433,7 @@ class _AlbumBrowserState extends State<AlbumBrowser>
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ShareHandler(
|
ShareHandler(
|
||||||
|
c,
|
||||||
context: context,
|
context: context,
|
||||||
clearSelection: () {
|
clearSelection: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
|
@ -441,10 +444,11 @@ class _AlbumBrowserState extends State<AlbumBrowser>
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onSelectionAddPressed(BuildContext context) async {
|
Future<void> _onSelectionAddPressed(BuildContext context) async {
|
||||||
return AddSelectionToAlbumHandler()(
|
final c = KiwiContainer().resolve<DiContainer>();
|
||||||
|
return AddSelectionToAlbumHandler(c)(
|
||||||
context: context,
|
context: context,
|
||||||
account: widget.account,
|
account: widget.account,
|
||||||
selectedFiles: selectedListItems
|
selection: selectedListItems
|
||||||
.whereType<_FileListItem>()
|
.whereType<_FileListItem>()
|
||||||
.map((e) => e.file)
|
.map((e) => e.file)
|
||||||
.toList(),
|
.toList(),
|
||||||
|
@ -493,11 +497,12 @@ class _AlbumBrowserState extends State<AlbumBrowser>
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onSelectionDownloadPressed() {
|
void _onSelectionDownloadPressed() {
|
||||||
|
final c = KiwiContainer().resolve<DiContainer>();
|
||||||
final selected = selectedListItems
|
final selected = selectedListItems
|
||||||
.whereType<_FileListItem>()
|
.whereType<_FileListItem>()
|
||||||
.map((e) => e.file)
|
.map((e) => e.file)
|
||||||
.toList();
|
.toList();
|
||||||
DownloadHandler().downloadFiles(widget.account, selected);
|
DownloadHandler(c).downloadFiles(widget.account, selected);
|
||||||
setState(() {
|
setState(() {
|
||||||
clearSelectedItems();
|
clearSelectedItems();
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,6 +4,7 @@ import 'package:nc_photos/account.dart';
|
||||||
import 'package:nc_photos/api/api_util.dart' as api_util;
|
import 'package:nc_photos/api/api_util.dart' as api_util;
|
||||||
import 'package:nc_photos/app_localizations.dart';
|
import 'package:nc_photos/app_localizations.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/iterable_extension.dart';
|
import 'package:nc_photos/iterable_extension.dart';
|
||||||
import 'package:nc_photos/k.dart' as k;
|
import 'package:nc_photos/k.dart' as k;
|
||||||
import 'package:nc_photos/snack_bar_manager.dart';
|
import 'package:nc_photos/snack_bar_manager.dart';
|
||||||
|
|
|
@ -14,6 +14,7 @@ import 'package:nc_photos/entity/album/cover_provider.dart';
|
||||||
import 'package:nc_photos/entity/album/provider.dart';
|
import 'package:nc_photos/entity/album/provider.dart';
|
||||||
import 'package:nc_photos/entity/album/sort_provider.dart';
|
import 'package:nc_photos/entity/album/sort_provider.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
||||||
import 'package:nc_photos/exception_util.dart' as exception_util;
|
import 'package:nc_photos/exception_util.dart' as exception_util;
|
||||||
import 'package:nc_photos/iterable_extension.dart';
|
import 'package:nc_photos/iterable_extension.dart';
|
||||||
|
|
|
@ -14,6 +14,7 @@ import 'package:nc_photos/ci_string.dart';
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/entity/album.dart';
|
import 'package:nc_photos/entity/album.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
||||||
import 'package:nc_photos/entity/share.dart';
|
import 'package:nc_photos/entity/share.dart';
|
||||||
import 'package:nc_photos/entity/share/data_source.dart';
|
import 'package:nc_photos/entity/share/data_source.dart';
|
||||||
|
|
|
@ -11,6 +11,7 @@ import 'package:nc_photos/compute_queue.dart';
|
||||||
import 'package:nc_photos/debug_util.dart';
|
import 'package:nc_photos/debug_util.dart';
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/exception_util.dart' as exception_util;
|
import 'package:nc_photos/exception_util.dart' as exception_util;
|
||||||
import 'package:nc_photos/k.dart' as k;
|
import 'package:nc_photos/k.dart' as k;
|
||||||
import 'package:nc_photos/language_util.dart' as language_util;
|
import 'package:nc_photos/language_util.dart' as language_util;
|
||||||
|
@ -18,6 +19,7 @@ import 'package:nc_photos/object_extension.dart';
|
||||||
import 'package:nc_photos/pref.dart';
|
import 'package:nc_photos/pref.dart';
|
||||||
import 'package:nc_photos/snack_bar_manager.dart';
|
import 'package:nc_photos/snack_bar_manager.dart';
|
||||||
import 'package:nc_photos/theme.dart';
|
import 'package:nc_photos/theme.dart';
|
||||||
|
import 'package:nc_photos/use_case/inflate_file_descriptor.dart';
|
||||||
import 'package:nc_photos/use_case/update_property.dart';
|
import 'package:nc_photos/use_case/update_property.dart';
|
||||||
import 'package:nc_photos/widget/builder/photo_list_item_builder.dart';
|
import 'package:nc_photos/widget/builder/photo_list_item_builder.dart';
|
||||||
import 'package:nc_photos/widget/empty_list_indicator.dart';
|
import 'package:nc_photos/widget/empty_list_indicator.dart';
|
||||||
|
@ -230,7 +232,7 @@ class _ArchiveBrowserState extends State<ArchiveBrowser>
|
||||||
.unarchiveSelectedProcessingNotification(selectedListItems.length)),
|
.unarchiveSelectedProcessingNotification(selectedListItems.length)),
|
||||||
duration: k.snackBarDurationShort,
|
duration: k.snackBarDurationShort,
|
||||||
));
|
));
|
||||||
final selectedFiles = selectedListItems
|
final selection = selectedListItems
|
||||||
.whereType<PhotoListFileItem>()
|
.whereType<PhotoListFileItem>()
|
||||||
.map((e) => e.file)
|
.map((e) => e.file)
|
||||||
.toList();
|
.toList();
|
||||||
|
@ -238,6 +240,8 @@ class _ArchiveBrowserState extends State<ArchiveBrowser>
|
||||||
clearSelectedItems();
|
clearSelectedItems();
|
||||||
});
|
});
|
||||||
final c = KiwiContainer().resolve<DiContainer>();
|
final c = KiwiContainer().resolve<DiContainer>();
|
||||||
|
final selectedFiles =
|
||||||
|
await InflateFileDescriptor(c)(widget.account, selection);
|
||||||
final failures = <File>[];
|
final failures = <File>[];
|
||||||
for (final f in selectedFiles) {
|
for (final f in selectedFiles) {
|
||||||
try {
|
try {
|
||||||
|
@ -265,7 +269,7 @@ class _ArchiveBrowserState extends State<ArchiveBrowser>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _transformItems(List<File> files) {
|
void _transformItems(List<FileDescriptor> files) {
|
||||||
_buildItemQueue.addJob(
|
_buildItemQueue.addJob(
|
||||||
PhotoListItemBuilderArguments(
|
PhotoListItemBuilderArguments(
|
||||||
widget.account,
|
widget.account,
|
||||||
|
@ -293,7 +297,7 @@ class _ArchiveBrowserState extends State<ArchiveBrowser>
|
||||||
|
|
||||||
late final _bloc = ScanAccountDirBloc.of(widget.account);
|
late final _bloc = ScanAccountDirBloc.of(widget.account);
|
||||||
|
|
||||||
var _backingFiles = <File>[];
|
var _backingFiles = <FileDescriptor>[];
|
||||||
|
|
||||||
final _buildItemQueue =
|
final _buildItemQueue =
|
||||||
ComputeQueue<PhotoListItemBuilderArguments, PhotoListItemBuilderResult>();
|
ComputeQueue<PhotoListItemBuilderArguments, PhotoListItemBuilderResult>();
|
||||||
|
|
|
@ -8,7 +8,7 @@ import 'package:nc_photos/app_localizations.dart';
|
||||||
import 'package:nc_photos/cache_manager_util.dart';
|
import 'package:nc_photos/cache_manager_util.dart';
|
||||||
import 'package:nc_photos/entity/album.dart';
|
import 'package:nc_photos/entity/album.dart';
|
||||||
import 'package:nc_photos/entity/album/provider.dart';
|
import 'package:nc_photos/entity/album/provider.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/k.dart' as k;
|
import 'package:nc_photos/k.dart' as k;
|
||||||
import 'package:nc_photos/theme.dart';
|
import 'package:nc_photos/theme.dart';
|
||||||
import 'package:nc_photos/widget/album_grid_item.dart';
|
import 'package:nc_photos/widget/album_grid_item.dart';
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import 'package:collection/collection.dart' show compareNatural;
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:nc_photos/account.dart';
|
import 'package:nc_photos/account.dart';
|
||||||
|
@ -6,9 +6,8 @@ import 'package:nc_photos/api/api_util.dart' as api_util;
|
||||||
import 'package:nc_photos/app_init.dart' as app_init;
|
import 'package:nc_photos/app_init.dart' as app_init;
|
||||||
import 'package:nc_photos/app_localizations.dart';
|
import 'package:nc_photos/app_localizations.dart';
|
||||||
import 'package:nc_photos/entity/album.dart';
|
import 'package:nc_photos/entity/album.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
||||||
import 'package:nc_photos/iterable_extension.dart';
|
|
||||||
import 'package:nc_photos/k.dart' as k;
|
import 'package:nc_photos/k.dart' as k;
|
||||||
import 'package:nc_photos/object_extension.dart';
|
import 'package:nc_photos/object_extension.dart';
|
||||||
import 'package:nc_photos/widget/photo_list_item.dart';
|
import 'package:nc_photos/widget/photo_list_item.dart';
|
||||||
|
@ -32,7 +31,7 @@ class PhotoListItemBuilderArguments {
|
||||||
});
|
});
|
||||||
|
|
||||||
final Account account;
|
final Account account;
|
||||||
final List<File> files;
|
final List<FileDescriptor> files;
|
||||||
final bool isArchived;
|
final bool isArchived;
|
||||||
final PhotoListItemSorter? sorter;
|
final PhotoListItemSorter? sorter;
|
||||||
final PhotoListItemGrouper? grouper;
|
final PhotoListItemGrouper? grouper;
|
||||||
|
@ -50,17 +49,17 @@ class PhotoListItemBuilderResult {
|
||||||
this.smartAlbums = const [],
|
this.smartAlbums = const [],
|
||||||
});
|
});
|
||||||
|
|
||||||
final List<File> backingFiles;
|
final List<FileDescriptor> backingFiles;
|
||||||
final List<SelectableItem> listItems;
|
final List<SelectableItem> listItems;
|
||||||
final List<Album> smartAlbums;
|
final List<Album> smartAlbums;
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef PhotoListItemSorter = int Function(File, File);
|
typedef PhotoListItemSorter = int Function(FileDescriptor, FileDescriptor);
|
||||||
|
|
||||||
abstract class PhotoListItemGrouper {
|
abstract class PhotoListItemGrouper {
|
||||||
const PhotoListItemGrouper();
|
const PhotoListItemGrouper();
|
||||||
|
|
||||||
SelectableItem? onFile(File file);
|
SelectableItem? onFile(FileDescriptor file);
|
||||||
}
|
}
|
||||||
|
|
||||||
class PhotoListFileDateGrouper implements PhotoListItemGrouper {
|
class PhotoListFileDateGrouper implements PhotoListItemGrouper {
|
||||||
|
@ -69,7 +68,7 @@ class PhotoListFileDateGrouper implements PhotoListItemGrouper {
|
||||||
}) : helper = DateGroupHelper(isMonthOnly: isMonthOnly);
|
}) : helper = DateGroupHelper(isMonthOnly: isMonthOnly);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
onFile(File file) => helper
|
onFile(FileDescriptor file) => helper
|
||||||
.onFile(file)
|
.onFile(file)
|
||||||
?.run((date) => PhotoListDateItem(date: date, isMonthOnly: isMonthOnly));
|
?.run((date) => PhotoListDateItem(date: date, isMonthOnly: isMonthOnly));
|
||||||
|
|
||||||
|
@ -83,10 +82,10 @@ class PhotoListItemSmartAlbumConfig {
|
||||||
final int memoriesDayRange;
|
final int memoriesDayRange;
|
||||||
}
|
}
|
||||||
|
|
||||||
int photoListFileDateTimeSorter(File a, File b) =>
|
int photoListFileDateTimeSorter(FileDescriptor a, FileDescriptor b) =>
|
||||||
compareFileDateTimeDescending(a, b);
|
compareFileDescriptorDateTimeDescending(a, b);
|
||||||
|
|
||||||
int photoListFilenameSorter(File a, File b) =>
|
int photoListFilenameSorter(FileDescriptor a, FileDescriptor b) =>
|
||||||
compareNatural(b.filename, a.filename);
|
compareNatural(b.filename, a.filename);
|
||||||
|
|
||||||
PhotoListItemBuilderResult buildPhotoListItem(
|
PhotoListItemBuilderResult buildPhotoListItem(
|
||||||
|
@ -112,7 +111,7 @@ class _PhotoListItemBuilder {
|
||||||
required this.locale,
|
required this.locale,
|
||||||
});
|
});
|
||||||
|
|
||||||
PhotoListItemBuilderResult call(Account account, List<File> files) {
|
PhotoListItemBuilderResult call(Account account, List<FileDescriptor> files) {
|
||||||
final s = Stopwatch()..start();
|
final s = Stopwatch()..start();
|
||||||
try {
|
try {
|
||||||
return _fromSortedItems(account, _sortItems(files));
|
return _fromSortedItems(account, _sortItems(files));
|
||||||
|
@ -121,17 +120,17 @@ class _PhotoListItemBuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<File> _sortItems(List<File> files) {
|
List<FileDescriptor> _sortItems(List<FileDescriptor> files) {
|
||||||
final filtered = files.where((f) => (f.isArchived ?? false) == isArchived);
|
final filtered = files.where((f) => f.fdIsArchived == isArchived);
|
||||||
if (sorter == null) {
|
if (sorter == null) {
|
||||||
return filtered.toList();
|
return filtered.toList();
|
||||||
} else {
|
} else {
|
||||||
return filtered.stableSorted(sorter);
|
return filtered.sorted(sorter!);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PhotoListItemBuilderResult _fromSortedItems(
|
PhotoListItemBuilderResult _fromSortedItems(
|
||||||
Account account, List<File> files) {
|
Account account, List<FileDescriptor> files) {
|
||||||
final today = DateTime.now();
|
final today = DateTime.now();
|
||||||
final memoryAlbumHelper = smartAlbumConfig != null
|
final memoryAlbumHelper = smartAlbumConfig != null
|
||||||
? MemoryAlbumHelper(
|
? MemoryAlbumHelper(
|
||||||
|
@ -156,7 +155,7 @@ class _PhotoListItemBuilder {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
SelectableItem? _buildListItem(int i, Account account, File file) {
|
SelectableItem? _buildListItem(int i, Account account, FileDescriptor file) {
|
||||||
final previewUrl = api_util.getFilePreviewUrl(account, file,
|
final previewUrl = api_util.getFilePreviewUrl(account, file,
|
||||||
width: k.photoThumbSize, height: k.photoThumbSize);
|
width: k.photoThumbSize, height: k.photoThumbSize);
|
||||||
if (file_util.isSupportedImageFormat(file)) {
|
if (file_util.isSupportedImageFormat(file)) {
|
||||||
|
@ -176,8 +175,7 @@ class _PhotoListItemBuilder {
|
||||||
shouldShowFavoriteBadge: shouldShowFavoriteBadge,
|
shouldShowFavoriteBadge: shouldShowFavoriteBadge,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
_log.shout(
|
_log.shout("[_buildListItem] Unsupported file format: ${file.fdMime}");
|
||||||
"[_buildListItem] Unsupported file format: ${file.contentType}");
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import 'package:nc_photos/app_localizations.dart';
|
||||||
import 'package:nc_photos/bloc/ls_dir.dart';
|
import 'package:nc_photos/bloc/ls_dir.dart';
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
||||||
import 'package:nc_photos/exception_util.dart' as exception_util;
|
import 'package:nc_photos/exception_util.dart' as exception_util;
|
||||||
import 'package:nc_photos/k.dart' as k;
|
import 'package:nc_photos/k.dart' as k;
|
||||||
|
|
|
@ -15,6 +15,7 @@ import 'package:nc_photos/entity/album/item.dart';
|
||||||
import 'package:nc_photos/entity/album/provider.dart';
|
import 'package:nc_photos/entity/album/provider.dart';
|
||||||
import 'package:nc_photos/entity/album/sort_provider.dart';
|
import 'package:nc_photos/entity/album/sort_provider.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
||||||
import 'package:nc_photos/event/event.dart';
|
import 'package:nc_photos/event/event.dart';
|
||||||
import 'package:nc_photos/exception_util.dart' as exception_util;
|
import 'package:nc_photos/exception_util.dart' as exception_util;
|
||||||
|
@ -388,7 +389,8 @@ class _DynamicAlbumBrowserState extends State<DynamicAlbumBrowser>
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onDownloadPressed() {
|
void _onDownloadPressed() {
|
||||||
DownloadHandler().downloadFiles(
|
final c = KiwiContainer().resolve<DiContainer>();
|
||||||
|
DownloadHandler(c).downloadFiles(
|
||||||
widget.account,
|
widget.account,
|
||||||
_sortedItems.whereType<AlbumFileItem>().map((e) => e.file).toList(),
|
_sortedItems.whereType<AlbumFileItem>().map((e) => e.file).toList(),
|
||||||
parentDir: _album!.name,
|
parentDir: _album!.name,
|
||||||
|
@ -411,11 +413,13 @@ class _DynamicAlbumBrowserState extends State<DynamicAlbumBrowser>
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onSelectionSharePressed(BuildContext context) {
|
void _onSelectionSharePressed(BuildContext context) {
|
||||||
|
final c = KiwiContainer().resolve<DiContainer>();
|
||||||
final selected = selectedListItems
|
final selected = selectedListItems
|
||||||
.whereType<_FileListItem>()
|
.whereType<_FileListItem>()
|
||||||
.map((e) => e.file)
|
.map((e) => e.file)
|
||||||
.toList();
|
.toList();
|
||||||
ShareHandler(
|
ShareHandler(
|
||||||
|
c,
|
||||||
context: context,
|
context: context,
|
||||||
clearSelection: () {
|
clearSelection: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
|
@ -477,11 +481,12 @@ class _DynamicAlbumBrowserState extends State<DynamicAlbumBrowser>
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onSelectionDownloadPressed() {
|
void _onSelectionDownloadPressed() {
|
||||||
|
final c = KiwiContainer().resolve<DiContainer>();
|
||||||
final selected = selectedListItems
|
final selected = selectedListItems
|
||||||
.whereType<_FileListItem>()
|
.whereType<_FileListItem>()
|
||||||
.map((e) => e.file)
|
.map((e) => e.file)
|
||||||
.toList();
|
.toList();
|
||||||
DownloadHandler().downloadFiles(widget.account, selected);
|
DownloadHandler(c).downloadFiles(widget.account, selected);
|
||||||
setState(() {
|
setState(() {
|
||||||
clearSelectedItems();
|
clearSelectedItems();
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:kiwi/kiwi.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:nc_photos/app_init.dart' as app_init;
|
import 'package:nc_photos/app_init.dart' as app_init;
|
||||||
import 'package:nc_photos/app_localizations.dart';
|
import 'package:nc_photos/app_localizations.dart';
|
||||||
import 'package:nc_photos/bloc/scan_local_dir.dart';
|
import 'package:nc_photos/bloc/scan_local_dir.dart';
|
||||||
import 'package:nc_photos/compute_queue.dart';
|
import 'package:nc_photos/compute_queue.dart';
|
||||||
|
import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
||||||
import 'package:nc_photos/entity/local_file.dart';
|
import 'package:nc_photos/entity/local_file.dart';
|
||||||
import 'package:nc_photos/exception_util.dart' as exception_util;
|
import 'package:nc_photos/exception_util.dart' as exception_util;
|
||||||
|
@ -244,11 +246,13 @@ class _EnhancedPhotoBrowserState extends State<EnhancedPhotoBrowser>
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onSelectionSharePressed(BuildContext context) async {
|
Future<void> _onSelectionSharePressed(BuildContext context) async {
|
||||||
|
final c = KiwiContainer().resolve<DiContainer>();
|
||||||
final selected = selectedListItems
|
final selected = selectedListItems
|
||||||
.whereType<PhotoListLocalFileItem>()
|
.whereType<PhotoListLocalFileItem>()
|
||||||
.map((e) => e.file)
|
.map((e) => e.file)
|
||||||
.toList();
|
.toList();
|
||||||
await ShareHandler(
|
await ShareHandler(
|
||||||
|
c,
|
||||||
context: context,
|
context: context,
|
||||||
clearSelection: () {
|
clearSelection: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
|
|
|
@ -7,16 +7,23 @@ import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/entity/album.dart';
|
import 'package:nc_photos/entity/album.dart';
|
||||||
import 'package:nc_photos/entity/album/item.dart';
|
import 'package:nc_photos/entity/album/item.dart';
|
||||||
import 'package:nc_photos/entity/album/provider.dart';
|
import 'package:nc_photos/entity/album/provider.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/notified_action.dart';
|
import 'package:nc_photos/notified_action.dart';
|
||||||
import 'package:nc_photos/use_case/add_to_album.dart';
|
import 'package:nc_photos/use_case/add_to_album.dart';
|
||||||
|
import 'package:nc_photos/use_case/inflate_file_descriptor.dart';
|
||||||
import 'package:nc_photos/widget/album_picker.dart';
|
import 'package:nc_photos/widget/album_picker.dart';
|
||||||
|
|
||||||
class AddSelectionToAlbumHandler {
|
class AddSelectionToAlbumHandler {
|
||||||
|
AddSelectionToAlbumHandler(this._c)
|
||||||
|
: assert(require(_c)),
|
||||||
|
assert(InflateFileDescriptor.require(_c));
|
||||||
|
|
||||||
|
static bool require(DiContainer c) => true;
|
||||||
|
|
||||||
Future<void> call({
|
Future<void> call({
|
||||||
required BuildContext context,
|
required BuildContext context,
|
||||||
required Account account,
|
required Account account,
|
||||||
required List<File> selectedFiles,
|
required List<FileDescriptor> selection,
|
||||||
required VoidCallback clearSelection,
|
required VoidCallback clearSelection,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
|
@ -32,6 +39,8 @@ class AddSelectionToAlbumHandler {
|
||||||
await NotifiedAction(
|
await NotifiedAction(
|
||||||
() async {
|
() async {
|
||||||
assert(value.provider is AlbumStaticProvider);
|
assert(value.provider is AlbumStaticProvider);
|
||||||
|
final selectedFiles =
|
||||||
|
await InflateFileDescriptor(_c)(account, selection);
|
||||||
final selected = selectedFiles
|
final selected = selectedFiles
|
||||||
.map((f) => AlbumFileItem(
|
.map((f) => AlbumFileItem(
|
||||||
addedBy: account.userId,
|
addedBy: account.userId,
|
||||||
|
@ -52,6 +61,8 @@ class AddSelectionToAlbumHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final DiContainer _c;
|
||||||
|
|
||||||
static final _log = Logger(
|
static final _log = Logger(
|
||||||
"widget.handler.add_selection_to_album_handler.AddSelectionToAlbumHandler");
|
"widget.handler.add_selection_to_album_handler.AddSelectionToAlbumHandler");
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,19 +4,24 @@ import 'package:nc_photos/app_localizations.dart';
|
||||||
import 'package:nc_photos/debug_util.dart';
|
import 'package:nc_photos/debug_util.dart';
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/notified_action.dart';
|
import 'package:nc_photos/notified_action.dart';
|
||||||
|
import 'package:nc_photos/use_case/inflate_file_descriptor.dart';
|
||||||
import 'package:nc_photos/use_case/update_property.dart';
|
import 'package:nc_photos/use_case/update_property.dart';
|
||||||
|
|
||||||
class ArchiveSelectionHandler {
|
class ArchiveSelectionHandler {
|
||||||
ArchiveSelectionHandler(this._c) : assert(require(_c));
|
ArchiveSelectionHandler(this._c)
|
||||||
|
: assert(require(_c)),
|
||||||
|
assert(InflateFileDescriptor.require(_c));
|
||||||
|
|
||||||
static bool require(DiContainer c) => DiContainer.has(c, DiType.fileRepo);
|
static bool require(DiContainer c) => DiContainer.has(c, DiType.fileRepo);
|
||||||
|
|
||||||
/// Archive [selectedFiles] and return the archived count
|
/// Archive [selectedFiles] and return the archived count
|
||||||
Future<int> call({
|
Future<int> call({
|
||||||
required Account account,
|
required Account account,
|
||||||
required List<File> selectedFiles,
|
required List<FileDescriptor> selection,
|
||||||
}) {
|
}) async {
|
||||||
|
final selectedFiles = await InflateFileDescriptor(_c)(account, selection);
|
||||||
return NotifiedListAction<File>(
|
return NotifiedListAction<File>(
|
||||||
list: selectedFiles,
|
list: selectedFiles,
|
||||||
action: (file) async {
|
action: (file) async {
|
||||||
|
|
|
@ -5,22 +5,30 @@ import 'package:nc_photos/account.dart';
|
||||||
import 'package:nc_photos/app_localizations.dart';
|
import 'package:nc_photos/app_localizations.dart';
|
||||||
import 'package:nc_photos/debug_util.dart';
|
import 'package:nc_photos/debug_util.dart';
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/k.dart' as k;
|
import 'package:nc_photos/k.dart' as k;
|
||||||
import 'package:nc_photos/navigation_manager.dart';
|
import 'package:nc_photos/navigation_manager.dart';
|
||||||
import 'package:nc_photos/snack_bar_manager.dart';
|
import 'package:nc_photos/snack_bar_manager.dart';
|
||||||
|
import 'package:nc_photos/use_case/inflate_file_descriptor.dart';
|
||||||
import 'package:nc_photos/use_case/remove.dart';
|
import 'package:nc_photos/use_case/remove.dart';
|
||||||
import 'package:nc_photos/widget/trashbin_browser.dart';
|
import 'package:nc_photos/widget/trashbin_browser.dart';
|
||||||
|
|
||||||
class RemoveSelectionHandler {
|
class RemoveSelectionHandler {
|
||||||
|
RemoveSelectionHandler(this._c)
|
||||||
|
: assert(require(_c)),
|
||||||
|
assert(InflateFileDescriptor.require(_c));
|
||||||
|
|
||||||
|
static bool require(DiContainer c) => true;
|
||||||
|
|
||||||
/// Remove [selectedFiles] and return the removed count
|
/// Remove [selectedFiles] and return the removed count
|
||||||
Future<int> call({
|
Future<int> call({
|
||||||
required Account account,
|
required Account account,
|
||||||
required List<File> selectedFiles,
|
required List<FileDescriptor> selection,
|
||||||
bool shouldCleanupAlbum = true,
|
bool shouldCleanupAlbum = true,
|
||||||
bool isRemoveOpened = false,
|
bool isRemoveOpened = false,
|
||||||
bool isMoveToTrash = false,
|
bool isMoveToTrash = false,
|
||||||
}) async {
|
}) async {
|
||||||
|
final selectedFiles = await InflateFileDescriptor(_c)(account, selection);
|
||||||
final String processingText, successText;
|
final String processingText, successText;
|
||||||
final String Function(int) failureText;
|
final String Function(int) failureText;
|
||||||
if (isRemoveOpened) {
|
if (isRemoveOpened) {
|
||||||
|
@ -81,6 +89,8 @@ class RemoveSelectionHandler {
|
||||||
return selectedFiles.length - failureCount;
|
return selectedFiles.length - failureCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final DiContainer _c;
|
||||||
|
|
||||||
static final _log =
|
static final _log =
|
||||||
Logger("widget.handler.remove_selection_handler.RemoveSelectionHandler");
|
Logger("widget.handler.remove_selection_handler.RemoveSelectionHandler");
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,8 +19,8 @@ import 'package:nc_photos/compute_queue.dart';
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/download_handler.dart';
|
import 'package:nc_photos/download_handler.dart';
|
||||||
import 'package:nc_photos/entity/album.dart';
|
import 'package:nc_photos/entity/album.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
import 'package:nc_photos/entity/sqlite_table_extension.dart' as sql;
|
||||||
import 'package:nc_photos/event/event.dart';
|
import 'package:nc_photos/event/event.dart';
|
||||||
import 'package:nc_photos/event/native_event.dart';
|
import 'package:nc_photos/event/native_event.dart';
|
||||||
import 'package:nc_photos/exception_util.dart' as exception_util;
|
import 'package:nc_photos/exception_util.dart' as exception_util;
|
||||||
|
@ -359,7 +359,7 @@ class _HomePhotosState extends State<HomePhotos>
|
||||||
?.item
|
?.item
|
||||||
.as<PhotoListFileItem>()
|
.as<PhotoListFileItem>()
|
||||||
?.file
|
?.file
|
||||||
.bestDateTime;
|
.fdDateTime;
|
||||||
if (date != null) {
|
if (date != null) {
|
||||||
final text = DateFormat(DateFormat.YEAR_ABBR_MONTH,
|
final text = DateFormat(DateFormat.YEAR_ABBR_MONTH,
|
||||||
Localizations.localeOf(context).languageCode)
|
Localizations.localeOf(context).languageCode)
|
||||||
|
@ -424,11 +424,13 @@ class _HomePhotosState extends State<HomePhotos>
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onSelectionSharePressed(BuildContext context) {
|
void _onSelectionSharePressed(BuildContext context) {
|
||||||
|
final c = KiwiContainer().resolve<DiContainer>();
|
||||||
final selected = selectedListItems
|
final selected = selectedListItems
|
||||||
.whereType<PhotoListFileItem>()
|
.whereType<PhotoListFileItem>()
|
||||||
.map((e) => e.file)
|
.map((e) => e.file)
|
||||||
.toList();
|
.toList();
|
||||||
ShareHandler(
|
ShareHandler(
|
||||||
|
c,
|
||||||
context: context,
|
context: context,
|
||||||
clearSelection: () {
|
clearSelection: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
|
@ -439,10 +441,11 @@ class _HomePhotosState extends State<HomePhotos>
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onSelectionAddToAlbumPressed(BuildContext context) {
|
Future<void> _onSelectionAddToAlbumPressed(BuildContext context) {
|
||||||
return AddSelectionToAlbumHandler()(
|
final c = KiwiContainer().resolve<DiContainer>();
|
||||||
|
return AddSelectionToAlbumHandler(c)(
|
||||||
context: context,
|
context: context,
|
||||||
account: widget.account,
|
account: widget.account,
|
||||||
selectedFiles: selectedListItems
|
selection: selectedListItems
|
||||||
.whereType<PhotoListFileItem>()
|
.whereType<PhotoListFileItem>()
|
||||||
.map((e) => e.file)
|
.map((e) => e.file)
|
||||||
.toList(),
|
.toList(),
|
||||||
|
@ -457,17 +460,19 @@ class _HomePhotosState extends State<HomePhotos>
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onSelectionDownloadPressed() {
|
void _onSelectionDownloadPressed() {
|
||||||
|
final c = KiwiContainer().resolve<DiContainer>();
|
||||||
final selected = selectedListItems
|
final selected = selectedListItems
|
||||||
.whereType<PhotoListFileItem>()
|
.whereType<PhotoListFileItem>()
|
||||||
.map((e) => e.file)
|
.map((e) => e.file)
|
||||||
.toList();
|
.toList();
|
||||||
DownloadHandler().downloadFiles(widget.account, selected);
|
DownloadHandler(c).downloadFiles(widget.account, selected);
|
||||||
setState(() {
|
setState(() {
|
||||||
clearSelectedItems();
|
clearSelectedItems();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onSelectionArchivePressed(BuildContext context) async {
|
Future<void> _onSelectionArchivePressed(BuildContext context) async {
|
||||||
|
final c = KiwiContainer().resolve<DiContainer>();
|
||||||
final selectedFiles = selectedListItems
|
final selectedFiles = selectedListItems
|
||||||
.whereType<PhotoListFileItem>()
|
.whereType<PhotoListFileItem>()
|
||||||
.map((e) => e.file)
|
.map((e) => e.file)
|
||||||
|
@ -475,13 +480,14 @@ class _HomePhotosState extends State<HomePhotos>
|
||||||
setState(() {
|
setState(() {
|
||||||
clearSelectedItems();
|
clearSelectedItems();
|
||||||
});
|
});
|
||||||
await ArchiveSelectionHandler(KiwiContainer().resolve<DiContainer>())(
|
await ArchiveSelectionHandler(c)(
|
||||||
account: widget.account,
|
account: widget.account,
|
||||||
selectedFiles: selectedFiles,
|
selection: selectedFiles,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onSelectionDeletePressed(BuildContext context) async {
|
Future<void> _onSelectionDeletePressed(BuildContext context) async {
|
||||||
|
final c = KiwiContainer().resolve<DiContainer>();
|
||||||
final selectedFiles = selectedListItems
|
final selectedFiles = selectedListItems
|
||||||
.whereType<PhotoListFileItem>()
|
.whereType<PhotoListFileItem>()
|
||||||
.map((e) => e.file)
|
.map((e) => e.file)
|
||||||
|
@ -489,9 +495,9 @@ class _HomePhotosState extends State<HomePhotos>
|
||||||
setState(() {
|
setState(() {
|
||||||
clearSelectedItems();
|
clearSelectedItems();
|
||||||
});
|
});
|
||||||
await RemoveSelectionHandler()(
|
await RemoveSelectionHandler(c)(
|
||||||
account: widget.account,
|
account: widget.account,
|
||||||
selectedFiles: selectedFiles,
|
selection: selectedFiles,
|
||||||
isMoveToTrash: true,
|
isMoveToTrash: true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -523,23 +529,34 @@ class _HomePhotosState extends State<HomePhotos>
|
||||||
_hasFiredMetadataTask.value = false;
|
_hasFiredMetadataTask.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _tryStartMetadataTask({
|
Future<void> _tryStartMetadataTask({
|
||||||
bool ignoreFired = false,
|
bool ignoreFired = false,
|
||||||
}) {
|
}) async {
|
||||||
if (_bloc.state is ScanAccountDirBlocSuccess &&
|
if (_bloc.state is ScanAccountDirBlocSuccess &&
|
||||||
Pref().isEnableExifOr() &&
|
Pref().isEnableExifOr() &&
|
||||||
(!_hasFiredMetadataTask.value || ignoreFired)) {
|
(!_hasFiredMetadataTask.value || ignoreFired)) {
|
||||||
final missingMetadataCount =
|
try {
|
||||||
_backingFiles.where(file_util.isMissingMetadata).length;
|
final c = KiwiContainer().resolve<DiContainer>();
|
||||||
if (missingMetadataCount > 0) {
|
final missingMetadataCount =
|
||||||
if (_web != null) {
|
await c.sqliteDb.countMissingMetadataByFileIds(
|
||||||
_web!.startMetadataTask(missingMetadataCount);
|
appAccount: widget.account,
|
||||||
} else {
|
fileIds: _backingFiles.map((e) => e.fdId).toList(),
|
||||||
service.startService();
|
);
|
||||||
|
_log.info(
|
||||||
|
"[_tryStartMetadataTask] Missing count: $missingMetadataCount");
|
||||||
|
if (missingMetadataCount > 0) {
|
||||||
|
if (_web != null) {
|
||||||
|
_web!.startMetadataTask(missingMetadataCount);
|
||||||
|
} else {
|
||||||
|
unawaited(service.startService());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
_hasFiredMetadataTask.value = true;
|
_hasFiredMetadataTask.value = true;
|
||||||
|
} catch (e, stackTrace) {
|
||||||
|
_log.shout("[_tryStartMetadataTask] Failed starting metadata task", e,
|
||||||
|
stackTrace);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -558,7 +575,7 @@ class _HomePhotosState extends State<HomePhotos>
|
||||||
|
|
||||||
/// Transform a File list to grid items
|
/// Transform a File list to grid items
|
||||||
void _transformItems(
|
void _transformItems(
|
||||||
List<File> files, {
|
List<FileDescriptor> files, {
|
||||||
bool isSorted = false,
|
bool isSorted = false,
|
||||||
bool isPostSuccess = false,
|
bool isPostSuccess = false,
|
||||||
}) {
|
}) {
|
||||||
|
@ -700,7 +717,7 @@ class _HomePhotosState extends State<HomePhotos>
|
||||||
|
|
||||||
late final _bloc = ScanAccountDirBloc.of(widget.account);
|
late final _bloc = ScanAccountDirBloc.of(widget.account);
|
||||||
|
|
||||||
var _backingFiles = <File>[];
|
var _backingFiles = <FileDescriptor>[];
|
||||||
var _smartAlbums = <Album>[];
|
var _smartAlbums = <Album>[];
|
||||||
|
|
||||||
final _buildItemQueue =
|
final _buildItemQueue =
|
||||||
|
|
|
@ -12,6 +12,7 @@ import 'package:nc_photos/compute_queue.dart';
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/download_handler.dart';
|
import 'package:nc_photos/download_handler.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/entity/search.dart';
|
import 'package:nc_photos/entity/search.dart';
|
||||||
import 'package:nc_photos/exception_util.dart' as exception_util;
|
import 'package:nc_photos/exception_util.dart' as exception_util;
|
||||||
import 'package:nc_photos/k.dart' as k;
|
import 'package:nc_photos/k.dart' as k;
|
||||||
|
@ -438,11 +439,13 @@ class _HomeSearchState extends State<HomeSearch>
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onSelectionSharePressed(BuildContext context) {
|
void _onSelectionSharePressed(BuildContext context) {
|
||||||
|
final c = KiwiContainer().resolve<DiContainer>();
|
||||||
final selected = selectedListItems
|
final selected = selectedListItems
|
||||||
.whereType<PhotoListFileItem>()
|
.whereType<PhotoListFileItem>()
|
||||||
.map((e) => e.file)
|
.map((e) => e.file)
|
||||||
.toList();
|
.toList();
|
||||||
ShareHandler(
|
ShareHandler(
|
||||||
|
c,
|
||||||
context: context,
|
context: context,
|
||||||
clearSelection: () {
|
clearSelection: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
|
@ -453,10 +456,11 @@ class _HomeSearchState extends State<HomeSearch>
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onSelectionAddToAlbumPressed(BuildContext context) {
|
Future<void> _onSelectionAddToAlbumPressed(BuildContext context) {
|
||||||
return AddSelectionToAlbumHandler()(
|
final c = KiwiContainer().resolve<DiContainer>();
|
||||||
|
return AddSelectionToAlbumHandler(c)(
|
||||||
context: context,
|
context: context,
|
||||||
account: widget.account,
|
account: widget.account,
|
||||||
selectedFiles: selectedListItems
|
selection: selectedListItems
|
||||||
.whereType<PhotoListFileItem>()
|
.whereType<PhotoListFileItem>()
|
||||||
.map((e) => e.file)
|
.map((e) => e.file)
|
||||||
.toList(),
|
.toList(),
|
||||||
|
@ -471,17 +475,19 @@ class _HomeSearchState extends State<HomeSearch>
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onSelectionDownloadPressed() {
|
void _onSelectionDownloadPressed() {
|
||||||
|
final c = KiwiContainer().resolve<DiContainer>();
|
||||||
final selected = selectedListItems
|
final selected = selectedListItems
|
||||||
.whereType<PhotoListFileItem>()
|
.whereType<PhotoListFileItem>()
|
||||||
.map((e) => e.file)
|
.map((e) => e.file)
|
||||||
.toList();
|
.toList();
|
||||||
DownloadHandler().downloadFiles(widget.account, selected);
|
DownloadHandler(c).downloadFiles(widget.account, selected);
|
||||||
setState(() {
|
setState(() {
|
||||||
clearSelectedItems();
|
clearSelectedItems();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onSelectionArchivePressed(BuildContext context) async {
|
Future<void> _onSelectionArchivePressed(BuildContext context) async {
|
||||||
|
final c = KiwiContainer().resolve<DiContainer>();
|
||||||
final selectedFiles = selectedListItems
|
final selectedFiles = selectedListItems
|
||||||
.whereType<PhotoListFileItem>()
|
.whereType<PhotoListFileItem>()
|
||||||
.map((e) => e.file)
|
.map((e) => e.file)
|
||||||
|
@ -489,13 +495,14 @@ class _HomeSearchState extends State<HomeSearch>
|
||||||
setState(() {
|
setState(() {
|
||||||
clearSelectedItems();
|
clearSelectedItems();
|
||||||
});
|
});
|
||||||
await ArchiveSelectionHandler(KiwiContainer().resolve<DiContainer>())(
|
await ArchiveSelectionHandler(c)(
|
||||||
account: widget.account,
|
account: widget.account,
|
||||||
selectedFiles: selectedFiles,
|
selection: selectedFiles,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onSelectionDeletePressed(BuildContext context) async {
|
Future<void> _onSelectionDeletePressed(BuildContext context) async {
|
||||||
|
final c = KiwiContainer().resolve<DiContainer>();
|
||||||
final selectedFiles = selectedListItems
|
final selectedFiles = selectedListItems
|
||||||
.whereType<PhotoListFileItem>()
|
.whereType<PhotoListFileItem>()
|
||||||
.map((e) => e.file)
|
.map((e) => e.file)
|
||||||
|
@ -503,9 +510,9 @@ class _HomeSearchState extends State<HomeSearch>
|
||||||
setState(() {
|
setState(() {
|
||||||
clearSelectedItems();
|
clearSelectedItems();
|
||||||
});
|
});
|
||||||
await RemoveSelectionHandler()(
|
await RemoveSelectionHandler(c)(
|
||||||
account: widget.account,
|
account: widget.account,
|
||||||
selectedFiles: selectedFiles,
|
selection: selectedFiles,
|
||||||
isMoveToTrash: true,
|
isMoveToTrash: true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -583,7 +590,7 @@ class _HomeSearchState extends State<HomeSearch>
|
||||||
late final _thumbSize =
|
late final _thumbSize =
|
||||||
photo_list_util.getThumbSize(_thumbZoomLevel).toDouble();
|
photo_list_util.getThumbSize(_thumbZoomLevel).toDouble();
|
||||||
|
|
||||||
var _backingFiles = <File>[];
|
var _backingFiles = <FileDescriptor>[];
|
||||||
|
|
||||||
static final _log = Logger("widget.home_search._HomeSearchState");
|
static final _log = Logger("widget.home_search._HomeSearchState");
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ import 'package:nc_photos/api/api_util.dart' as api_util;
|
||||||
import 'package:nc_photos/app_localizations.dart';
|
import 'package:nc_photos/app_localizations.dart';
|
||||||
import 'package:nc_photos/cache_manager_util.dart';
|
import 'package:nc_photos/cache_manager_util.dart';
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/help_utils.dart' as help_util;
|
import 'package:nc_photos/help_utils.dart' as help_util;
|
||||||
import 'package:nc_photos/k.dart' as k;
|
import 'package:nc_photos/k.dart' as k;
|
||||||
import 'package:nc_photos/object_extension.dart';
|
import 'package:nc_photos/object_extension.dart';
|
||||||
|
@ -27,7 +27,7 @@ class ImageEditorArguments {
|
||||||
const ImageEditorArguments(this.account, this.file);
|
const ImageEditorArguments(this.account, this.file);
|
||||||
|
|
||||||
final Account account;
|
final Account account;
|
||||||
final File file;
|
final FileDescriptor file;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ImageEditor extends StatefulWidget {
|
class ImageEditor extends StatefulWidget {
|
||||||
|
@ -54,7 +54,7 @@ class ImageEditor extends StatefulWidget {
|
||||||
createState() => _ImageEditorState();
|
createState() => _ImageEditorState();
|
||||||
|
|
||||||
final Account account;
|
final Account account;
|
||||||
final File file;
|
final FileDescriptor file;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ImageEditorState extends State<ImageEditor> {
|
class _ImageEditorState extends State<ImageEditor> {
|
||||||
|
@ -275,7 +275,7 @@ class _ImageEditorState extends State<ImageEditor> {
|
||||||
Future<void> _onSavePressed(BuildContext context) async {
|
Future<void> _onSavePressed(BuildContext context) async {
|
||||||
final c = KiwiContainer().resolve<DiContainer>();
|
final c = KiwiContainer().resolve<DiContainer>();
|
||||||
await ImageProcessor.filter(
|
await ImageProcessor.filter(
|
||||||
"${widget.account.url}/${widget.file.path}",
|
"${widget.account.url}/${widget.file.fdPath}",
|
||||||
widget.file.filename,
|
widget.file.filename,
|
||||||
4096,
|
4096,
|
||||||
3072,
|
3072,
|
||||||
|
|
|
@ -11,7 +11,7 @@ import 'package:nc_photos/account.dart';
|
||||||
import 'package:nc_photos/api/api.dart';
|
import 'package:nc_photos/api/api.dart';
|
||||||
import 'package:nc_photos/app_localizations.dart';
|
import 'package:nc_photos/app_localizations.dart';
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
||||||
import 'package:nc_photos/help_utils.dart';
|
import 'package:nc_photos/help_utils.dart';
|
||||||
import 'package:nc_photos/k.dart' as k;
|
import 'package:nc_photos/k.dart' as k;
|
||||||
|
@ -34,7 +34,7 @@ class ImageEnhancerArguments {
|
||||||
const ImageEnhancerArguments(this.account, this.file, this.isSaveToServer);
|
const ImageEnhancerArguments(this.account, this.file, this.isSaveToServer);
|
||||||
|
|
||||||
final Account account;
|
final Account account;
|
||||||
final File file;
|
final FileDescriptor file;
|
||||||
final bool isSaveToServer;
|
final bool isSaveToServer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,8 +45,8 @@ class ImageEnhancer extends StatefulWidget {
|
||||||
builder: (context) => ImageEnhancer.fromArgs(args),
|
builder: (context) => ImageEnhancer.fromArgs(args),
|
||||||
);
|
);
|
||||||
|
|
||||||
static bool isSupportedFormat(File file) =>
|
static bool isSupportedFormat(FileDescriptor file) =>
|
||||||
file_util.isSupportedImageFormat(file) && file.contentType != "image/gif";
|
file_util.isSupportedImageFormat(file) && file.fdMime != "image/gif";
|
||||||
|
|
||||||
const ImageEnhancer({
|
const ImageEnhancer({
|
||||||
super.key,
|
super.key,
|
||||||
|
@ -67,7 +67,7 @@ class ImageEnhancer extends StatefulWidget {
|
||||||
createState() => _ImageEnhancerState();
|
createState() => _ImageEnhancerState();
|
||||||
|
|
||||||
final Account account;
|
final Account account;
|
||||||
final File file;
|
final FileDescriptor file;
|
||||||
final bool isSaveToServer;
|
final bool isSaveToServer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -181,7 +181,7 @@ class _ImageEnhancerState extends State<ImageEnhancer> {
|
||||||
switch (_selectedOption.algorithm) {
|
switch (_selectedOption.algorithm) {
|
||||||
case _Algorithm.zeroDce:
|
case _Algorithm.zeroDce:
|
||||||
await ImageProcessor.zeroDce(
|
await ImageProcessor.zeroDce(
|
||||||
"${widget.account.url}/${widget.file.path}",
|
"${widget.account.url}/${widget.file.fdPath}",
|
||||||
widget.file.filename,
|
widget.file.filename,
|
||||||
_c.pref.getEnhanceMaxWidthOr(),
|
_c.pref.getEnhanceMaxWidthOr(),
|
||||||
_c.pref.getEnhanceMaxHeightOr(),
|
_c.pref.getEnhanceMaxHeightOr(),
|
||||||
|
@ -195,7 +195,7 @@ class _ImageEnhancerState extends State<ImageEnhancer> {
|
||||||
|
|
||||||
case _Algorithm.deepLab3Portrait:
|
case _Algorithm.deepLab3Portrait:
|
||||||
await ImageProcessor.deepLab3Portrait(
|
await ImageProcessor.deepLab3Portrait(
|
||||||
"${widget.account.url}/${widget.file.path}",
|
"${widget.account.url}/${widget.file.fdPath}",
|
||||||
widget.file.filename,
|
widget.file.filename,
|
||||||
_c.pref.getEnhanceMaxWidthOr(),
|
_c.pref.getEnhanceMaxWidthOr(),
|
||||||
_c.pref.getEnhanceMaxHeightOr(),
|
_c.pref.getEnhanceMaxHeightOr(),
|
||||||
|
@ -209,7 +209,7 @@ class _ImageEnhancerState extends State<ImageEnhancer> {
|
||||||
|
|
||||||
case _Algorithm.esrgan:
|
case _Algorithm.esrgan:
|
||||||
await ImageProcessor.esrgan(
|
await ImageProcessor.esrgan(
|
||||||
"${widget.account.url}/${widget.file.path}",
|
"${widget.account.url}/${widget.file.fdPath}",
|
||||||
widget.file.filename,
|
widget.file.filename,
|
||||||
_c.pref.getEnhanceMaxWidthOr(),
|
_c.pref.getEnhanceMaxWidthOr(),
|
||||||
_c.pref.getEnhanceMaxHeightOr(),
|
_c.pref.getEnhanceMaxHeightOr(),
|
||||||
|
@ -222,7 +222,7 @@ class _ImageEnhancerState extends State<ImageEnhancer> {
|
||||||
|
|
||||||
case _Algorithm.arbitraryStyleTransfer:
|
case _Algorithm.arbitraryStyleTransfer:
|
||||||
await ImageProcessor.arbitraryStyleTransfer(
|
await ImageProcessor.arbitraryStyleTransfer(
|
||||||
"${widget.account.url}/${widget.file.path}",
|
"${widget.account.url}/${widget.file.fdPath}",
|
||||||
widget.file.filename,
|
widget.file.filename,
|
||||||
math.min(
|
math.min(
|
||||||
_c.pref.getEnhanceMaxWidthOr(), _isAtLeast5GbRam() ? 1600 : 1280),
|
_c.pref.getEnhanceMaxWidthOr(), _isAtLeast5GbRam() ? 1600 : 1280),
|
||||||
|
@ -239,7 +239,7 @@ class _ImageEnhancerState extends State<ImageEnhancer> {
|
||||||
|
|
||||||
case _Algorithm.deepLab3ColorPop:
|
case _Algorithm.deepLab3ColorPop:
|
||||||
await ImageProcessor.deepLab3ColorPop(
|
await ImageProcessor.deepLab3ColorPop(
|
||||||
"${widget.account.url}/${widget.file.path}",
|
"${widget.account.url}/${widget.file.fdPath}",
|
||||||
widget.file.filename,
|
widget.file.filename,
|
||||||
_c.pref.getEnhanceMaxWidthOr(),
|
_c.pref.getEnhanceMaxWidthOr(),
|
||||||
_c.pref.getEnhanceMaxHeightOr(),
|
_c.pref.getEnhanceMaxHeightOr(),
|
||||||
|
@ -253,7 +253,7 @@ class _ImageEnhancerState extends State<ImageEnhancer> {
|
||||||
|
|
||||||
case _Algorithm.neurOp:
|
case _Algorithm.neurOp:
|
||||||
await ImageProcessor.neurOp(
|
await ImageProcessor.neurOp(
|
||||||
"${widget.account.url}/${widget.file.path}",
|
"${widget.account.url}/${widget.file.fdPath}",
|
||||||
widget.file.filename,
|
widget.file.filename,
|
||||||
_c.pref.getEnhanceMaxWidthOr(),
|
_c.pref.getEnhanceMaxWidthOr(),
|
||||||
_c.pref.getEnhanceMaxHeightOr(),
|
_c.pref.getEnhanceMaxHeightOr(),
|
||||||
|
|
|
@ -6,9 +6,8 @@ import 'package:nc_photos/account.dart';
|
||||||
import 'package:nc_photos/api/api.dart';
|
import 'package:nc_photos/api/api.dart';
|
||||||
import 'package:nc_photos/api/api_util.dart' as api_util;
|
import 'package:nc_photos/api/api_util.dart' as api_util;
|
||||||
import 'package:nc_photos/cache_manager_util.dart';
|
import 'package:nc_photos/cache_manager_util.dart';
|
||||||
import 'package:nc_photos/entity/file.dart' as app;
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/entity/local_file.dart';
|
import 'package:nc_photos/entity/local_file.dart';
|
||||||
import 'package:nc_photos/flutter_util.dart' as flutter_util;
|
|
||||||
import 'package:nc_photos/k.dart' as k;
|
import 'package:nc_photos/k.dart' as k;
|
||||||
import 'package:nc_photos/mobile/android/content_uri_image_provider.dart';
|
import 'package:nc_photos/mobile/android/content_uri_image_provider.dart';
|
||||||
import 'package:nc_photos/widget/cached_network_image_mod.dart' as mod;
|
import 'package:nc_photos/widget/cached_network_image_mod.dart' as mod;
|
||||||
|
@ -91,7 +90,7 @@ class RemoteImageViewer extends StatefulWidget {
|
||||||
@override
|
@override
|
||||||
createState() => _RemoteImageViewerState();
|
createState() => _RemoteImageViewerState();
|
||||||
|
|
||||||
static void preloadImage(Account account, app.File file) {
|
static void preloadImage(Account account, FileDescriptor file) {
|
||||||
LargeImageCacheManager.inst.getFileStream(
|
LargeImageCacheManager.inst.getFileStream(
|
||||||
_getImageUrl(account, file),
|
_getImageUrl(account, file),
|
||||||
headers: {
|
headers: {
|
||||||
|
@ -101,7 +100,7 @@ class RemoteImageViewer extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
final Account account;
|
final Account account;
|
||||||
final app.File file;
|
final FileDescriptor file;
|
||||||
final bool canZoom;
|
final bool canZoom;
|
||||||
final VoidCallback? onLoaded;
|
final VoidCallback? onLoaded;
|
||||||
final ValueChanged<double>? onHeightChanged;
|
final ValueChanged<double>? onHeightChanged;
|
||||||
|
@ -116,26 +115,23 @@ class _RemoteImageViewerState extends State<RemoteImageViewer> {
|
||||||
onHeightChanged: widget.onHeightChanged,
|
onHeightChanged: widget.onHeightChanged,
|
||||||
onZoomStarted: widget.onZoomStarted,
|
onZoomStarted: widget.onZoomStarted,
|
||||||
onZoomEnded: widget.onZoomEnded,
|
onZoomEnded: widget.onZoomEnded,
|
||||||
child: Hero(
|
child: mod.CachedNetworkImage(
|
||||||
tag: flutter_util.getImageHeroTag(widget.file),
|
cacheManager: LargeImageCacheManager.inst,
|
||||||
child: mod.CachedNetworkImage(
|
imageUrl: _getImageUrl(widget.account, widget.file),
|
||||||
cacheManager: LargeImageCacheManager.inst,
|
httpHeaders: {
|
||||||
imageUrl: _getImageUrl(widget.account, widget.file),
|
"Authorization": Api.getAuthorizationHeaderValue(widget.account),
|
||||||
httpHeaders: {
|
},
|
||||||
"Authorization": Api.getAuthorizationHeaderValue(widget.account),
|
fit: BoxFit.contain,
|
||||||
},
|
fadeInDuration: const Duration(),
|
||||||
fit: BoxFit.contain,
|
filterQuality: FilterQuality.high,
|
||||||
fadeInDuration: const Duration(),
|
imageRenderMethodForWeb: ImageRenderMethodForWeb.HttpGet,
|
||||||
filterQuality: FilterQuality.high,
|
imageBuilder: (context, child, imageProvider) {
|
||||||
imageRenderMethodForWeb: ImageRenderMethodForWeb.HttpGet,
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
imageBuilder: (context, child, imageProvider) {
|
_onItemLoaded();
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
});
|
||||||
_onItemLoaded();
|
const SizeChangedLayoutNotification().dispatch(context);
|
||||||
});
|
return child;
|
||||||
const SizeChangedLayoutNotification().dispatch(context);
|
},
|
||||||
return child;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -333,8 +329,8 @@ class _ImageViewerState extends State<_ImageViewer>
|
||||||
static final _log = Logger("widget.image_viewer._ImageViewerState");
|
static final _log = Logger("widget.image_viewer._ImageViewerState");
|
||||||
}
|
}
|
||||||
|
|
||||||
String _getImageUrl(Account account, app.File file) {
|
String _getImageUrl(Account account, FileDescriptor file) {
|
||||||
if (file.contentType == "image/gif") {
|
if (file.fdMime == "image/gif") {
|
||||||
return api_util.getFileUrl(account, file);
|
return api_util.getFileUrl(account, file);
|
||||||
} else {
|
} else {
|
||||||
return api_util.getFilePreviewUrl(
|
return api_util.getFilePreviewUrl(
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:kiwi/kiwi.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:nc_photos/app_localizations.dart';
|
import 'package:nc_photos/app_localizations.dart';
|
||||||
|
import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
||||||
import 'package:nc_photos/entity/local_file.dart';
|
import 'package:nc_photos/entity/local_file.dart';
|
||||||
import 'package:nc_photos/share_handler.dart';
|
import 'package:nc_photos/share_handler.dart';
|
||||||
|
@ -135,9 +137,10 @@ class _LocalFileViewerState extends State<LocalFileViewer> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onSharePressed(BuildContext context) async {
|
Future<void> _onSharePressed(BuildContext context) async {
|
||||||
|
final c = KiwiContainer().resolve<DiContainer>();
|
||||||
final file = widget.streamFiles[_viewerController.currentPage];
|
final file = widget.streamFiles[_viewerController.currentPage];
|
||||||
_log.info("[_onSharePressed] Sharing file: ${file.logTag}");
|
_log.info("[_onSharePressed] Sharing file: ${file.logTag}");
|
||||||
await ShareHandler(context: context).shareLocalFiles([file]);
|
await ShareHandler(c, context: context).shareLocalFiles([file]);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onMenuSelected(BuildContext context, _AppBarMenuOption option) {
|
void _onMenuSelected(BuildContext context, _AppBarMenuOption option) {
|
||||||
|
|
|
@ -16,6 +16,7 @@ import 'package:nc_photos/compute_queue.dart';
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/download_handler.dart';
|
import 'package:nc_photos/download_handler.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/entity/person.dart';
|
import 'package:nc_photos/entity/person.dart';
|
||||||
import 'package:nc_photos/event/event.dart';
|
import 'package:nc_photos/event/event.dart';
|
||||||
import 'package:nc_photos/exception_util.dart' as exception_util;
|
import 'package:nc_photos/exception_util.dart' as exception_util;
|
||||||
|
@ -351,11 +352,13 @@ class _PersonBrowserState extends State<PersonBrowser>
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onSelectionSharePressed(BuildContext context) {
|
void _onSelectionSharePressed(BuildContext context) {
|
||||||
|
final c = KiwiContainer().resolve<DiContainer>();
|
||||||
final selected = selectedListItems
|
final selected = selectedListItems
|
||||||
.whereType<PhotoListFileItem>()
|
.whereType<PhotoListFileItem>()
|
||||||
.map((e) => e.file)
|
.map((e) => e.file)
|
||||||
.toList();
|
.toList();
|
||||||
ShareHandler(
|
ShareHandler(
|
||||||
|
c,
|
||||||
context: context,
|
context: context,
|
||||||
clearSelection: () {
|
clearSelection: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
|
@ -366,10 +369,11 @@ class _PersonBrowserState extends State<PersonBrowser>
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onSelectionAddToAlbumPressed(BuildContext context) {
|
Future<void> _onSelectionAddToAlbumPressed(BuildContext context) {
|
||||||
return AddSelectionToAlbumHandler()(
|
final c = KiwiContainer().resolve<DiContainer>();
|
||||||
|
return AddSelectionToAlbumHandler(c)(
|
||||||
context: context,
|
context: context,
|
||||||
account: widget.account,
|
account: widget.account,
|
||||||
selectedFiles: selectedListItems
|
selection: selectedListItems
|
||||||
.whereType<PhotoListFileItem>()
|
.whereType<PhotoListFileItem>()
|
||||||
.map((e) => e.file)
|
.map((e) => e.file)
|
||||||
.toList(),
|
.toList(),
|
||||||
|
@ -384,17 +388,19 @@ class _PersonBrowserState extends State<PersonBrowser>
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onSelectionDownloadPressed() {
|
void _onSelectionDownloadPressed() {
|
||||||
|
final c = KiwiContainer().resolve<DiContainer>();
|
||||||
final selected = selectedListItems
|
final selected = selectedListItems
|
||||||
.whereType<PhotoListFileItem>()
|
.whereType<PhotoListFileItem>()
|
||||||
.map((e) => e.file)
|
.map((e) => e.file)
|
||||||
.toList();
|
.toList();
|
||||||
DownloadHandler().downloadFiles(widget.account, selected);
|
DownloadHandler(c).downloadFiles(widget.account, selected);
|
||||||
setState(() {
|
setState(() {
|
||||||
clearSelectedItems();
|
clearSelectedItems();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onSelectionArchivePressed(BuildContext context) async {
|
Future<void> _onSelectionArchivePressed(BuildContext context) async {
|
||||||
|
final c = KiwiContainer().resolve<DiContainer>();
|
||||||
final selectedFiles = selectedListItems
|
final selectedFiles = selectedListItems
|
||||||
.whereType<PhotoListFileItem>()
|
.whereType<PhotoListFileItem>()
|
||||||
.map((e) => e.file)
|
.map((e) => e.file)
|
||||||
|
@ -402,13 +408,14 @@ class _PersonBrowserState extends State<PersonBrowser>
|
||||||
setState(() {
|
setState(() {
|
||||||
clearSelectedItems();
|
clearSelectedItems();
|
||||||
});
|
});
|
||||||
await ArchiveSelectionHandler(KiwiContainer().resolve<DiContainer>())(
|
await ArchiveSelectionHandler(c)(
|
||||||
account: widget.account,
|
account: widget.account,
|
||||||
selectedFiles: selectedFiles,
|
selection: selectedFiles,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onSelectionDeletePressed(BuildContext context) async {
|
Future<void> _onSelectionDeletePressed(BuildContext context) async {
|
||||||
|
final c = KiwiContainer().resolve<DiContainer>();
|
||||||
final selectedFiles = selectedListItems
|
final selectedFiles = selectedListItems
|
||||||
.whereType<PhotoListFileItem>()
|
.whereType<PhotoListFileItem>()
|
||||||
.map((e) => e.file)
|
.map((e) => e.file)
|
||||||
|
@ -416,16 +423,15 @@ class _PersonBrowserState extends State<PersonBrowser>
|
||||||
setState(() {
|
setState(() {
|
||||||
clearSelectedItems();
|
clearSelectedItems();
|
||||||
});
|
});
|
||||||
await RemoveSelectionHandler()(
|
await RemoveSelectionHandler(c)(
|
||||||
account: widget.account,
|
account: widget.account,
|
||||||
selectedFiles: selectedFiles,
|
selection: selectedFiles,
|
||||||
isMoveToTrash: true,
|
isMoveToTrash: true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onFilePropertyUpdated(FilePropertyUpdatedEvent ev) {
|
void _onFilePropertyUpdated(FilePropertyUpdatedEvent ev) {
|
||||||
if (_backingFiles.containsIf(ev.file, (a, b) => a.fileId == b.fileId) !=
|
if (_backingFiles.containsIf(ev.file, (a, b) => a.fdId == b.fdId) != true) {
|
||||||
true) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_refreshThrottler.trigger(
|
_refreshThrottler.trigger(
|
||||||
|
@ -474,7 +480,7 @@ class _PersonBrowserState extends State<PersonBrowser>
|
||||||
late final DiContainer _c;
|
late final DiContainer _c;
|
||||||
|
|
||||||
late final ListFaceFileBloc _bloc = ListFaceFileBloc(_c);
|
late final ListFaceFileBloc _bloc = ListFaceFileBloc(_c);
|
||||||
var _backingFiles = <File>[];
|
var _backingFiles = <FileDescriptor>[];
|
||||||
|
|
||||||
final _buildItemQueue =
|
final _buildItemQueue =
|
||||||
ComputeQueue<PhotoListItemBuilderArguments, PhotoListItemBuilderResult>();
|
ComputeQueue<PhotoListItemBuilderArguments, PhotoListItemBuilderResult>();
|
||||||
|
|
|
@ -7,8 +7,7 @@ import 'package:nc_photos/account.dart';
|
||||||
import 'package:nc_photos/api/api.dart';
|
import 'package:nc_photos/api/api.dart';
|
||||||
import 'package:nc_photos/app_localizations.dart';
|
import 'package:nc_photos/app_localizations.dart';
|
||||||
import 'package:nc_photos/cache_manager_util.dart';
|
import 'package:nc_photos/cache_manager_util.dart';
|
||||||
import 'package:nc_photos/flutter_util.dart'as flutter_util;
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
|
||||||
import 'package:nc_photos/entity/local_file.dart';
|
import 'package:nc_photos/entity/local_file.dart';
|
||||||
import 'package:nc_photos/k.dart' as k;
|
import 'package:nc_photos/k.dart' as k;
|
||||||
import 'package:nc_photos/mobile/android/content_uri_image_provider.dart';
|
import 'package:nc_photos/mobile/android/content_uri_image_provider.dart';
|
||||||
|
@ -33,24 +32,24 @@ abstract class PhotoListFileItem extends SelectableItem {
|
||||||
other is PhotoListFileItem && file.compareServerIdentity(other.file);
|
other is PhotoListFileItem && file.compareServerIdentity(other.file);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
get hashCode => file.path.hashCode;
|
get hashCode => file.fdPath.hashCode;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
toString() => "$runtimeType {"
|
toString() => "$runtimeType {"
|
||||||
"fileIndex: $fileIndex, "
|
"fileIndex: $fileIndex, "
|
||||||
"file: ${file.path}, "
|
"file: ${file.fdPath}, "
|
||||||
"shouldShowFavoriteBadge: $shouldShowFavoriteBadge, "
|
"shouldShowFavoriteBadge: $shouldShowFavoriteBadge, "
|
||||||
"}";
|
"}";
|
||||||
|
|
||||||
final int fileIndex;
|
final int fileIndex;
|
||||||
final File file;
|
final FileDescriptor file;
|
||||||
final bool shouldShowFavoriteBadge;
|
final bool shouldShowFavoriteBadge;
|
||||||
}
|
}
|
||||||
|
|
||||||
class PhotoListImageItem extends PhotoListFileItem {
|
class PhotoListImageItem extends PhotoListFileItem {
|
||||||
const PhotoListImageItem({
|
const PhotoListImageItem({
|
||||||
required int fileIndex,
|
required int fileIndex,
|
||||||
required File file,
|
required FileDescriptor file,
|
||||||
required this.account,
|
required this.account,
|
||||||
required this.previewUrl,
|
required this.previewUrl,
|
||||||
required bool shouldShowFavoriteBadge,
|
required bool shouldShowFavoriteBadge,
|
||||||
|
@ -64,9 +63,8 @@ class PhotoListImageItem extends PhotoListFileItem {
|
||||||
buildWidget(BuildContext context) => PhotoListImage(
|
buildWidget(BuildContext context) => PhotoListImage(
|
||||||
account: account,
|
account: account,
|
||||||
previewUrl: previewUrl,
|
previewUrl: previewUrl,
|
||||||
isGif: file.contentType == "image/gif",
|
isGif: file.fdMime == "image/gif",
|
||||||
isFavorite: shouldShowFavoriteBadge && file.isFavorite == true,
|
isFavorite: shouldShowFavoriteBadge && file.fdIsFavorite == true,
|
||||||
heroKey: flutter_util.getImageHeroTag(file),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
final Account account;
|
final Account account;
|
||||||
|
@ -76,7 +74,7 @@ class PhotoListImageItem extends PhotoListFileItem {
|
||||||
class PhotoListVideoItem extends PhotoListFileItem {
|
class PhotoListVideoItem extends PhotoListFileItem {
|
||||||
const PhotoListVideoItem({
|
const PhotoListVideoItem({
|
||||||
required int fileIndex,
|
required int fileIndex,
|
||||||
required File file,
|
required FileDescriptor file,
|
||||||
required this.account,
|
required this.account,
|
||||||
required this.previewUrl,
|
required this.previewUrl,
|
||||||
required bool shouldShowFavoriteBadge,
|
required bool shouldShowFavoriteBadge,
|
||||||
|
@ -90,7 +88,7 @@ class PhotoListVideoItem extends PhotoListFileItem {
|
||||||
buildWidget(BuildContext context) => PhotoListVideo(
|
buildWidget(BuildContext context) => PhotoListVideo(
|
||||||
account: account,
|
account: account,
|
||||||
previewUrl: previewUrl,
|
previewUrl: previewUrl,
|
||||||
isFavorite: shouldShowFavoriteBadge && file.isFavorite == true,
|
isFavorite: shouldShowFavoriteBadge && file.fdIsFavorite == true,
|
||||||
);
|
);
|
||||||
|
|
||||||
final Account account;
|
final Account account;
|
||||||
|
|
|
@ -7,15 +7,15 @@ import 'package:nc_photos/entity/album.dart';
|
||||||
import 'package:nc_photos/entity/album/cover_provider.dart';
|
import 'package:nc_photos/entity/album/cover_provider.dart';
|
||||||
import 'package:nc_photos/entity/album/provider.dart';
|
import 'package:nc_photos/entity/album/provider.dart';
|
||||||
import 'package:nc_photos/entity/album/sort_provider.dart';
|
import 'package:nc_photos/entity/album/sort_provider.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
|
|
||||||
class DateGroupHelper {
|
class DateGroupHelper {
|
||||||
DateGroupHelper({
|
DateGroupHelper({
|
||||||
required this.isMonthOnly,
|
required this.isMonthOnly,
|
||||||
});
|
});
|
||||||
|
|
||||||
DateTime? onFile(File file) {
|
DateTime? onFile(FileDescriptor file) {
|
||||||
final newDate = file.bestDateTime.toLocal();
|
final newDate = file.fdDateTime.toLocal();
|
||||||
if (newDate.year != _currentDate?.year ||
|
if (newDate.year != _currentDate?.year ||
|
||||||
newDate.month != _currentDate?.month ||
|
newDate.month != _currentDate?.month ||
|
||||||
(!isMonthOnly && newDate.day != _currentDate?.day)) {
|
(!isMonthOnly && newDate.day != _currentDate?.day)) {
|
||||||
|
@ -40,8 +40,8 @@ class MemoryAlbumHelper {
|
||||||
}) : today = (today?.toLocal() ?? DateTime.now()).toMidnight(),
|
}) : today = (today?.toLocal() ?? DateTime.now()).toMidnight(),
|
||||||
dayRange = math.max(dayRange, 0);
|
dayRange = math.max(dayRange, 0);
|
||||||
|
|
||||||
void addFile(File f) {
|
void addFile(FileDescriptor f) {
|
||||||
final date = f.bestDateTime.toLocal().toMidnight();
|
final date = f.fdDateTime.toLocal().toMidnight();
|
||||||
final diff = today.difference(date).inDays;
|
final diff = today.difference(date).inDays;
|
||||||
if (diff < 300) {
|
if (diff < 300) {
|
||||||
return;
|
return;
|
||||||
|
@ -49,8 +49,7 @@ class MemoryAlbumHelper {
|
||||||
for (final dy in [0, -1, 1]) {
|
for (final dy in [0, -1, 1]) {
|
||||||
if (today.copyWith(year: date.year + dy).difference(date).abs().inDays <=
|
if (today.copyWith(year: date.year + dy).difference(date).abs().inDays <=
|
||||||
dayRange) {
|
dayRange) {
|
||||||
_log.fine(
|
_log.fine("[addFile] Add file (${f.fdDateTime}) to ${date.year + dy}");
|
||||||
"[addFile] Add file (${f.bestDateTime}) to ${date.year + dy}");
|
|
||||||
_addFileToYear(f, date.year + dy);
|
_addFileToYear(f, date.year + dy);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -69,13 +68,13 @@ class MemoryAlbumHelper {
|
||||||
provider: AlbumMemoryProvider(
|
provider: AlbumMemoryProvider(
|
||||||
year: e.key, month: today.month, day: today.day),
|
year: e.key, month: today.month, day: today.day),
|
||||||
coverProvider:
|
coverProvider:
|
||||||
AlbumManualCoverProvider(coverFile: e.value.coverFile),
|
AlbumMemoryCoverProvider(coverFile: e.value.coverFile),
|
||||||
sortProvider: const AlbumTimeSortProvider(isAscending: false),
|
sortProvider: const AlbumTimeSortProvider(isAscending: false),
|
||||||
))
|
))
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _addFileToYear(File f, int year) {
|
void _addFileToYear(FileDescriptor f, int year) {
|
||||||
final item = _data[year];
|
final item = _data[year];
|
||||||
final date = today.copyWith(year: year);
|
final date = today.copyWith(year: year);
|
||||||
if (item == null) {
|
if (item == null) {
|
||||||
|
@ -117,10 +116,10 @@ class _MemoryAlbumHelperItem {
|
||||||
_MemoryAlbumHelperItem(this.date, this.coverFile)
|
_MemoryAlbumHelperItem(this.date, this.coverFile)
|
||||||
: coverDiff = getCoverDiff(date, coverFile);
|
: coverDiff = getCoverDiff(date, coverFile);
|
||||||
|
|
||||||
static Duration getCoverDiff(DateTime date, File f) =>
|
static Duration getCoverDiff(DateTime date, FileDescriptor f) =>
|
||||||
f.bestDateTime.difference(date.copyWith(hour: 12)).abs();
|
f.fdDateTime.difference(date.copyWith(hour: 12)).abs();
|
||||||
|
|
||||||
final DateTime date;
|
final DateTime date;
|
||||||
File coverFile;
|
FileDescriptor coverFile;
|
||||||
Duration coverDiff;
|
Duration coverDiff;
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import 'package:nc_photos/compute_queue.dart';
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/download_handler.dart';
|
import 'package:nc_photos/download_handler.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/exception_util.dart' as exception_util;
|
import 'package:nc_photos/exception_util.dart' as exception_util;
|
||||||
import 'package:nc_photos/k.dart' as k;
|
import 'package:nc_photos/k.dart' as k;
|
||||||
import 'package:nc_photos/language_util.dart' as language_util;
|
import 'package:nc_photos/language_util.dart' as language_util;
|
||||||
|
@ -295,11 +296,13 @@ class _PlaceBrowserState extends State<PlaceBrowser>
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onSelectionSharePressed(BuildContext context) {
|
void _onSelectionSharePressed(BuildContext context) {
|
||||||
|
final c = KiwiContainer().resolve<DiContainer>();
|
||||||
final selected = selectedListItems
|
final selected = selectedListItems
|
||||||
.whereType<PhotoListFileItem>()
|
.whereType<PhotoListFileItem>()
|
||||||
.map((e) => e.file)
|
.map((e) => e.file)
|
||||||
.toList();
|
.toList();
|
||||||
ShareHandler(
|
ShareHandler(
|
||||||
|
c,
|
||||||
context: context,
|
context: context,
|
||||||
clearSelection: () {
|
clearSelection: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
|
@ -310,10 +313,11 @@ class _PlaceBrowserState extends State<PlaceBrowser>
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onSelectionAddToAlbumPressed(BuildContext context) {
|
Future<void> _onSelectionAddToAlbumPressed(BuildContext context) {
|
||||||
return AddSelectionToAlbumHandler()(
|
final c = KiwiContainer().resolve<DiContainer>();
|
||||||
|
return AddSelectionToAlbumHandler(c)(
|
||||||
context: context,
|
context: context,
|
||||||
account: widget.account,
|
account: widget.account,
|
||||||
selectedFiles: selectedListItems
|
selection: selectedListItems
|
||||||
.whereType<PhotoListFileItem>()
|
.whereType<PhotoListFileItem>()
|
||||||
.map((e) => e.file)
|
.map((e) => e.file)
|
||||||
.toList(),
|
.toList(),
|
||||||
|
@ -328,17 +332,19 @@ class _PlaceBrowserState extends State<PlaceBrowser>
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onSelectionDownloadPressed() {
|
void _onSelectionDownloadPressed() {
|
||||||
|
final c = KiwiContainer().resolve<DiContainer>();
|
||||||
final selected = selectedListItems
|
final selected = selectedListItems
|
||||||
.whereType<PhotoListFileItem>()
|
.whereType<PhotoListFileItem>()
|
||||||
.map((e) => e.file)
|
.map((e) => e.file)
|
||||||
.toList();
|
.toList();
|
||||||
DownloadHandler().downloadFiles(widget.account, selected);
|
DownloadHandler(c).downloadFiles(widget.account, selected);
|
||||||
setState(() {
|
setState(() {
|
||||||
clearSelectedItems();
|
clearSelectedItems();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onSelectionArchivePressed(BuildContext context) async {
|
Future<void> _onSelectionArchivePressed(BuildContext context) async {
|
||||||
|
final c = KiwiContainer().resolve<DiContainer>();
|
||||||
final selectedFiles = selectedListItems
|
final selectedFiles = selectedListItems
|
||||||
.whereType<PhotoListFileItem>()
|
.whereType<PhotoListFileItem>()
|
||||||
.map((e) => e.file)
|
.map((e) => e.file)
|
||||||
|
@ -346,13 +352,14 @@ class _PlaceBrowserState extends State<PlaceBrowser>
|
||||||
setState(() {
|
setState(() {
|
||||||
clearSelectedItems();
|
clearSelectedItems();
|
||||||
});
|
});
|
||||||
await ArchiveSelectionHandler(KiwiContainer().resolve<DiContainer>())(
|
await ArchiveSelectionHandler(c)(
|
||||||
account: widget.account,
|
account: widget.account,
|
||||||
selectedFiles: selectedFiles,
|
selection: selectedFiles,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onSelectionDeletePressed(BuildContext context) async {
|
Future<void> _onSelectionDeletePressed(BuildContext context) async {
|
||||||
|
final c = KiwiContainer().resolve<DiContainer>();
|
||||||
final selectedFiles = selectedListItems
|
final selectedFiles = selectedListItems
|
||||||
.whereType<PhotoListFileItem>()
|
.whereType<PhotoListFileItem>()
|
||||||
.map((e) => e.file)
|
.map((e) => e.file)
|
||||||
|
@ -360,9 +367,9 @@ class _PlaceBrowserState extends State<PlaceBrowser>
|
||||||
setState(() {
|
setState(() {
|
||||||
clearSelectedItems();
|
clearSelectedItems();
|
||||||
});
|
});
|
||||||
await RemoveSelectionHandler()(
|
await RemoveSelectionHandler(c)(
|
||||||
account: widget.account,
|
account: widget.account,
|
||||||
selectedFiles: selectedFiles,
|
selection: selectedFiles,
|
||||||
isMoveToTrash: true,
|
isMoveToTrash: true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -408,7 +415,7 @@ class _PlaceBrowserState extends State<PlaceBrowser>
|
||||||
late final DiContainer _c;
|
late final DiContainer _c;
|
||||||
|
|
||||||
late final ListLocationFileBloc _bloc = ListLocationFileBloc(_c);
|
late final ListLocationFileBloc _bloc = ListLocationFileBloc(_c);
|
||||||
var _backingFiles = <File>[];
|
var _backingFiles = <FileDescriptor>[];
|
||||||
|
|
||||||
final _buildItemQueue =
|
final _buildItemQueue =
|
||||||
ComputeQueue<PhotoListItemBuilderArguments, PhotoListItemBuilderResult>();
|
ComputeQueue<PhotoListItemBuilderArguments, PhotoListItemBuilderResult>();
|
||||||
|
|
|
@ -6,6 +6,7 @@ import 'package:nc_photos/account.dart';
|
||||||
import 'package:nc_photos/app_localizations.dart';
|
import 'package:nc_photos/app_localizations.dart';
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
||||||
import 'package:nc_photos/k.dart' as k;
|
import 'package:nc_photos/k.dart' as k;
|
||||||
import 'package:nc_photos/snack_bar_manager.dart';
|
import 'package:nc_photos/snack_bar_manager.dart';
|
||||||
|
|
|
@ -3,6 +3,7 @@ import 'package:logging/logging.dart';
|
||||||
import 'package:nc_photos/account.dart';
|
import 'package:nc_photos/account.dart';
|
||||||
import 'package:nc_photos/app_localizations.dart';
|
import 'package:nc_photos/app_localizations.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
||||||
import 'package:nc_photos/iterable_extension.dart';
|
import 'package:nc_photos/iterable_extension.dart';
|
||||||
import 'package:nc_photos/k.dart' as k;
|
import 'package:nc_photos/k.dart' as k;
|
||||||
|
|
|
@ -12,6 +12,7 @@ import 'package:nc_photos/app_localizations.dart';
|
||||||
import 'package:nc_photos/cache_manager_util.dart';
|
import 'package:nc_photos/cache_manager_util.dart';
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/entity/share.dart';
|
import 'package:nc_photos/entity/share.dart';
|
||||||
import 'package:nc_photos/entity/share/data_source.dart';
|
import 'package:nc_photos/entity/share/data_source.dart';
|
||||||
import 'package:nc_photos/exception_util.dart' as exception_util;
|
import 'package:nc_photos/exception_util.dart' as exception_util;
|
||||||
|
|
|
@ -4,7 +4,7 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:nc_photos/account.dart';
|
import 'package:nc_photos/account.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
||||||
import 'package:nc_photos/k.dart' as k;
|
import 'package:nc_photos/k.dart' as k;
|
||||||
import 'package:nc_photos/theme.dart';
|
import 'package:nc_photos/theme.dart';
|
||||||
|
@ -26,7 +26,7 @@ class SlideshowViewerArguments {
|
||||||
);
|
);
|
||||||
|
|
||||||
final Account account;
|
final Account account;
|
||||||
final List<File> streamFiles;
|
final List<FileDescriptor> streamFiles;
|
||||||
final int startIndex;
|
final int startIndex;
|
||||||
final SlideshowConfig config;
|
final SlideshowConfig config;
|
||||||
}
|
}
|
||||||
|
@ -59,7 +59,7 @@ class SlideshowViewer extends StatefulWidget {
|
||||||
createState() => _SlideshowViewerState();
|
createState() => _SlideshowViewerState();
|
||||||
|
|
||||||
final Account account;
|
final Account account;
|
||||||
final List<File> streamFiles;
|
final List<FileDescriptor> streamFiles;
|
||||||
final int startIndex;
|
final int startIndex;
|
||||||
final SlideshowConfig config;
|
final SlideshowConfig config;
|
||||||
}
|
}
|
||||||
|
@ -185,7 +185,7 @@ class _SlideshowViewerState extends State<SlideshowViewer>
|
||||||
} else if (file_util.isSupportedVideoFormat(file)) {
|
} else if (file_util.isSupportedVideoFormat(file)) {
|
||||||
return _buildVideoView(context, index);
|
return _buildVideoView(context, index);
|
||||||
} else {
|
} else {
|
||||||
_log.shout("[_buildItemView] Unknown file format: ${file.contentType}");
|
_log.shout("[_buildItemView] Unknown file format: ${file.fdMime}");
|
||||||
return Container();
|
return Container();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -216,7 +216,8 @@ class _SmartAlbumBrowserState extends State<SmartAlbumBrowser>
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onDownloadPressed() {
|
void _onDownloadPressed() {
|
||||||
DownloadHandler().downloadFiles(
|
final c = KiwiContainer().resolve<DiContainer>();
|
||||||
|
DownloadHandler(c).downloadFiles(
|
||||||
widget.account,
|
widget.account,
|
||||||
_sortedItems.whereType<AlbumFileItem>().map((e) => e.file).toList(),
|
_sortedItems.whereType<AlbumFileItem>().map((e) => e.file).toList(),
|
||||||
parentDir: _album!.name,
|
parentDir: _album!.name,
|
||||||
|
@ -236,11 +237,13 @@ class _SmartAlbumBrowserState extends State<SmartAlbumBrowser>
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onSelectionSharePressed(BuildContext context) {
|
void _onSelectionSharePressed(BuildContext context) {
|
||||||
|
final c = KiwiContainer().resolve<DiContainer>();
|
||||||
final selected = selectedListItems
|
final selected = selectedListItems
|
||||||
.whereType<_FileListItem>()
|
.whereType<_FileListItem>()
|
||||||
.map((e) => e.file)
|
.map((e) => e.file)
|
||||||
.toList();
|
.toList();
|
||||||
ShareHandler(
|
ShareHandler(
|
||||||
|
c,
|
||||||
context: context,
|
context: context,
|
||||||
clearSelection: () {
|
clearSelection: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
|
@ -251,10 +254,11 @@ class _SmartAlbumBrowserState extends State<SmartAlbumBrowser>
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onSelectionAddPressed(BuildContext context) async {
|
Future<void> _onSelectionAddPressed(BuildContext context) async {
|
||||||
return AddSelectionToAlbumHandler()(
|
final c = KiwiContainer().resolve<DiContainer>();
|
||||||
|
return AddSelectionToAlbumHandler(c)(
|
||||||
context: context,
|
context: context,
|
||||||
account: widget.account,
|
account: widget.account,
|
||||||
selectedFiles: selectedListItems
|
selection: selectedListItems
|
||||||
.whereType<_FileListItem>()
|
.whereType<_FileListItem>()
|
||||||
.map((e) => e.file)
|
.map((e) => e.file)
|
||||||
.toList(),
|
.toList(),
|
||||||
|
@ -269,11 +273,12 @@ class _SmartAlbumBrowserState extends State<SmartAlbumBrowser>
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onSelectionDownloadPressed() {
|
void _onSelectionDownloadPressed() {
|
||||||
|
final c = KiwiContainer().resolve<DiContainer>();
|
||||||
final selected = selectedListItems
|
final selected = selectedListItems
|
||||||
.whereType<_FileListItem>()
|
.whereType<_FileListItem>()
|
||||||
.map((e) => e.file)
|
.map((e) => e.file)
|
||||||
.toList();
|
.toList();
|
||||||
DownloadHandler().downloadFiles(widget.account, selected);
|
DownloadHandler(c).downloadFiles(widget.account, selected);
|
||||||
setState(() {
|
setState(() {
|
||||||
clearSelectedItems();
|
clearSelectedItems();
|
||||||
});
|
});
|
||||||
|
|
|
@ -11,6 +11,7 @@ import 'package:nc_photos/compute_queue.dart';
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/download_handler.dart';
|
import 'package:nc_photos/download_handler.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/entity/tag.dart';
|
import 'package:nc_photos/entity/tag.dart';
|
||||||
import 'package:nc_photos/event/event.dart';
|
import 'package:nc_photos/event/event.dart';
|
||||||
import 'package:nc_photos/exception_util.dart' as exception_util;
|
import 'package:nc_photos/exception_util.dart' as exception_util;
|
||||||
|
@ -304,11 +305,13 @@ class _TagBrowserState extends State<TagBrowser>
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onSelectionSharePressed(BuildContext context) {
|
void _onSelectionSharePressed(BuildContext context) {
|
||||||
|
final c = KiwiContainer().resolve<DiContainer>();
|
||||||
final selected = selectedListItems
|
final selected = selectedListItems
|
||||||
.whereType<PhotoListFileItem>()
|
.whereType<PhotoListFileItem>()
|
||||||
.map((e) => e.file)
|
.map((e) => e.file)
|
||||||
.toList();
|
.toList();
|
||||||
ShareHandler(
|
ShareHandler(
|
||||||
|
c,
|
||||||
context: context,
|
context: context,
|
||||||
clearSelection: () {
|
clearSelection: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
|
@ -319,10 +322,11 @@ class _TagBrowserState extends State<TagBrowser>
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onSelectionAddToAlbumPressed(BuildContext context) {
|
Future<void> _onSelectionAddToAlbumPressed(BuildContext context) {
|
||||||
return AddSelectionToAlbumHandler()(
|
final c = KiwiContainer().resolve<DiContainer>();
|
||||||
|
return AddSelectionToAlbumHandler(c)(
|
||||||
context: context,
|
context: context,
|
||||||
account: widget.account,
|
account: widget.account,
|
||||||
selectedFiles: selectedListItems
|
selection: selectedListItems
|
||||||
.whereType<PhotoListFileItem>()
|
.whereType<PhotoListFileItem>()
|
||||||
.map((e) => e.file)
|
.map((e) => e.file)
|
||||||
.toList(),
|
.toList(),
|
||||||
|
@ -337,17 +341,19 @@ class _TagBrowserState extends State<TagBrowser>
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onSelectionDownloadPressed() {
|
void _onSelectionDownloadPressed() {
|
||||||
|
final c = KiwiContainer().resolve<DiContainer>();
|
||||||
final selected = selectedListItems
|
final selected = selectedListItems
|
||||||
.whereType<PhotoListFileItem>()
|
.whereType<PhotoListFileItem>()
|
||||||
.map((e) => e.file)
|
.map((e) => e.file)
|
||||||
.toList();
|
.toList();
|
||||||
DownloadHandler().downloadFiles(widget.account, selected);
|
DownloadHandler(c).downloadFiles(widget.account, selected);
|
||||||
setState(() {
|
setState(() {
|
||||||
clearSelectedItems();
|
clearSelectedItems();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onSelectionArchivePressed(BuildContext context) async {
|
Future<void> _onSelectionArchivePressed(BuildContext context) async {
|
||||||
|
final c = KiwiContainer().resolve<DiContainer>();
|
||||||
final selectedFiles = selectedListItems
|
final selectedFiles = selectedListItems
|
||||||
.whereType<PhotoListFileItem>()
|
.whereType<PhotoListFileItem>()
|
||||||
.map((e) => e.file)
|
.map((e) => e.file)
|
||||||
|
@ -355,13 +361,14 @@ class _TagBrowserState extends State<TagBrowser>
|
||||||
setState(() {
|
setState(() {
|
||||||
clearSelectedItems();
|
clearSelectedItems();
|
||||||
});
|
});
|
||||||
await ArchiveSelectionHandler(KiwiContainer().resolve<DiContainer>())(
|
await ArchiveSelectionHandler(c)(
|
||||||
account: widget.account,
|
account: widget.account,
|
||||||
selectedFiles: selectedFiles,
|
selection: selectedFiles,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onSelectionDeletePressed(BuildContext context) async {
|
Future<void> _onSelectionDeletePressed(BuildContext context) async {
|
||||||
|
final c = KiwiContainer().resolve<DiContainer>();
|
||||||
final selectedFiles = selectedListItems
|
final selectedFiles = selectedListItems
|
||||||
.whereType<PhotoListFileItem>()
|
.whereType<PhotoListFileItem>()
|
||||||
.map((e) => e.file)
|
.map((e) => e.file)
|
||||||
|
@ -369,16 +376,15 @@ class _TagBrowserState extends State<TagBrowser>
|
||||||
setState(() {
|
setState(() {
|
||||||
clearSelectedItems();
|
clearSelectedItems();
|
||||||
});
|
});
|
||||||
await RemoveSelectionHandler()(
|
await RemoveSelectionHandler(c)(
|
||||||
account: widget.account,
|
account: widget.account,
|
||||||
selectedFiles: selectedFiles,
|
selection: selectedFiles,
|
||||||
isMoveToTrash: true,
|
isMoveToTrash: true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onFilePropertyUpdated(FilePropertyUpdatedEvent ev) {
|
void _onFilePropertyUpdated(FilePropertyUpdatedEvent ev) {
|
||||||
if (_backingFiles.containsIf(ev.file, (a, b) => a.fileId == b.fileId) !=
|
if (_backingFiles.containsIf(ev.file, (a, b) => a.fdId == b.fdId) != true) {
|
||||||
true) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_refreshThrottler.trigger(
|
_refreshThrottler.trigger(
|
||||||
|
@ -427,7 +433,7 @@ class _TagBrowserState extends State<TagBrowser>
|
||||||
late final DiContainer _c;
|
late final DiContainer _c;
|
||||||
|
|
||||||
late final ListTagFileBloc _bloc = ListTagFileBloc(_c);
|
late final ListTagFileBloc _bloc = ListTagFileBloc(_c);
|
||||||
var _backingFiles = <File>[];
|
var _backingFiles = <FileDescriptor>[];
|
||||||
|
|
||||||
final _buildItemQueue =
|
final _buildItemQueue =
|
||||||
ComputeQueue<PhotoListItemBuilderArguments, PhotoListItemBuilderResult>();
|
ComputeQueue<PhotoListItemBuilderArguments, PhotoListItemBuilderResult>();
|
||||||
|
|
|
@ -12,6 +12,7 @@ import 'package:nc_photos/compute_queue.dart';
|
||||||
import 'package:nc_photos/debug_util.dart';
|
import 'package:nc_photos/debug_util.dart';
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/exception_util.dart' as exception_util;
|
import 'package:nc_photos/exception_util.dart' as exception_util;
|
||||||
import 'package:nc_photos/k.dart' as k;
|
import 'package:nc_photos/k.dart' as k;
|
||||||
import 'package:nc_photos/language_util.dart' as language_util;
|
import 'package:nc_photos/language_util.dart' as language_util;
|
||||||
|
@ -19,6 +20,7 @@ import 'package:nc_photos/object_extension.dart';
|
||||||
import 'package:nc_photos/pref.dart';
|
import 'package:nc_photos/pref.dart';
|
||||||
import 'package:nc_photos/snack_bar_manager.dart';
|
import 'package:nc_photos/snack_bar_manager.dart';
|
||||||
import 'package:nc_photos/theme.dart';
|
import 'package:nc_photos/theme.dart';
|
||||||
|
import 'package:nc_photos/use_case/inflate_file_descriptor.dart';
|
||||||
import 'package:nc_photos/use_case/restore_trashbin.dart';
|
import 'package:nc_photos/use_case/restore_trashbin.dart';
|
||||||
import 'package:nc_photos/widget/builder/photo_list_item_builder.dart';
|
import 'package:nc_photos/widget/builder/photo_list_item_builder.dart';
|
||||||
import 'package:nc_photos/widget/empty_list_indicator.dart';
|
import 'package:nc_photos/widget/empty_list_indicator.dart';
|
||||||
|
@ -294,18 +296,20 @@ class _TrashbinBrowserState extends State<TrashbinBrowser>
|
||||||
.restoreSelectedProcessingNotification(selectedListItems.length)),
|
.restoreSelectedProcessingNotification(selectedListItems.length)),
|
||||||
duration: k.snackBarDurationShort,
|
duration: k.snackBarDurationShort,
|
||||||
));
|
));
|
||||||
final selectedFiles = selectedListItems
|
final selection = selectedListItems
|
||||||
.whereType<PhotoListFileItem>()
|
.whereType<PhotoListFileItem>()
|
||||||
.map((e) => e.file)
|
.map((e) => e.file)
|
||||||
.toList();
|
.toList();
|
||||||
setState(() {
|
setState(() {
|
||||||
clearSelectedItems();
|
clearSelectedItems();
|
||||||
});
|
});
|
||||||
|
final c = KiwiContainer().resolve<DiContainer>();
|
||||||
|
final selectedFiles =
|
||||||
|
await InflateFileDescriptor(c)(widget.account, selection);
|
||||||
final failures = <File>[];
|
final failures = <File>[];
|
||||||
for (final f in selectedFiles) {
|
for (final f in selectedFiles) {
|
||||||
try {
|
try {
|
||||||
await RestoreTrashbin(KiwiContainer().resolve<DiContainer>())(
|
await RestoreTrashbin(c)(widget.account, f);
|
||||||
widget.account, f);
|
|
||||||
} catch (e, stacktrace) {
|
} catch (e, stacktrace) {
|
||||||
_log.shout(
|
_log.shout(
|
||||||
"[_onSelectionAppBarRestorePressed] Failed while restoring file: ${logFilename(f.path)}",
|
"[_onSelectionAppBarRestorePressed] Failed while restoring file: ${logFilename(f.path)}",
|
||||||
|
@ -363,7 +367,7 @@ class _TrashbinBrowserState extends State<TrashbinBrowser>
|
||||||
(result) {
|
(result) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_backingFiles = result.backingFiles;
|
_backingFiles = result.backingFiles.cast();
|
||||||
itemStreamListItems = result.listItems;
|
itemStreamListItems = result.listItems;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -382,10 +386,11 @@ class _TrashbinBrowserState extends State<TrashbinBrowser>
|
||||||
return _deleteFiles(selectedFiles);
|
return _deleteFiles(selectedFiles);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _deleteFiles(List<File> files) async {
|
Future<void> _deleteFiles(List<FileDescriptor> files) async {
|
||||||
await RemoveSelectionHandler()(
|
final c = KiwiContainer().resolve<DiContainer>();
|
||||||
|
await RemoveSelectionHandler(c)(
|
||||||
account: widget.account,
|
account: widget.account,
|
||||||
selectedFiles: files,
|
selection: files,
|
||||||
shouldCleanupAlbum: false,
|
shouldCleanupAlbum: false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -415,7 +420,9 @@ enum _SelectionAppBarMenuOption {
|
||||||
delete,
|
delete,
|
||||||
}
|
}
|
||||||
|
|
||||||
int _fileSorter(File a, File b) {
|
int _fileSorter(FileDescriptor fdA, FileDescriptor fdB) {
|
||||||
|
final a = fdA as File;
|
||||||
|
final b = fdB as File;
|
||||||
if (a.trashbinDeletionTime == null && b.trashbinDeletionTime == null) {
|
if (a.trashbinDeletionTime == null && b.trashbinDeletionTime == null) {
|
||||||
// ?
|
// ?
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -311,11 +311,12 @@ class _TrashbinViewerState extends State<TrashbinViewer> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _delete(BuildContext context) async {
|
Future<void> _delete(BuildContext context) async {
|
||||||
|
final c = KiwiContainer().resolve<DiContainer>();
|
||||||
final file = widget.streamFiles[_viewerController.currentPage];
|
final file = widget.streamFiles[_viewerController.currentPage];
|
||||||
_log.info("[_delete] Removing file: ${file.path}");
|
_log.info("[_delete] Removing file: ${file.path}");
|
||||||
final count = await RemoveSelectionHandler()(
|
final count = await RemoveSelectionHandler(c)(
|
||||||
account: widget.account,
|
account: widget.account,
|
||||||
selectedFiles: [file],
|
selection: [file],
|
||||||
shouldCleanupAlbum: false,
|
shouldCleanupAlbum: false,
|
||||||
isRemoveOpened: true,
|
isRemoveOpened: true,
|
||||||
);
|
);
|
||||||
|
|
|
@ -4,7 +4,7 @@ import 'package:nc_photos/account.dart';
|
||||||
import 'package:nc_photos/api/api.dart';
|
import 'package:nc_photos/api/api.dart';
|
||||||
import 'package:nc_photos/api/api_util.dart' as api_util;
|
import 'package:nc_photos/api/api_util.dart' as api_util;
|
||||||
import 'package:nc_photos/app_localizations.dart';
|
import 'package:nc_photos/app_localizations.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/exception_util.dart' as exception_util;
|
import 'package:nc_photos/exception_util.dart' as exception_util;
|
||||||
import 'package:nc_photos/k.dart' as k;
|
import 'package:nc_photos/k.dart' as k;
|
||||||
import 'package:nc_photos/platform/k.dart' as platform_k;
|
import 'package:nc_photos/platform/k.dart' as platform_k;
|
||||||
|
@ -33,7 +33,7 @@ class VideoViewer extends StatefulWidget {
|
||||||
createState() => _VideoViewerState();
|
createState() => _VideoViewerState();
|
||||||
|
|
||||||
final Account account;
|
final Account account;
|
||||||
final File file;
|
final FileDescriptor file;
|
||||||
final VoidCallback? onLoaded;
|
final VoidCallback? onLoaded;
|
||||||
final VoidCallback? onLoadFailure;
|
final VoidCallback? onLoadFailure;
|
||||||
final ValueChanged<double>? onHeightChanged;
|
final ValueChanged<double>? onHeightChanged;
|
||||||
|
|
|
@ -12,7 +12,7 @@ import 'package:nc_photos/app_localizations.dart';
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/download_handler.dart';
|
import 'package:nc_photos/download_handler.dart';
|
||||||
import 'package:nc_photos/entity/album.dart';
|
import 'package:nc_photos/entity/album.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
||||||
import 'package:nc_photos/flutter_util.dart';
|
import 'package:nc_photos/flutter_util.dart';
|
||||||
import 'package:nc_photos/k.dart' as k;
|
import 'package:nc_photos/k.dart' as k;
|
||||||
|
@ -21,6 +21,7 @@ import 'package:nc_photos/platform/features.dart' as features;
|
||||||
import 'package:nc_photos/pref.dart';
|
import 'package:nc_photos/pref.dart';
|
||||||
import 'package:nc_photos/share_handler.dart';
|
import 'package:nc_photos/share_handler.dart';
|
||||||
import 'package:nc_photos/theme.dart';
|
import 'package:nc_photos/theme.dart';
|
||||||
|
import 'package:nc_photos/use_case/inflate_file_descriptor.dart';
|
||||||
import 'package:nc_photos/use_case/update_property.dart';
|
import 'package:nc_photos/use_case/update_property.dart';
|
||||||
import 'package:nc_photos/widget/animated_visibility.dart';
|
import 'package:nc_photos/widget/animated_visibility.dart';
|
||||||
import 'package:nc_photos/widget/disposable.dart';
|
import 'package:nc_photos/widget/disposable.dart';
|
||||||
|
@ -45,7 +46,7 @@ class ViewerArguments {
|
||||||
});
|
});
|
||||||
|
|
||||||
final Account account;
|
final Account account;
|
||||||
final List<File> streamFiles;
|
final List<FileDescriptor> streamFiles;
|
||||||
final int startIndex;
|
final int startIndex;
|
||||||
final Album? album;
|
final Album? album;
|
||||||
}
|
}
|
||||||
|
@ -81,7 +82,7 @@ class Viewer extends StatefulWidget {
|
||||||
createState() => _ViewerState();
|
createState() => _ViewerState();
|
||||||
|
|
||||||
final Account account;
|
final Account account;
|
||||||
final List<File> streamFiles;
|
final List<FileDescriptor> streamFiles;
|
||||||
final int startIndex;
|
final int startIndex;
|
||||||
|
|
||||||
/// The album these files belongs to, or null
|
/// The album these files belongs to, or null
|
||||||
|
@ -166,7 +167,8 @@ class _ViewerState extends State<Viewer>
|
||||||
foregroundColor: Colors.white.withOpacity(.87),
|
foregroundColor: Colors.white.withOpacity(.87),
|
||||||
actions: [
|
actions: [
|
||||||
if (!_isDetailPaneActive && _canOpenDetailPane()) ...[
|
if (!_isDetailPaneActive && _canOpenDetailPane()) ...[
|
||||||
(_pageStates[index]?.favoriteOverride ?? file.isFavorite) ==
|
(_pageStates[index]?.favoriteOverride ??
|
||||||
|
file.fdIsFavorite) ==
|
||||||
true
|
true
|
||||||
? IconButton(
|
? IconButton(
|
||||||
icon: const Icon(Icons.star),
|
icon: const Icon(Icons.star),
|
||||||
|
@ -313,7 +315,7 @@ class _ViewerState extends State<Viewer>
|
||||||
visible: _isShowDetailPane,
|
visible: _isShowDetailPane,
|
||||||
child: ViewerDetailPane(
|
child: ViewerDetailPane(
|
||||||
account: widget.account,
|
account: widget.account,
|
||||||
file: widget.streamFiles[index],
|
fd: widget.streamFiles[index],
|
||||||
album: widget.album,
|
album: widget.album,
|
||||||
onSlideshowPressed: _onSlideshowPressed,
|
onSlideshowPressed: _onSlideshowPressed,
|
||||||
),
|
),
|
||||||
|
@ -336,7 +338,7 @@ class _ViewerState extends State<Viewer>
|
||||||
} else if (file_util.isSupportedVideoFormat(file)) {
|
} else if (file_util.isSupportedVideoFormat(file)) {
|
||||||
return _buildVideoView(context, index);
|
return _buildVideoView(context, index);
|
||||||
} else {
|
} else {
|
||||||
_log.shout("[_buildItemView] Unknown file format: ${file.contentType}");
|
_log.shout("[_buildItemView] Unknown file format: ${file.fdMime}");
|
||||||
_pageStates[index]!.itemHeight = 0;
|
_pageStates[index]!.itemHeight = 0;
|
||||||
return Container();
|
return Container();
|
||||||
}
|
}
|
||||||
|
@ -481,13 +483,19 @@ class _ViewerState extends State<Viewer>
|
||||||
/// Called when the page is being built for the first time
|
/// Called when the page is being built for the first time
|
||||||
void _onCreateNewPage(BuildContext context, int index) {
|
void _onCreateNewPage(BuildContext context, int index) {
|
||||||
_pageStates[index] = _PageState(ScrollController(
|
_pageStates[index] = _PageState(ScrollController(
|
||||||
initialScrollOffset: _isShowDetailPane && !_isClosingDetailPane
|
initialScrollOffset: _isShowDetailPane && !_isClosingDetailPane
|
||||||
? _calcDetailPaneOpenedScrollPosition(index)
|
? _calcDetailPaneOpenedScrollPosition(index)
|
||||||
: 0));
|
: 0,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Called when the page is being built after previously moved out of view
|
/// Called when the page is being built after previously moved out of view
|
||||||
void _onRecreatePageAfterMovedOut(BuildContext context, int index) {
|
void _onRecreatePageAfterMovedOut(BuildContext context, int index) {
|
||||||
|
_pageStates[index]!.setScrollController(ScrollController(
|
||||||
|
initialScrollOffset: _isShowDetailPane && !_isClosingDetailPane
|
||||||
|
? _calcDetailPaneOpenedScrollPosition(index)
|
||||||
|
: 0,
|
||||||
|
));
|
||||||
if (_isShowDetailPane && !_isClosingDetailPane) {
|
if (_isShowDetailPane && !_isClosingDetailPane) {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
if (_pageStates[index]!.itemHeight != null) {
|
if (_pageStates[index]!.itemHeight != null) {
|
||||||
|
@ -509,8 +517,9 @@ class _ViewerState extends State<Viewer>
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final file = widget.streamFiles[_viewerController.currentPage];
|
final fd = widget.streamFiles[_viewerController.currentPage];
|
||||||
final c = KiwiContainer().resolve<DiContainer>();
|
final c = KiwiContainer().resolve<DiContainer>();
|
||||||
|
final file = (await InflateFileDescriptor(c)(widget.account, [fd])).first;
|
||||||
setState(() {
|
setState(() {
|
||||||
_pageStates[index]!.favoriteOverride = true;
|
_pageStates[index]!.favoriteOverride = true;
|
||||||
});
|
});
|
||||||
|
@ -542,8 +551,9 @@ class _ViewerState extends State<Viewer>
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final file = widget.streamFiles[_viewerController.currentPage];
|
final fd = widget.streamFiles[_viewerController.currentPage];
|
||||||
final c = KiwiContainer().resolve<DiContainer>();
|
final c = KiwiContainer().resolve<DiContainer>();
|
||||||
|
final file = (await InflateFileDescriptor(c)(widget.account, [fd])).first;
|
||||||
setState(() {
|
setState(() {
|
||||||
_pageStates[index]!.favoriteOverride = false;
|
_pageStates[index]!.favoriteOverride = false;
|
||||||
});
|
});
|
||||||
|
@ -578,8 +588,10 @@ class _ViewerState extends State<Viewer>
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onSharePressed(BuildContext context) {
|
void _onSharePressed(BuildContext context) {
|
||||||
|
final c = KiwiContainer().resolve<DiContainer>();
|
||||||
final file = widget.streamFiles[_viewerController.currentPage];
|
final file = widget.streamFiles[_viewerController.currentPage];
|
||||||
ShareHandler(
|
ShareHandler(
|
||||||
|
c,
|
||||||
context: context,
|
context: context,
|
||||||
).shareFiles(widget.account, [file]);
|
).shareFiles(widget.account, [file]);
|
||||||
}
|
}
|
||||||
|
@ -591,7 +603,7 @@ class _ViewerState extends State<Viewer>
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_log.info("[_onEditPressed] Edit file: ${file.path}");
|
_log.info("[_onEditPressed] Edit file: ${file.fdPath}");
|
||||||
Navigator.of(context).pushNamed(ImageEditor.routeName,
|
Navigator.of(context).pushNamed(ImageEditor.routeName,
|
||||||
arguments: ImageEditorArguments(widget.account, file));
|
arguments: ImageEditorArguments(widget.account, file));
|
||||||
}
|
}
|
||||||
|
@ -604,24 +616,26 @@ class _ViewerState extends State<Viewer>
|
||||||
}
|
}
|
||||||
final c = KiwiContainer().resolve<DiContainer>();
|
final c = KiwiContainer().resolve<DiContainer>();
|
||||||
|
|
||||||
_log.info("[_onEnhancePressed] Enhance file: ${file.path}");
|
_log.info("[_onEnhancePressed] Enhance file: ${file.fdPath}");
|
||||||
Navigator.of(context).pushNamed(ImageEnhancer.routeName,
|
Navigator.of(context).pushNamed(ImageEnhancer.routeName,
|
||||||
arguments: ImageEnhancerArguments(
|
arguments: ImageEnhancerArguments(
|
||||||
widget.account, file, c.pref.isSaveEditResultToServerOr()));
|
widget.account, file, c.pref.isSaveEditResultToServerOr()));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onDownloadPressed() {
|
void _onDownloadPressed() {
|
||||||
|
final c = KiwiContainer().resolve<DiContainer>();
|
||||||
final file = widget.streamFiles[_viewerController.currentPage];
|
final file = widget.streamFiles[_viewerController.currentPage];
|
||||||
_log.info("[_onDownloadPressed] Downloading file: ${file.path}");
|
_log.info("[_onDownloadPressed] Downloading file: ${file.fdPath}");
|
||||||
DownloadHandler().downloadFiles(widget.account, [file]);
|
DownloadHandler(c).downloadFiles(widget.account, [file]);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onDeletePressed(BuildContext context) async {
|
Future<void> _onDeletePressed(BuildContext context) async {
|
||||||
|
final c = KiwiContainer().resolve<DiContainer>();
|
||||||
final file = widget.streamFiles[_viewerController.currentPage];
|
final file = widget.streamFiles[_viewerController.currentPage];
|
||||||
_log.info("[_onDeletePressed] Removing file: ${file.path}");
|
_log.info("[_onDeletePressed] Removing file: ${file.fdPath}");
|
||||||
final count = await RemoveSelectionHandler()(
|
final count = await RemoveSelectionHandler(c)(
|
||||||
account: widget.account,
|
account: widget.account,
|
||||||
selectedFiles: [file],
|
selection: [file],
|
||||||
isRemoveOpened: true,
|
isRemoveOpened: true,
|
||||||
isMoveToTrash: true,
|
isMoveToTrash: true,
|
||||||
);
|
);
|
||||||
|
@ -754,6 +768,10 @@ class _ViewerState extends State<Viewer>
|
||||||
class _PageState {
|
class _PageState {
|
||||||
_PageState(this.scrollController);
|
_PageState(this.scrollController);
|
||||||
|
|
||||||
|
void setScrollController(ScrollController c) {
|
||||||
|
scrollController = c;
|
||||||
|
}
|
||||||
|
|
||||||
ScrollController scrollController;
|
ScrollController scrollController;
|
||||||
double? itemHeight;
|
double? itemHeight;
|
||||||
bool hasLoaded = false;
|
bool hasLoaded = false;
|
||||||
|
|
|
@ -16,6 +16,7 @@ import 'package:nc_photos/entity/album/item.dart';
|
||||||
import 'package:nc_photos/entity/album/provider.dart';
|
import 'package:nc_photos/entity/album/provider.dart';
|
||||||
import 'package:nc_photos/entity/exif_extension.dart';
|
import 'package:nc_photos/entity/exif_extension.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/k.dart' as k;
|
import 'package:nc_photos/k.dart' as k;
|
||||||
import 'package:nc_photos/location_util.dart' as location_util;
|
import 'package:nc_photos/location_util.dart' as location_util;
|
||||||
import 'package:nc_photos/notified_action.dart';
|
import 'package:nc_photos/notified_action.dart';
|
||||||
|
@ -24,6 +25,7 @@ import 'package:nc_photos/platform/features.dart' as features;
|
||||||
import 'package:nc_photos/platform/k.dart' as platform_k;
|
import 'package:nc_photos/platform/k.dart' as platform_k;
|
||||||
import 'package:nc_photos/snack_bar_manager.dart';
|
import 'package:nc_photos/snack_bar_manager.dart';
|
||||||
import 'package:nc_photos/theme.dart';
|
import 'package:nc_photos/theme.dart';
|
||||||
|
import 'package:nc_photos/use_case/inflate_file_descriptor.dart';
|
||||||
import 'package:nc_photos/use_case/list_file_tag.dart';
|
import 'package:nc_photos/use_case/list_file_tag.dart';
|
||||||
import 'package:nc_photos/use_case/remove_from_album.dart';
|
import 'package:nc_photos/use_case/remove_from_album.dart';
|
||||||
import 'package:nc_photos/use_case/update_album.dart';
|
import 'package:nc_photos/use_case/update_album.dart';
|
||||||
|
@ -42,7 +44,7 @@ class ViewerDetailPane extends StatefulWidget {
|
||||||
const ViewerDetailPane({
|
const ViewerDetailPane({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.account,
|
required this.account,
|
||||||
required this.file,
|
required this.fd,
|
||||||
this.album,
|
this.album,
|
||||||
this.onSlideshowPressed,
|
this.onSlideshowPressed,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
@ -51,7 +53,7 @@ class ViewerDetailPane extends StatefulWidget {
|
||||||
createState() => _ViewerDetailPaneState();
|
createState() => _ViewerDetailPaneState();
|
||||||
|
|
||||||
final Account account;
|
final Account account;
|
||||||
final File file;
|
final FileDescriptor fd;
|
||||||
|
|
||||||
/// The album this file belongs to, or null
|
/// The album this file belongs to, or null
|
||||||
final Album? album;
|
final Album? album;
|
||||||
|
@ -63,6 +65,7 @@ class _ViewerDetailPaneState extends State<ViewerDetailPane> {
|
||||||
_ViewerDetailPaneState() {
|
_ViewerDetailPaneState() {
|
||||||
final c = KiwiContainer().resolve<DiContainer>();
|
final c = KiwiContainer().resolve<DiContainer>();
|
||||||
assert(require(c));
|
assert(require(c));
|
||||||
|
assert(InflateFileDescriptor.require(c));
|
||||||
_c = c;
|
_c = c;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,27 +75,46 @@ class _ViewerDetailPaneState extends State<ViewerDetailPane> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
initState() {
|
initState() {
|
||||||
|
_log.info("[initState] File: ${widget.fd.fdPath}");
|
||||||
super.initState();
|
super.initState();
|
||||||
|
_dateTime = widget.fd.fdDateTime.toLocal();
|
||||||
|
_initFile();
|
||||||
|
}
|
||||||
|
|
||||||
_dateTime = widget.file.bestDateTime.toLocal();
|
Future<void> _initFile() async {
|
||||||
if (widget.file.metadata == null) {
|
_file =
|
||||||
|
(await InflateFileDescriptor(_c)(widget.account, [widget.fd])).first;
|
||||||
|
_log.fine("[_initFile] File inflated");
|
||||||
|
// update file
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {});
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (_file!.metadata == null) {
|
||||||
_log.info("[initState] Metadata missing in File");
|
_log.info("[initState] Metadata missing in File");
|
||||||
} else {
|
} else {
|
||||||
_log.info("[initState] Metadata exists in File");
|
_log.info("[initState] Metadata exists in File");
|
||||||
if (widget.file.metadata!.exif != null) {
|
if (_file!.metadata!.exif != null) {
|
||||||
_initMetadata();
|
_initMetadata();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_initTags();
|
await _initTags();
|
||||||
|
// update tages
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {});
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// postpone loading map to improve responsiveness
|
// postpone loading map to improve responsiveness
|
||||||
Future.delayed(const Duration(milliseconds: 750)).then((_) {
|
unawaited(Future.delayed(const Duration(milliseconds: 750)).then((_) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_shouldBlockGpsMap = false;
|
_shouldBlockGpsMap = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -104,90 +126,60 @@ class _ViewerDetailPaneState extends State<ViewerDetailPane> {
|
||||||
Localizations.localeOf(context).languageCode)
|
Localizations.localeOf(context).languageCode)
|
||||||
.format(_dateTime);
|
.format(_dateTime);
|
||||||
|
|
||||||
String sizeSubStr = "";
|
|
||||||
const space = " ";
|
|
||||||
if (widget.file.metadata?.imageWidth != null &&
|
|
||||||
widget.file.metadata?.imageHeight != null) {
|
|
||||||
final pixelCount = widget.file.metadata!.imageWidth! *
|
|
||||||
widget.file.metadata!.imageHeight!;
|
|
||||||
if (pixelCount >= 500000) {
|
|
||||||
final mpCount = pixelCount / 1000000.0;
|
|
||||||
sizeSubStr += L10n.global().megapixelCount(mpCount.toStringAsFixed(1));
|
|
||||||
sizeSubStr += space;
|
|
||||||
}
|
|
||||||
sizeSubStr += _byteSizeToString(widget.file.contentLength ?? 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
String cameraSubStr = "";
|
|
||||||
if (_fNumber != null) {
|
|
||||||
cameraSubStr += "f/${_fNumber!.toStringAsFixed(1)}$space";
|
|
||||||
}
|
|
||||||
if (_exposureTime != null) {
|
|
||||||
cameraSubStr += L10n.global().secondCountSymbol(_exposureTime!);
|
|
||||||
cameraSubStr += space;
|
|
||||||
}
|
|
||||||
if (_focalLength != null) {
|
|
||||||
cameraSubStr += L10n.global()
|
|
||||||
.millimeterCountSymbol(_focalLength!.toStringAsFixedTruncated(2));
|
|
||||||
cameraSubStr += space;
|
|
||||||
}
|
|
||||||
if (_isoSpeedRatings != null) {
|
|
||||||
cameraSubStr += "ISO$_isoSpeedRatings$space";
|
|
||||||
}
|
|
||||||
cameraSubStr = cameraSubStr.trim();
|
|
||||||
|
|
||||||
return Material(
|
return Material(
|
||||||
type: MaterialType.transparency,
|
type: MaterialType.transparency,
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
SingleChildScrollView(
|
if (_file != null) ...[
|
||||||
scrollDirection: Axis.horizontal,
|
SingleChildScrollView(
|
||||||
child: Row(
|
scrollDirection: Axis.horizontal,
|
||||||
children: [
|
child: Row(
|
||||||
if (_canRemoveFromAlbum)
|
children: [
|
||||||
|
if (_canRemoveFromAlbum)
|
||||||
|
_DetailPaneButton(
|
||||||
|
icon: Icons.remove_outlined,
|
||||||
|
label: L10n.global().removeFromAlbumTooltip,
|
||||||
|
onPressed: () => _onRemoveFromAlbumPressed(context),
|
||||||
|
),
|
||||||
|
if (widget.album != null &&
|
||||||
|
widget.album!.albumFile?.isOwned(widget.account.userId) ==
|
||||||
|
true)
|
||||||
|
_DetailPaneButton(
|
||||||
|
icon: Icons.photo_album_outlined,
|
||||||
|
label: L10n.global().useAsAlbumCoverTooltip,
|
||||||
|
onPressed: () => _onSetAlbumCoverPressed(context),
|
||||||
|
),
|
||||||
_DetailPaneButton(
|
_DetailPaneButton(
|
||||||
icon: Icons.remove_outlined,
|
icon: Icons.add,
|
||||||
label: L10n.global().removeFromAlbumTooltip,
|
label: L10n.global().addToAlbumTooltip,
|
||||||
onPressed: () => _onRemoveFromAlbumPressed(context),
|
onPressed: () => _onAddToAlbumPressed(context),
|
||||||
),
|
),
|
||||||
if (widget.album != null &&
|
if (widget.fd.fdIsArchived == true)
|
||||||
widget.album!.albumFile?.isOwned(widget.account.userId) ==
|
_DetailPaneButton(
|
||||||
true)
|
icon: Icons.unarchive_outlined,
|
||||||
|
label: L10n.global().unarchiveTooltip,
|
||||||
|
onPressed: () => _onUnarchivePressed(context),
|
||||||
|
)
|
||||||
|
else
|
||||||
|
_DetailPaneButton(
|
||||||
|
icon: Icons.archive_outlined,
|
||||||
|
label: L10n.global().archiveTooltip,
|
||||||
|
onPressed: () => _onArchivePressed(context),
|
||||||
|
),
|
||||||
_DetailPaneButton(
|
_DetailPaneButton(
|
||||||
icon: Icons.photo_album_outlined,
|
icon: Icons.slideshow_outlined,
|
||||||
label: L10n.global().useAsAlbumCoverTooltip,
|
label: L10n.global().slideshowTooltip,
|
||||||
onPressed: () => _onSetAlbumCoverPressed(context),
|
onPressed: widget.onSlideshowPressed,
|
||||||
),
|
),
|
||||||
_DetailPaneButton(
|
],
|
||||||
icon: Icons.add,
|
),
|
||||||
label: L10n.global().addToAlbumTooltip,
|
|
||||||
onPressed: () => _onAddToAlbumPressed(context),
|
|
||||||
),
|
|
||||||
if (widget.file.isArchived == true)
|
|
||||||
_DetailPaneButton(
|
|
||||||
icon: Icons.unarchive_outlined,
|
|
||||||
label: L10n.global().unarchiveTooltip,
|
|
||||||
onPressed: () => _onUnarchivePressed(context),
|
|
||||||
)
|
|
||||||
else
|
|
||||||
_DetailPaneButton(
|
|
||||||
icon: Icons.archive_outlined,
|
|
||||||
label: L10n.global().archiveTooltip,
|
|
||||||
onPressed: () => _onArchivePressed(context),
|
|
||||||
),
|
|
||||||
_DetailPaneButton(
|
|
||||||
icon: Icons.slideshow_outlined,
|
|
||||||
label: L10n.global().slideshowTooltip,
|
|
||||||
onPressed: widget.onSlideshowPressed,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
const Padding(
|
||||||
const Padding(
|
padding: EdgeInsets.symmetric(horizontal: 32),
|
||||||
padding: EdgeInsets.symmetric(horizontal: 32),
|
child: Divider(),
|
||||||
child: Divider(),
|
),
|
||||||
),
|
],
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: ListTileCenterLeading(
|
leading: ListTileCenterLeading(
|
||||||
child: Icon(
|
child: Icon(
|
||||||
|
@ -195,140 +187,147 @@ class _ViewerDetailPaneState extends State<ViewerDetailPane> {
|
||||||
color: AppTheme.getSecondaryTextColor(context),
|
color: AppTheme.getSecondaryTextColor(context),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
title: Text(path_lib.basenameWithoutExtension(widget.file.path)),
|
title: Text(path_lib.basenameWithoutExtension(widget.fd.fdPath)),
|
||||||
subtitle: Text(widget.file.strippedPath),
|
subtitle: Text(widget.fd.strippedPath),
|
||||||
),
|
),
|
||||||
if (!widget.file.isOwned(widget.account.userId))
|
if (_file != null) ...[
|
||||||
ListTile(
|
if (!_file!.isOwned(widget.account.userId))
|
||||||
leading: ListTileCenterLeading(
|
ListTile(
|
||||||
child: Icon(
|
leading: ListTileCenterLeading(
|
||||||
Icons.share_outlined,
|
child: Icon(
|
||||||
|
Icons.share_outlined,
|
||||||
|
color: AppTheme.getSecondaryTextColor(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
title:
|
||||||
|
Text(_file!.ownerDisplayName ?? _file!.ownerId!.toString()),
|
||||||
|
subtitle: Text(L10n.global().fileSharedByDescription),
|
||||||
|
),
|
||||||
|
if (_tags.isNotEmpty)
|
||||||
|
ListTile(
|
||||||
|
leading: Icon(
|
||||||
|
Icons.local_offer_outlined,
|
||||||
color: AppTheme.getSecondaryTextColor(context),
|
color: AppTheme.getSecondaryTextColor(context),
|
||||||
),
|
),
|
||||||
),
|
title: SizedBox(
|
||||||
title: Text(widget.file.ownerDisplayName ??
|
height: 40,
|
||||||
widget.file.ownerId!.toString()),
|
child: ListView.separated(
|
||||||
subtitle: Text(L10n.global().fileSharedByDescription),
|
scrollDirection: Axis.horizontal,
|
||||||
),
|
itemCount: _tags.length,
|
||||||
if (_tags.isNotEmpty)
|
itemBuilder: (context, index) => Center(
|
||||||
ListTile(
|
child: Wrap(
|
||||||
leading: Icon(
|
children: [
|
||||||
Icons.local_offer_outlined,
|
Container(
|
||||||
color: AppTheme.getSecondaryTextColor(context),
|
decoration: BoxDecoration(
|
||||||
),
|
color: AppTheme.getUnfocusedIconColor(context),
|
||||||
title: SizedBox(
|
borderRadius:
|
||||||
height: 40,
|
const BorderRadius.all(Radius.circular(8)),
|
||||||
child: ListView.separated(
|
),
|
||||||
scrollDirection: Axis.horizontal,
|
padding: const EdgeInsets.symmetric(
|
||||||
itemCount: _tags.length,
|
horizontal: 8, vertical: 4),
|
||||||
itemBuilder: (context, index) => Center(
|
alignment: Alignment.center,
|
||||||
child: Wrap(
|
child: Text(
|
||||||
children: [
|
_tags[index],
|
||||||
Container(
|
style: TextStyle(
|
||||||
decoration: BoxDecoration(
|
fontSize: 12,
|
||||||
color: AppTheme.getUnfocusedIconColor(context),
|
color: AppTheme.getPrimaryTextColorInverse(
|
||||||
borderRadius:
|
context),
|
||||||
const BorderRadius.all(Radius.circular(8)),
|
),
|
||||||
),
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 8, vertical: 4),
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: Text(
|
|
||||||
_tags[index],
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
color:
|
|
||||||
AppTheme.getPrimaryTextColorInverse(context),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
|
separatorBuilder: (context, index) =>
|
||||||
|
const SizedBox(width: 8),
|
||||||
),
|
),
|
||||||
separatorBuilder: (context, index) =>
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: Icon(
|
leading: Icon(
|
||||||
Icons.calendar_today_outlined,
|
Icons.calendar_today_outlined,
|
||||||
color: AppTheme.getSecondaryTextColor(context),
|
color: AppTheme.getSecondaryTextColor(context),
|
||||||
),
|
),
|
||||||
title: Text("$dateStr $timeStr"),
|
title: Text("$dateStr $timeStr"),
|
||||||
trailing: Icon(
|
trailing: _file == null
|
||||||
Icons.edit_outlined,
|
? null
|
||||||
color: AppTheme.getSecondaryTextColor(context),
|
: Icon(
|
||||||
),
|
Icons.edit_outlined,
|
||||||
onTap: () => _onDateTimeTap(context),
|
color: AppTheme.getSecondaryTextColor(context),
|
||||||
|
),
|
||||||
|
onTap: _file == null ? null : () => _onDateTimeTap(context),
|
||||||
),
|
),
|
||||||
if (widget.file.metadata?.imageWidth != null &&
|
if (_file != null) ...[
|
||||||
widget.file.metadata?.imageHeight != null)
|
if (_file!.metadata?.imageWidth != null &&
|
||||||
ListTile(
|
_file!.metadata?.imageHeight != null)
|
||||||
leading: ListTileCenterLeading(
|
ListTile(
|
||||||
child: Icon(
|
leading: ListTileCenterLeading(
|
||||||
|
child: Icon(
|
||||||
|
Icons.aspect_ratio,
|
||||||
|
color: AppTheme.getSecondaryTextColor(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
title: Text(
|
||||||
|
"${_file!.metadata!.imageWidth} x ${_file!.metadata!.imageHeight}"),
|
||||||
|
subtitle: Text(_buildSizeSubtitle()),
|
||||||
|
)
|
||||||
|
else
|
||||||
|
ListTile(
|
||||||
|
leading: Icon(
|
||||||
Icons.aspect_ratio,
|
Icons.aspect_ratio,
|
||||||
color: AppTheme.getSecondaryTextColor(context),
|
color: AppTheme.getSecondaryTextColor(context),
|
||||||
),
|
),
|
||||||
|
title: Text(_byteSizeToString(_file!.contentLength ?? 0)),
|
||||||
),
|
),
|
||||||
title: Text(
|
if (_model != null)
|
||||||
"${widget.file.metadata!.imageWidth} x ${widget.file.metadata!.imageHeight}"),
|
ListTile(
|
||||||
subtitle: Text(sizeSubStr),
|
leading: ListTileCenterLeading(
|
||||||
)
|
child: Icon(
|
||||||
else
|
Icons.camera_outlined,
|
||||||
ListTile(
|
color: AppTheme.getSecondaryTextColor(context),
|
||||||
leading: Icon(
|
),
|
||||||
Icons.aspect_ratio,
|
),
|
||||||
color: AppTheme.getSecondaryTextColor(context),
|
title: Text(_model!),
|
||||||
|
subtitle: _buildCameraSubtitle()
|
||||||
|
.run((s) => s.isNotEmpty ? Text(s) : null),
|
||||||
),
|
),
|
||||||
title: Text(_byteSizeToString(widget.file.contentLength ?? 0)),
|
if (_location?.name != null)
|
||||||
),
|
ListTile(
|
||||||
if (_model != null)
|
leading: ListTileCenterLeading(
|
||||||
ListTile(
|
child: Icon(
|
||||||
leading: ListTileCenterLeading(
|
Icons.location_on_outlined,
|
||||||
child: Icon(
|
color: AppTheme.getSecondaryTextColor(context),
|
||||||
Icons.camera_outlined,
|
),
|
||||||
|
),
|
||||||
|
title: Text(L10n.global().gpsPlaceText(_location!.name!)),
|
||||||
|
subtitle: _location!.toSubtitle()?.run((obj) => Text(obj)),
|
||||||
|
trailing: Icon(
|
||||||
|
Icons.info_outline,
|
||||||
color: AppTheme.getSecondaryTextColor(context),
|
color: AppTheme.getSecondaryTextColor(context),
|
||||||
),
|
),
|
||||||
|
onTap: () {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (_) => const AboutGeocodingDialog(),
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
title: Text(_model!),
|
if (features.isSupportMapView && _gps != null)
|
||||||
subtitle: cameraSubStr.isNotEmpty ? Text(cameraSubStr) : null,
|
AnimatedVisibility(
|
||||||
),
|
opacity: _shouldBlockGpsMap ? 0 : 1,
|
||||||
if (_location?.name != null)
|
curve: Curves.easeInOut,
|
||||||
ListTile(
|
duration: k.animationDurationNormal,
|
||||||
leading: ListTileCenterLeading(
|
child: SizedBox(
|
||||||
child: Icon(
|
height: 256,
|
||||||
Icons.location_on_outlined,
|
child: GpsMap(
|
||||||
color: AppTheme.getSecondaryTextColor(context),
|
center: _gps!,
|
||||||
|
zoom: 16,
|
||||||
|
onTap: _onMapTap,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
title: Text(L10n.global().gpsPlaceText(_location!.name!)),
|
],
|
||||||
subtitle: _location!.toSubtitle()?.run((obj) => Text(obj)),
|
|
||||||
trailing: Icon(
|
|
||||||
Icons.info_outline,
|
|
||||||
color: AppTheme.getSecondaryTextColor(context),
|
|
||||||
),
|
|
||||||
onTap: () {
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (_) => const AboutGeocodingDialog(),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
if (features.isSupportMapView && _gps != null)
|
|
||||||
AnimatedVisibility(
|
|
||||||
opacity: _shouldBlockGpsMap ? 0 : 1,
|
|
||||||
curve: Curves.easeInOut,
|
|
||||||
duration: k.animationDurationNormal,
|
|
||||||
child: SizedBox(
|
|
||||||
height: 256,
|
|
||||||
child: GpsMap(
|
|
||||||
center: _gps!,
|
|
||||||
zoom: 16,
|
|
||||||
onTap: _onMapTap,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -336,7 +335,8 @@ class _ViewerDetailPaneState extends State<ViewerDetailPane> {
|
||||||
|
|
||||||
/// Convert EXIF data to readable format
|
/// Convert EXIF data to readable format
|
||||||
void _initMetadata() {
|
void _initMetadata() {
|
||||||
final exif = widget.file.metadata!.exif!;
|
assert(_file != null);
|
||||||
|
final exif = _file!.metadata!.exif!;
|
||||||
_log.info("[_initMetadata] $exif");
|
_log.info("[_initMetadata] $exif");
|
||||||
|
|
||||||
if (exif.make != null && exif.model != null) {
|
if (exif.make != null && exif.model != null) {
|
||||||
|
@ -363,20 +363,60 @@ class _ViewerDetailPaneState extends State<ViewerDetailPane> {
|
||||||
if (lat != null && lng != null) {
|
if (lat != null && lng != null) {
|
||||||
_log.fine("GPS: ($lat, $lng)");
|
_log.fine("GPS: ($lat, $lng)");
|
||||||
_gps = Tuple2(lat, lng);
|
_gps = Tuple2(lat, lng);
|
||||||
_location = widget.file.location;
|
_location = _file!.location;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _initTags() async {
|
Future<void> _initTags() async {
|
||||||
|
assert(_file != null);
|
||||||
final c = KiwiContainer().resolve<DiContainer>();
|
final c = KiwiContainer().resolve<DiContainer>();
|
||||||
try {
|
try {
|
||||||
final tags = await ListFileTag(c)(widget.account, widget.file);
|
final tags = await ListFileTag(c)(widget.account, _file!);
|
||||||
_tags.addAll(tags.map((t) => t.displayName));
|
_tags.addAll(tags.map((t) => t.displayName));
|
||||||
} catch (e, stackTrace) {
|
} catch (e, stackTrace) {
|
||||||
_log.shout("[_initTags] Failed while ListFileTag", e, stackTrace);
|
_log.shout("[_initTags] Failed while ListFileTag", e, stackTrace);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String _buildSizeSubtitle() {
|
||||||
|
String sizeSubStr = "";
|
||||||
|
const space = " ";
|
||||||
|
if (_file!.metadata?.imageWidth != null &&
|
||||||
|
_file!.metadata?.imageHeight != null) {
|
||||||
|
final pixelCount =
|
||||||
|
_file!.metadata!.imageWidth! * _file!.metadata!.imageHeight!;
|
||||||
|
if (pixelCount >= 500000) {
|
||||||
|
final mpCount = pixelCount / 1000000.0;
|
||||||
|
sizeSubStr += L10n.global().megapixelCount(mpCount.toStringAsFixed(1));
|
||||||
|
sizeSubStr += space;
|
||||||
|
}
|
||||||
|
sizeSubStr += _byteSizeToString(_file!.contentLength ?? 0);
|
||||||
|
}
|
||||||
|
return sizeSubStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
String _buildCameraSubtitle() {
|
||||||
|
String cameraSubStr = "";
|
||||||
|
const space = " ";
|
||||||
|
if (_fNumber != null) {
|
||||||
|
cameraSubStr += "f/${_fNumber!.toStringAsFixed(1)}$space";
|
||||||
|
}
|
||||||
|
if (_exposureTime != null) {
|
||||||
|
cameraSubStr += L10n.global().secondCountSymbol(_exposureTime!);
|
||||||
|
cameraSubStr += space;
|
||||||
|
}
|
||||||
|
if (_focalLength != null) {
|
||||||
|
cameraSubStr += L10n.global()
|
||||||
|
.millimeterCountSymbol(_focalLength!.toStringAsFixedTruncated(2));
|
||||||
|
cameraSubStr += space;
|
||||||
|
}
|
||||||
|
if (_isoSpeedRatings != null) {
|
||||||
|
cameraSubStr += "ISO$_isoSpeedRatings$space";
|
||||||
|
}
|
||||||
|
cameraSubStr = cameraSubStr.trim();
|
||||||
|
return cameraSubStr;
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _onRemoveFromAlbumPressed(BuildContext context) async {
|
Future<void> _onRemoveFromAlbumPressed(BuildContext context) async {
|
||||||
assert(widget.album!.provider is AlbumStaticProvider);
|
assert(widget.album!.provider is AlbumStaticProvider);
|
||||||
try {
|
try {
|
||||||
|
@ -385,7 +425,7 @@ class _ViewerDetailPaneState extends State<ViewerDetailPane> {
|
||||||
final thisItem = AlbumStaticProvider.of(widget.album!)
|
final thisItem = AlbumStaticProvider.of(widget.album!)
|
||||||
.items
|
.items
|
||||||
.whereType<AlbumFileItem>()
|
.whereType<AlbumFileItem>()
|
||||||
.firstWhere((element) => element.file.path == widget.file.path);
|
.firstWhere((element) => element.file.path == widget.fd.fdPath);
|
||||||
await RemoveFromAlbum(KiwiContainer().resolve<DiContainer>())(
|
await RemoveFromAlbum(KiwiContainer().resolve<DiContainer>())(
|
||||||
widget.account, widget.album!, [thisItem]);
|
widget.account, widget.album!, [thisItem]);
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
|
@ -403,9 +443,10 @@ class _ViewerDetailPaneState extends State<ViewerDetailPane> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onSetAlbumCoverPressed(BuildContext context) async {
|
Future<void> _onSetAlbumCoverPressed(BuildContext context) async {
|
||||||
|
assert(_file != null);
|
||||||
assert(widget.album != null);
|
assert(widget.album != null);
|
||||||
_log.info(
|
_log.info(
|
||||||
"[_onSetAlbumCoverPressed] Set '${widget.file.path}' as album cover for '${widget.album!.name}'");
|
"[_onSetAlbumCoverPressed] Set '${widget.fd.fdPath}' as album cover for '${widget.album!.name}'");
|
||||||
try {
|
try {
|
||||||
await NotifiedAction(
|
await NotifiedAction(
|
||||||
() async {
|
() async {
|
||||||
|
@ -413,7 +454,7 @@ class _ViewerDetailPaneState extends State<ViewerDetailPane> {
|
||||||
widget.account,
|
widget.account,
|
||||||
widget.album!.copyWith(
|
widget.album!.copyWith(
|
||||||
coverProvider: AlbumManualCoverProvider(
|
coverProvider: AlbumManualCoverProvider(
|
||||||
coverFile: widget.file,
|
coverFile: _file!,
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
},
|
},
|
||||||
|
@ -428,20 +469,23 @@ class _ViewerDetailPaneState extends State<ViewerDetailPane> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onAddToAlbumPressed(BuildContext context) {
|
Future<void> _onAddToAlbumPressed(BuildContext context) {
|
||||||
return AddSelectionToAlbumHandler()(
|
assert(_file != null);
|
||||||
|
final c = KiwiContainer().resolve<DiContainer>();
|
||||||
|
return AddSelectionToAlbumHandler(c)(
|
||||||
context: context,
|
context: context,
|
||||||
account: widget.account,
|
account: widget.account,
|
||||||
selectedFiles: [widget.file],
|
selection: [_file!],
|
||||||
clearSelection: () {},
|
clearSelection: () {},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onArchivePressed(BuildContext context) async {
|
Future<void> _onArchivePressed(BuildContext context) async {
|
||||||
_log.info("[_onArchivePressed] Archive file: ${widget.file.path}");
|
assert(_file != null);
|
||||||
final count =
|
_log.info("[_onArchivePressed] Archive file: ${widget.fd.fdPath}");
|
||||||
await ArchiveSelectionHandler(KiwiContainer().resolve<DiContainer>())(
|
final c = KiwiContainer().resolve<DiContainer>();
|
||||||
|
final count = await ArchiveSelectionHandler(c)(
|
||||||
account: widget.account,
|
account: widget.account,
|
||||||
selectedFiles: [widget.file],
|
selection: [_file!],
|
||||||
);
|
);
|
||||||
if (count == 1) {
|
if (count == 1) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
|
@ -451,12 +495,13 @@ class _ViewerDetailPaneState extends State<ViewerDetailPane> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onUnarchivePressed(BuildContext context) async {
|
Future<void> _onUnarchivePressed(BuildContext context) async {
|
||||||
_log.info("[_onUnarchivePressed] Unarchive file: ${widget.file.path}");
|
assert(_file != null);
|
||||||
|
_log.info("[_onUnarchivePressed] Unarchive file: ${widget.fd.fdPath}");
|
||||||
try {
|
try {
|
||||||
await NotifiedAction(
|
await NotifiedAction(
|
||||||
() async {
|
() async {
|
||||||
await UpdateProperty(_c.fileRepo)
|
await UpdateProperty(_c.fileRepo)
|
||||||
.updateIsArchived(widget.account, widget.file, false);
|
.updateIsArchived(widget.account, _file!, false);
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
}
|
}
|
||||||
|
@ -467,7 +512,7 @@ class _ViewerDetailPaneState extends State<ViewerDetailPane> {
|
||||||
)();
|
)();
|
||||||
} catch (e, stackTrace) {
|
} catch (e, stackTrace) {
|
||||||
_log.shout(
|
_log.shout(
|
||||||
"[_onUnarchivePressed] Failed while archiving file: ${logFilename(widget.file.path)}",
|
"[_onUnarchivePressed] Failed while archiving file: ${logFilename(widget.fd.fdPath)}",
|
||||||
e,
|
e,
|
||||||
stackTrace);
|
stackTrace);
|
||||||
}
|
}
|
||||||
|
@ -484,6 +529,7 @@ class _ViewerDetailPaneState extends State<ViewerDetailPane> {
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onDateTimeTap(BuildContext context) {
|
void _onDateTimeTap(BuildContext context) {
|
||||||
|
assert(_file != null);
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => PhotoDateTimeEditDialog(initialDateTime: _dateTime),
|
builder: (context) => PhotoDateTimeEditDialog(initialDateTime: _dateTime),
|
||||||
|
@ -493,7 +539,7 @@ class _ViewerDetailPaneState extends State<ViewerDetailPane> {
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await UpdateProperty(_c.fileRepo)
|
await UpdateProperty(_c.fileRepo)
|
||||||
.updateOverrideDateTime(widget.account, widget.file, value);
|
.updateOverrideDateTime(widget.account, _file!, value);
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_dateTime = value;
|
_dateTime = value;
|
||||||
|
@ -501,7 +547,7 @@ class _ViewerDetailPaneState extends State<ViewerDetailPane> {
|
||||||
}
|
}
|
||||||
} catch (e, stacktrace) {
|
} catch (e, stacktrace) {
|
||||||
_log.shout(
|
_log.shout(
|
||||||
"[_onDateTimeTap] Failed while updateOverrideDateTime: ${logFilename(widget.file.path)}",
|
"[_onDateTimeTap] Failed while updateOverrideDateTime: ${logFilename(widget.fd.fdPath)}",
|
||||||
e,
|
e,
|
||||||
stacktrace);
|
stacktrace);
|
||||||
SnackBarManager().showSnackBar(SnackBar(
|
SnackBarManager().showSnackBar(SnackBar(
|
||||||
|
@ -527,7 +573,7 @@ class _ViewerDetailPaneState extends State<ViewerDetailPane> {
|
||||||
.items
|
.items
|
||||||
.whereType<AlbumFileItem>()
|
.whereType<AlbumFileItem>()
|
||||||
.firstWhere(
|
.firstWhere(
|
||||||
(element) => element.file.compareServerIdentity(widget.file));
|
(element) => element.file.compareServerIdentity(widget.fd));
|
||||||
if (thisItem.addedBy == widget.account.userId) {
|
if (thisItem.addedBy == widget.account.userId) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -537,6 +583,7 @@ class _ViewerDetailPaneState extends State<ViewerDetailPane> {
|
||||||
|
|
||||||
late final DiContainer _c;
|
late final DiContainer _c;
|
||||||
|
|
||||||
|
File? _file;
|
||||||
late DateTime _dateTime;
|
late DateTime _dateTime;
|
||||||
// EXIF data
|
// EXIF data
|
||||||
String? _model;
|
String? _model;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import 'package:nc_photos/ci_string.dart';
|
import 'package:nc_photos/ci_string.dart';
|
||||||
import 'package:nc_photos/entity/exif.dart';
|
import 'package:nc_photos/entity/exif.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/or_null.dart';
|
import 'package:nc_photos/or_null.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ import 'package:nc_photos/entity/album.dart';
|
||||||
import 'package:nc_photos/entity/favorite.dart';
|
import 'package:nc_photos/entity/favorite.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
import 'package:nc_photos/entity/file/data_source.dart';
|
import 'package:nc_photos/entity/file/data_source.dart';
|
||||||
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
||||||
import 'package:nc_photos/entity/person.dart';
|
import 'package:nc_photos/entity/person.dart';
|
||||||
import 'package:nc_photos/entity/share.dart';
|
import 'package:nc_photos/entity/share.dart';
|
||||||
|
|
|
@ -12,6 +12,7 @@ import 'package:nc_photos/entity/album/item.dart';
|
||||||
import 'package:nc_photos/entity/album/provider.dart';
|
import 'package:nc_photos/entity/album/provider.dart';
|
||||||
import 'package:nc_photos/entity/album/sort_provider.dart';
|
import 'package:nc_photos/entity/album/sort_provider.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/entity/share.dart';
|
import 'package:nc_photos/entity/share.dart';
|
||||||
import 'package:nc_photos/entity/sharee.dart';
|
import 'package:nc_photos/entity/sharee.dart';
|
||||||
import 'package:nc_photos/entity/sqlite_table.dart' as sql;
|
import 'package:nc_photos/entity/sqlite_table.dart' as sql;
|
||||||
|
@ -377,6 +378,15 @@ File buildJpegFile({
|
||||||
ownerDisplayName: ownerDisplayName ?? ownerId.toString(),
|
ownerDisplayName: ownerDisplayName ?? ownerId.toString(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
FileDescriptor fileToFileDescriptor(File f) => FileDescriptor(
|
||||||
|
fdPath: f.path,
|
||||||
|
fdId: f.fileId!,
|
||||||
|
fdMime: f.contentType,
|
||||||
|
fdIsArchived: f.isArchived ?? false,
|
||||||
|
fdIsFavorite: f.isFavorite ?? false,
|
||||||
|
fdDateTime: f.bestDateTime,
|
||||||
|
);
|
||||||
|
|
||||||
Share buildShare({
|
Share buildShare({
|
||||||
required String id,
|
required String id,
|
||||||
DateTime? stime,
|
DateTime? stime,
|
||||||
|
|
113
app/test/use_case/inflate_file_descriptor_test.dart
Normal file
113
app/test/use_case/inflate_file_descriptor_test.dart
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
import 'package:nc_photos/di_container.dart';
|
||||||
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
|
import 'package:nc_photos/entity/sqlite_table_extension.dart' as sql;
|
||||||
|
import 'package:nc_photos/list_extension.dart';
|
||||||
|
import 'package:nc_photos/use_case/inflate_file_descriptor.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
import '../test_util.dart' as util;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group("InflateFileDescriptor", () {
|
||||||
|
test("one", _one);
|
||||||
|
test("multiple", _multiple);
|
||||||
|
test("missing", _missing);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inflate one FileDescriptor
|
||||||
|
///
|
||||||
|
/// Expect: one file
|
||||||
|
Future<void> _one() async {
|
||||||
|
final account = util.buildAccount();
|
||||||
|
final files = (util.FilesBuilder()
|
||||||
|
..addDir("admin")
|
||||||
|
..addJpeg("admin/test1.jpg")
|
||||||
|
..addJpeg("admin/test2.jpg"))
|
||||||
|
.build();
|
||||||
|
final c = DiContainer(
|
||||||
|
sqliteDb: util.buildTestDb(),
|
||||||
|
);
|
||||||
|
addTearDown(() => c.sqliteDb.close());
|
||||||
|
await c.sqliteDb.transaction(() async {
|
||||||
|
await c.sqliteDb.insertAccountOf(account);
|
||||||
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
await InflateFileDescriptor(c)(
|
||||||
|
account,
|
||||||
|
[util.fileToFileDescriptor(files[1])],
|
||||||
|
),
|
||||||
|
[files[1]],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inflate 3 FileDescriptors
|
||||||
|
///
|
||||||
|
/// Expect: 3 files
|
||||||
|
Future<void> _multiple() async {
|
||||||
|
final account = util.buildAccount();
|
||||||
|
final files = (util.FilesBuilder()
|
||||||
|
..addDir("admin")
|
||||||
|
..addJpeg("admin/test1.jpg")
|
||||||
|
..addJpeg("admin/test2.jpg")
|
||||||
|
..addJpeg("admin/test3.jpg")
|
||||||
|
..addJpeg("admin/test4.jpg")
|
||||||
|
..addJpeg("admin/test5.jpg")
|
||||||
|
..addJpeg("admin/test6.jpg"))
|
||||||
|
.build();
|
||||||
|
final c = DiContainer(
|
||||||
|
sqliteDb: util.buildTestDb(),
|
||||||
|
);
|
||||||
|
addTearDown(() => c.sqliteDb.close());
|
||||||
|
await c.sqliteDb.transaction(() async {
|
||||||
|
await c.sqliteDb.insertAccountOf(account);
|
||||||
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
await InflateFileDescriptor(c)(
|
||||||
|
account,
|
||||||
|
files.slice(1, 7, 2).map(util.fileToFileDescriptor).toList(),
|
||||||
|
),
|
||||||
|
[files[1], files[3], files[5]],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inflate a FileDescriptor that doesn't exists in the DB
|
||||||
|
///
|
||||||
|
/// Expect: throw StateError
|
||||||
|
Future<void> _missing() async {
|
||||||
|
final account = util.buildAccount();
|
||||||
|
final files = (util.FilesBuilder()
|
||||||
|
..addDir("admin")
|
||||||
|
..addJpeg("admin/test1.jpg")
|
||||||
|
..addJpeg("admin/test2.jpg"))
|
||||||
|
.build();
|
||||||
|
final c = DiContainer(
|
||||||
|
sqliteDb: util.buildTestDb(),
|
||||||
|
);
|
||||||
|
addTearDown(() => c.sqliteDb.close());
|
||||||
|
await c.sqliteDb.transaction(() async {
|
||||||
|
await c.sqliteDb.insertAccountOf(account);
|
||||||
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
() async => await InflateFileDescriptor(c)(
|
||||||
|
account,
|
||||||
|
[
|
||||||
|
FileDescriptor(
|
||||||
|
fdPath: "remote.php/dav/files/admin/test3.jpg",
|
||||||
|
fdId: 4,
|
||||||
|
fdMime: null,
|
||||||
|
fdIsArchived: false,
|
||||||
|
fdIsFavorite: false,
|
||||||
|
fdDateTime: DateTime.now(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
throwsA(const TypeMatcher<StateError>()),
|
||||||
|
);
|
||||||
|
}
|
|
@ -45,7 +45,7 @@ Future<void> _root() async {
|
||||||
(await ScanDirOffline(c)(
|
(await ScanDirOffline(c)(
|
||||||
account, File(path: file_util.unstripPath(account, "."))))
|
account, File(path: file_util.unstripPath(account, "."))))
|
||||||
.toSet(),
|
.toSet(),
|
||||||
files.toSet(),
|
files.map(util.fileToFileDescriptor).toSet(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,7 +73,7 @@ Future<void> _subDir() async {
|
||||||
(await ScanDirOffline(c)(
|
(await ScanDirOffline(c)(
|
||||||
account, File(path: file_util.unstripPath(account, "test"))))
|
account, File(path: file_util.unstripPath(account, "test"))))
|
||||||
.toSet(),
|
.toSet(),
|
||||||
{files[1]},
|
[files[1]].map(util.fileToFileDescriptor).toSet(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,7 +102,7 @@ Future<void> _unsupportedFile() async {
|
||||||
(await ScanDirOffline(c)(
|
(await ScanDirOffline(c)(
|
||||||
account, File(path: file_util.unstripPath(account, "."))))
|
account, File(path: file_util.unstripPath(account, "."))))
|
||||||
.toSet(),
|
.toSet(),
|
||||||
{files[0]},
|
[files[0]].map(util.fileToFileDescriptor).toSet(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,12 +140,12 @@ Future<void> _multiAccountRoot() async {
|
||||||
(await ScanDirOffline(c)(
|
(await ScanDirOffline(c)(
|
||||||
account, File(path: file_util.unstripPath(account, "."))))
|
account, File(path: file_util.unstripPath(account, "."))))
|
||||||
.toSet(),
|
.toSet(),
|
||||||
files.toSet(),
|
files.map(util.fileToFileDescriptor).toSet(),
|
||||||
);
|
);
|
||||||
expect(
|
expect(
|
||||||
(await ScanDirOffline(c)(
|
(await ScanDirOffline(c)(
|
||||||
user1Account, File(path: file_util.unstripPath(user1Account, "."))))
|
user1Account, File(path: file_util.unstripPath(user1Account, "."))))
|
||||||
.toSet(),
|
.toSet(),
|
||||||
user1Files.toSet(),
|
user1Files.map(util.fileToFileDescriptor).toSet(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -98,7 +98,7 @@ void _prevYear() {
|
||||||
name: "2020",
|
name: "2020",
|
||||||
provider:
|
provider:
|
||||||
AlbumMemoryProvider(year: 2020, month: today.month, day: today.day),
|
AlbumMemoryProvider(year: 2020, month: today.month, day: today.day),
|
||||||
coverProvider: AlbumManualCoverProvider(coverFile: file),
|
coverProvider: AlbumMemoryCoverProvider(coverFile: file),
|
||||||
sortProvider: const AlbumTimeSortProvider(isAscending: false),
|
sortProvider: const AlbumTimeSortProvider(isAscending: false),
|
||||||
lastUpdated: DateTime(2021),
|
lastUpdated: DateTime(2021),
|
||||||
),
|
),
|
||||||
|
@ -141,7 +141,7 @@ void _prevYear2DaysBefore() {
|
||||||
name: "2020",
|
name: "2020",
|
||||||
provider:
|
provider:
|
||||||
AlbumMemoryProvider(year: 2020, month: today.month, day: today.day),
|
AlbumMemoryProvider(year: 2020, month: today.month, day: today.day),
|
||||||
coverProvider: AlbumManualCoverProvider(coverFile: file),
|
coverProvider: AlbumMemoryCoverProvider(coverFile: file),
|
||||||
sortProvider: const AlbumTimeSortProvider(isAscending: false),
|
sortProvider: const AlbumTimeSortProvider(isAscending: false),
|
||||||
lastUpdated: DateTime(2021),
|
lastUpdated: DateTime(2021),
|
||||||
),
|
),
|
||||||
|
@ -184,7 +184,7 @@ void _prevYear2DaysAfter() {
|
||||||
name: "2020",
|
name: "2020",
|
||||||
provider:
|
provider:
|
||||||
AlbumMemoryProvider(year: 2020, month: today.month, day: today.day),
|
AlbumMemoryProvider(year: 2020, month: today.month, day: today.day),
|
||||||
coverProvider: AlbumManualCoverProvider(coverFile: file),
|
coverProvider: AlbumMemoryCoverProvider(coverFile: file),
|
||||||
sortProvider: const AlbumTimeSortProvider(isAscending: false),
|
sortProvider: const AlbumTimeSortProvider(isAscending: false),
|
||||||
lastUpdated: DateTime(2021),
|
lastUpdated: DateTime(2021),
|
||||||
),
|
),
|
||||||
|
@ -227,7 +227,7 @@ void _onFeb29AddFeb27() {
|
||||||
name: "2019",
|
name: "2019",
|
||||||
provider:
|
provider:
|
||||||
AlbumMemoryProvider(year: 2019, month: today.month, day: today.day),
|
AlbumMemoryProvider(year: 2019, month: today.month, day: today.day),
|
||||||
coverProvider: AlbumManualCoverProvider(coverFile: file),
|
coverProvider: AlbumMemoryCoverProvider(coverFile: file),
|
||||||
sortProvider: const AlbumTimeSortProvider(isAscending: false),
|
sortProvider: const AlbumTimeSortProvider(isAscending: false),
|
||||||
lastUpdated: DateTime(2021),
|
lastUpdated: DateTime(2021),
|
||||||
),
|
),
|
||||||
|
@ -270,7 +270,7 @@ void _onFeb29AddMar3() {
|
||||||
name: "2019",
|
name: "2019",
|
||||||
provider:
|
provider:
|
||||||
AlbumMemoryProvider(year: 2019, month: today.month, day: today.day),
|
AlbumMemoryProvider(year: 2019, month: today.month, day: today.day),
|
||||||
coverProvider: AlbumManualCoverProvider(coverFile: file),
|
coverProvider: AlbumMemoryCoverProvider(coverFile: file),
|
||||||
sortProvider: const AlbumTimeSortProvider(isAscending: false),
|
sortProvider: const AlbumTimeSortProvider(isAscending: false),
|
||||||
lastUpdated: DateTime(2021),
|
lastUpdated: DateTime(2021),
|
||||||
),
|
),
|
||||||
|
@ -313,7 +313,7 @@ void _onFeb29AddMar2LeapYear() {
|
||||||
name: "2016",
|
name: "2016",
|
||||||
provider:
|
provider:
|
||||||
AlbumMemoryProvider(year: 2016, month: today.month, day: today.day),
|
AlbumMemoryProvider(year: 2016, month: today.month, day: today.day),
|
||||||
coverProvider: AlbumManualCoverProvider(coverFile: file),
|
coverProvider: AlbumMemoryCoverProvider(coverFile: file),
|
||||||
sortProvider: const AlbumTimeSortProvider(isAscending: false),
|
sortProvider: const AlbumTimeSortProvider(isAscending: false),
|
||||||
lastUpdated: DateTime(2021),
|
lastUpdated: DateTime(2021),
|
||||||
),
|
),
|
||||||
|
@ -356,7 +356,7 @@ void _onJan1AddDec31PrevYear() {
|
||||||
name: "2019",
|
name: "2019",
|
||||||
provider:
|
provider:
|
||||||
AlbumMemoryProvider(year: 2019, month: today.month, day: today.day),
|
AlbumMemoryProvider(year: 2019, month: today.month, day: today.day),
|
||||||
coverProvider: AlbumManualCoverProvider(coverFile: file),
|
coverProvider: AlbumMemoryCoverProvider(coverFile: file),
|
||||||
sortProvider: const AlbumTimeSortProvider(isAscending: false),
|
sortProvider: const AlbumTimeSortProvider(isAscending: false),
|
||||||
lastUpdated: DateTime(2021),
|
lastUpdated: DateTime(2021),
|
||||||
),
|
),
|
||||||
|
@ -385,7 +385,7 @@ void _onDec31AddJan1() {
|
||||||
name: "2019",
|
name: "2019",
|
||||||
provider:
|
provider:
|
||||||
AlbumMemoryProvider(year: 2019, month: today.month, day: today.day),
|
AlbumMemoryProvider(year: 2019, month: today.month, day: today.day),
|
||||||
coverProvider: AlbumManualCoverProvider(coverFile: file),
|
coverProvider: AlbumMemoryCoverProvider(coverFile: file),
|
||||||
sortProvider: const AlbumTimeSortProvider(isAscending: false),
|
sortProvider: const AlbumTimeSortProvider(isAscending: false),
|
||||||
lastUpdated: DateTime(2021),
|
lastUpdated: DateTime(2021),
|
||||||
),
|
),
|
||||||
|
@ -414,7 +414,7 @@ void _onMay15AddMay15Range0() {
|
||||||
name: "2021",
|
name: "2021",
|
||||||
provider:
|
provider:
|
||||||
AlbumMemoryProvider(year: 2021, month: today.month, day: today.day),
|
AlbumMemoryProvider(year: 2021, month: today.month, day: today.day),
|
||||||
coverProvider: AlbumManualCoverProvider(coverFile: file),
|
coverProvider: AlbumMemoryCoverProvider(coverFile: file),
|
||||||
sortProvider: const AlbumTimeSortProvider(isAscending: false),
|
sortProvider: const AlbumTimeSortProvider(isAscending: false),
|
||||||
lastUpdated: DateTime(2021),
|
lastUpdated: DateTime(2021),
|
||||||
),
|
),
|
||||||
|
|
Loading…
Reference in a new issue