2024-01-12 19:53:14 +01:00
|
|
|
part of '../home_photos2.dart';
|
|
|
|
|
|
|
|
@npLog
|
|
|
|
class _Bloc extends Bloc<_Event, _State> with BlocLogger {
|
|
|
|
_Bloc(
|
|
|
|
this._c, {
|
|
|
|
required this.account,
|
|
|
|
required this.controller,
|
|
|
|
required this.prefController,
|
|
|
|
required this.accountPrefController,
|
|
|
|
required this.collectionsController,
|
2024-01-17 18:17:53 +01:00
|
|
|
required this.syncController,
|
|
|
|
required this.personsController,
|
2024-01-30 18:31:52 +01:00
|
|
|
required this.metadataController,
|
2024-01-12 19:53:14 +01:00
|
|
|
}) : super(_State.init(
|
2024-02-25 05:09:57 +01:00
|
|
|
zoom: prefController.homePhotosZoomLevelValue,
|
2024-01-12 19:53:14 +01:00
|
|
|
isEnableMemoryCollection:
|
2024-02-25 05:09:57 +01:00
|
|
|
accountPrefController.isEnableMemoryAlbumValue,
|
2024-01-12 19:53:14 +01:00
|
|
|
)) {
|
|
|
|
on<_LoadItems>(_onLoad);
|
2024-01-30 18:31:52 +01:00
|
|
|
on<_RequestRefresh>(_onRequestRefresh);
|
2024-01-12 19:53:14 +01:00
|
|
|
on<_TransformItems>(_onTransformItems);
|
|
|
|
on<_OnItemTransformed>(_onOnItemTransformed);
|
|
|
|
|
|
|
|
on<_SetSelectedItems>(_onSetSelectedItems);
|
|
|
|
on<_AddSelectedItemsToCollection>(_onAddSelectedItemsToCollection);
|
|
|
|
on<_ArchiveSelectedItems>(_onArchiveSelectedItems);
|
|
|
|
on<_DeleteSelectedItems>(_onDeleteSelectedItems);
|
|
|
|
on<_DownloadSelectedItems>(_onDownloadSelectedItems);
|
|
|
|
|
|
|
|
on<_AddVisibleItem>(_onAddVisibleItem);
|
|
|
|
on<_RemoveVisibleItem>(_onRemoveVisibleItem);
|
|
|
|
|
|
|
|
on<_SetContentListMaxExtent>(_onSetContentListMaxExtent);
|
2024-01-13 11:57:44 +01:00
|
|
|
on<_SetSyncProgress>(_onSetSyncProgress);
|
2024-01-12 19:53:14 +01:00
|
|
|
|
|
|
|
on<_StartScaling>(_onStartScaling);
|
|
|
|
on<_EndScaling>(_onEndScaling);
|
|
|
|
on<_SetScale>(_onSetScale);
|
|
|
|
|
|
|
|
on<_SetEnableMemoryCollection>(_onSetEnableMemoryCollection);
|
2024-01-13 19:09:10 +01:00
|
|
|
on<_SetSortByName>(_onSetSortByName);
|
2024-01-13 19:23:30 +01:00
|
|
|
on<_SetMemoriesRange>(_onSetMemoriesRange);
|
2024-01-13 19:48:49 +01:00
|
|
|
on<_UpdateDateTimeGroup>(_onUpdateDateTimeGroup);
|
2024-01-12 19:53:14 +01:00
|
|
|
|
|
|
|
on<_SetError>(_onSetError);
|
|
|
|
|
|
|
|
_subscriptions
|
2024-02-25 05:09:57 +01:00
|
|
|
.add(accountPrefController.isEnableMemoryAlbumChange.listen((event) {
|
2024-01-12 19:53:14 +01:00
|
|
|
add(_SetEnableMemoryCollection(event));
|
|
|
|
}));
|
2024-02-25 05:09:57 +01:00
|
|
|
_subscriptions
|
|
|
|
.add(prefController.isPhotosTabSortByNameChange.listen((event) {
|
2024-01-13 19:09:10 +01:00
|
|
|
add(_SetSortByName(event));
|
|
|
|
}));
|
2024-02-25 05:09:57 +01:00
|
|
|
_subscriptions.add(prefController.memoriesRangeChange.listen((event) {
|
2024-01-13 19:23:30 +01:00
|
|
|
add(_SetMemoriesRange(event));
|
|
|
|
}));
|
2024-01-12 19:53:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
Future<void> close() {
|
|
|
|
for (final s in _subscriptions) {
|
|
|
|
s.cancel();
|
|
|
|
}
|
|
|
|
return super.close();
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
String get tag => _log.fullName;
|
|
|
|
|
|
|
|
@override
|
|
|
|
bool Function(dynamic, dynamic)? get shouldLog => (currentState, nextState) {
|
|
|
|
currentState = currentState as _State;
|
|
|
|
nextState = nextState as _State;
|
|
|
|
return currentState.scale == nextState.scale &&
|
2024-01-13 11:57:44 +01:00
|
|
|
currentState.visibleItems == nextState.visibleItems &&
|
|
|
|
currentState.syncProgress == nextState.syncProgress;
|
2024-01-12 19:53:14 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
@override
|
|
|
|
void onError(Object error, StackTrace stackTrace) {
|
|
|
|
// we need this to prevent onError being triggered recursively
|
|
|
|
if (!isClosed && !_isHandlingError) {
|
|
|
|
_isHandlingError = true;
|
|
|
|
try {
|
|
|
|
add(_SetError(error, stackTrace));
|
|
|
|
} catch (_) {}
|
|
|
|
_isHandlingError = false;
|
|
|
|
}
|
|
|
|
super.onError(error, stackTrace);
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<void> _onLoad(_LoadItems ev, Emitter<_State> emit) {
|
|
|
|
_log.info(ev);
|
|
|
|
return emit.forEach<FilesStreamEvent>(
|
|
|
|
controller.stream,
|
2024-01-13 11:57:44 +01:00
|
|
|
onData: (data) {
|
|
|
|
if (_isInitialLoad && !data.hasNext) {
|
|
|
|
_isInitialLoad = false;
|
|
|
|
_syncRemote();
|
|
|
|
}
|
|
|
|
return state.copyWith(
|
|
|
|
files: data.data,
|
|
|
|
isLoading: data.hasNext || _itemTransformerQueue.isProcessing,
|
|
|
|
);
|
|
|
|
},
|
2024-01-12 19:53:14 +01:00
|
|
|
onError: (e, stackTrace) {
|
|
|
|
_log.severe("[_onLoad] Uncaught exception", e, stackTrace);
|
|
|
|
return state.copyWith(
|
|
|
|
isLoading: _itemTransformerQueue.isProcessing,
|
|
|
|
error: ExceptionEvent(e, stackTrace),
|
|
|
|
);
|
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2024-01-30 18:31:52 +01:00
|
|
|
void _onRequestRefresh(_RequestRefresh ev, Emitter<_State> emit) {
|
2024-01-12 19:53:14 +01:00
|
|
|
_log.info(ev);
|
2024-01-13 14:59:25 +01:00
|
|
|
emit(state.copyWith(syncProgress: const Progress(0)));
|
2024-01-13 11:57:44 +01:00
|
|
|
_syncRemote();
|
2024-01-30 18:31:52 +01:00
|
|
|
metadataController.scheduleNext();
|
2024-01-12 19:53:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void _onTransformItems(_TransformItems ev, Emitter<_State> emit) {
|
|
|
|
_log.info(ev);
|
|
|
|
_transformItems(ev.items);
|
|
|
|
emit(state.copyWith(isLoading: true));
|
|
|
|
}
|
|
|
|
|
|
|
|
void _onOnItemTransformed(_OnItemTransformed ev, Emitter<_State> emit) {
|
|
|
|
_log.info(ev);
|
|
|
|
emit(state.copyWith(
|
|
|
|
transformedItems: ev.items,
|
|
|
|
memoryCollections: ev.memoryCollections,
|
|
|
|
isLoading: _itemTransformerQueue.isProcessing,
|
|
|
|
));
|
2024-01-17 18:17:53 +01:00
|
|
|
syncController.requestSync(
|
|
|
|
account: account,
|
|
|
|
filesController: controller,
|
|
|
|
personsController: personsController,
|
2024-02-25 05:09:57 +01:00
|
|
|
personProvider: accountPrefController.personProviderValue,
|
2024-01-17 18:17:53 +01:00
|
|
|
);
|
2024-01-12 19:53:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void _onSetSelectedItems(_SetSelectedItems ev, Emitter<_State> emit) {
|
|
|
|
_log.info(ev);
|
|
|
|
emit(state.copyWith(selectedItems: ev.items));
|
|
|
|
}
|
|
|
|
|
|
|
|
void _onAddSelectedItemsToCollection(
|
|
|
|
_AddSelectedItemsToCollection ev, Emitter<_State> emit) {
|
|
|
|
_log.info(ev);
|
|
|
|
final selected = state.selectedItems;
|
|
|
|
_clearSelection(emit);
|
|
|
|
final selectedFiles =
|
|
|
|
selected.whereType<_FileItem>().map((e) => e.file).toList();
|
|
|
|
if (selectedFiles.isNotEmpty) {
|
|
|
|
final targetController = collectionsController.stream.value
|
|
|
|
.itemsControllerByCollection(ev.collection);
|
|
|
|
targetController.addFiles(selectedFiles).onError((e, stackTrace) {
|
|
|
|
if (e != null) {
|
|
|
|
add(_SetError(e, stackTrace));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void _onArchiveSelectedItems(_ArchiveSelectedItems ev, Emitter<_State> emit) {
|
|
|
|
_log.info(ev);
|
|
|
|
final selected = state.selectedItems;
|
|
|
|
_clearSelection(emit);
|
|
|
|
final selectedFiles =
|
|
|
|
selected.whereType<_FileItem>().map((e) => e.file).toList();
|
|
|
|
if (selectedFiles.isNotEmpty) {
|
|
|
|
controller.updateProperty(
|
|
|
|
selectedFiles,
|
|
|
|
isArchived: const OrNull(true),
|
|
|
|
errorBuilder: (fileIds) => _ArchiveFailedError(fileIds.length),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void _onDeleteSelectedItems(_DeleteSelectedItems ev, Emitter<_State> emit) {
|
|
|
|
_log.info(ev);
|
|
|
|
final selected = state.selectedItems;
|
|
|
|
_clearSelection(emit);
|
|
|
|
final selectedFiles =
|
|
|
|
selected.whereType<_FileItem>().map((e) => e.file).toList();
|
|
|
|
if (selectedFiles.isNotEmpty) {
|
|
|
|
controller.remove(
|
|
|
|
selectedFiles,
|
|
|
|
errorBuilder: (fileIds) => _RemoveFailedError(fileIds.length),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void _onDownloadSelectedItems(
|
|
|
|
_DownloadSelectedItems ev, Emitter<_State> emit) {
|
|
|
|
_log.info(ev);
|
|
|
|
final selected = state.selectedItems;
|
|
|
|
_clearSelection(emit);
|
|
|
|
final selectedFiles =
|
|
|
|
selected.whereType<_FileItem>().map((e) => e.file).toList();
|
|
|
|
if (selectedFiles.isNotEmpty) {
|
|
|
|
unawaited(DownloadHandler(_c).downloadFiles(account, selectedFiles));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void _onAddVisibleItem(_AddVisibleItem ev, Emitter<_State> emit) {
|
|
|
|
// _log.info(ev);
|
|
|
|
if (state.visibleItems.contains(ev.item)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
emit(state.copyWith(
|
|
|
|
visibleItems: state.visibleItems.added(ev.item),
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
void _onRemoveVisibleItem(_RemoveVisibleItem ev, Emitter<_State> emit) {
|
|
|
|
// _log.info(ev);
|
|
|
|
if (!state.visibleItems.contains(ev.item)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
emit(state.copyWith(
|
|
|
|
visibleItems: state.visibleItems.removed(ev.item),
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
void _onSetContentListMaxExtent(
|
|
|
|
_SetContentListMaxExtent ev, Emitter<_State> emit) {
|
|
|
|
_log.info(ev);
|
|
|
|
emit(state.copyWith(contentListMaxExtent: ev.value));
|
|
|
|
}
|
|
|
|
|
2024-01-13 11:57:44 +01:00
|
|
|
void _onSetSyncProgress(_SetSyncProgress ev, Emitter<_State> emit) {
|
|
|
|
_log.info(ev);
|
|
|
|
emit(state.copyWith(syncProgress: ev.progress));
|
|
|
|
}
|
|
|
|
|
2024-01-12 19:53:14 +01:00
|
|
|
void _onStartScaling(_StartScaling ev, Emitter<_State> emit) {
|
|
|
|
_log.info(ev);
|
|
|
|
}
|
|
|
|
|
|
|
|
void _onEndScaling(_EndScaling ev, Emitter<_State> emit) {
|
|
|
|
_log.info(ev);
|
|
|
|
if (state.scale == null) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
final int newZoom;
|
2024-01-13 19:48:49 +01:00
|
|
|
final currZoom = state.zoom;
|
2024-01-12 19:53:14 +01:00
|
|
|
if (state.scale! >= 1.25) {
|
|
|
|
// scale up
|
2024-01-13 19:48:49 +01:00
|
|
|
newZoom = (currZoom + 1).clamp(-1, 2);
|
2024-01-12 19:53:14 +01:00
|
|
|
} else if (state.scale! <= 0.75) {
|
2024-01-13 19:48:49 +01:00
|
|
|
newZoom = (currZoom - 1).clamp(-1, 2);
|
2024-01-12 19:53:14 +01:00
|
|
|
} else {
|
2024-01-13 19:48:49 +01:00
|
|
|
newZoom = currZoom;
|
2024-01-12 19:53:14 +01:00
|
|
|
}
|
|
|
|
emit(state.copyWith(
|
|
|
|
zoom: newZoom,
|
|
|
|
scale: null,
|
|
|
|
));
|
2024-01-13 19:48:49 +01:00
|
|
|
if ((currZoom >= 0) != (newZoom >= 0)) {
|
|
|
|
add(const _UpdateDateTimeGroup());
|
|
|
|
}
|
2024-01-12 19:53:14 +01:00
|
|
|
unawaited(prefController.setHomePhotosZoomLevel(newZoom));
|
|
|
|
}
|
|
|
|
|
|
|
|
void _onSetScale(_SetScale ev, Emitter<_State> emit) {
|
|
|
|
// _log.info(ev);
|
|
|
|
emit(state.copyWith(scale: ev.scale));
|
|
|
|
}
|
|
|
|
|
|
|
|
void _onSetEnableMemoryCollection(
|
|
|
|
_SetEnableMemoryCollection ev, Emitter<_State> emit) {
|
|
|
|
_log.info(ev);
|
|
|
|
emit(state.copyWith(isEnableMemoryCollection: ev.value));
|
|
|
|
}
|
|
|
|
|
2024-01-13 19:09:10 +01:00
|
|
|
void _onSetSortByName(_SetSortByName ev, Emitter<_State> emit) {
|
|
|
|
_log.info(ev);
|
|
|
|
_transformItems(state.files);
|
|
|
|
}
|
|
|
|
|
2024-01-13 19:23:30 +01:00
|
|
|
void _onSetMemoriesRange(_SetMemoriesRange ev, Emitter<_State> emit) {
|
|
|
|
_log.info(ev);
|
|
|
|
_transformItems(state.files);
|
|
|
|
}
|
|
|
|
|
2024-01-13 19:48:49 +01:00
|
|
|
void _onUpdateDateTimeGroup(_UpdateDateTimeGroup ev, Emitter<_State> emit) {
|
|
|
|
_log.info(ev);
|
|
|
|
_transformItems(state.files);
|
|
|
|
}
|
|
|
|
|
2024-01-12 19:53:14 +01:00
|
|
|
void _onSetError(_SetError ev, Emitter<_State> emit) {
|
|
|
|
_log.info(ev);
|
|
|
|
emit(state.copyWith(error: ExceptionEvent(ev.error, ev.stackTrace)));
|
|
|
|
}
|
|
|
|
|
|
|
|
Future _transformItems(List<FileDescriptor> files) async {
|
|
|
|
_log.info("[_transformItems] Queue ${files.length} items");
|
|
|
|
_itemTransformerQueue.addJob(
|
|
|
|
_ItemTransformerArgument(
|
|
|
|
account: account,
|
|
|
|
files: files,
|
2024-02-25 05:09:57 +01:00
|
|
|
sort: prefController.isPhotosTabSortByNameValue
|
2024-01-13 19:09:10 +01:00
|
|
|
? _ItemSort.filename
|
|
|
|
: _ItemSort.dateTime,
|
2024-02-25 05:09:57 +01:00
|
|
|
isGroupByDay: prefController.homePhotosZoomLevelValue >= 0,
|
|
|
|
memoriesDayRange: prefController.memoriesRangeValue,
|
2024-01-12 19:53:14 +01:00
|
|
|
locale: language_util.getSelectedLocale() ??
|
|
|
|
PlatformDispatcher.instance.locale,
|
|
|
|
),
|
|
|
|
_buildItem,
|
|
|
|
(result) {
|
|
|
|
if (!isClosed) {
|
|
|
|
add(_OnItemTransformed(result.items, result.memoryCollections));
|
|
|
|
}
|
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2024-01-13 11:57:44 +01:00
|
|
|
void _syncRemote() {
|
|
|
|
final stopwatch = Stopwatch()..start();
|
|
|
|
controller.syncRemote(
|
|
|
|
onProgressUpdate: (progress) {
|
|
|
|
if (!isClosed) {
|
|
|
|
add(_SetSyncProgress(progress));
|
|
|
|
}
|
|
|
|
},
|
|
|
|
).whenComplete(() {
|
|
|
|
if (!isClosed) {
|
|
|
|
add(const _SetSyncProgress(null));
|
|
|
|
}
|
2024-01-13 14:59:25 +01:00
|
|
|
_log.info(
|
|
|
|
"[_syncRemote] Elapsed time: ${stopwatch.elapsedMilliseconds}ms");
|
2024-01-13 11:57:44 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2024-01-12 19:53:14 +01:00
|
|
|
void _clearSelection(Emitter<_State> emit) {
|
|
|
|
emit(state.copyWith(selectedItems: const {}));
|
|
|
|
}
|
|
|
|
|
|
|
|
final DiContainer _c;
|
|
|
|
final Account account;
|
|
|
|
final FilesController controller;
|
|
|
|
final PrefController prefController;
|
|
|
|
final AccountPrefController accountPrefController;
|
|
|
|
final CollectionsController collectionsController;
|
2024-01-17 18:17:53 +01:00
|
|
|
final SyncController syncController;
|
|
|
|
final PersonsController personsController;
|
2024-01-30 18:31:52 +01:00
|
|
|
final MetadataController metadataController;
|
2024-01-12 19:53:14 +01:00
|
|
|
|
|
|
|
final _itemTransformerQueue =
|
|
|
|
ComputeQueue<_ItemTransformerArgument, _ItemTransformerResult>();
|
|
|
|
final _subscriptions = <StreamSubscription>[];
|
|
|
|
var _isHandlingError = false;
|
2024-01-13 11:57:44 +01:00
|
|
|
var _isInitialLoad = true;
|
2024-01-12 19:53:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
_ItemTransformerResult _buildItem(_ItemTransformerArgument arg) {
|
2024-01-13 19:09:10 +01:00
|
|
|
final int Function(FileDescriptor, FileDescriptor) sorter;
|
|
|
|
switch (arg.sort) {
|
|
|
|
case _ItemSort.filename:
|
|
|
|
sorter = (a, b) => a.fdPath.compareTo(b.fdPath);
|
|
|
|
break;
|
|
|
|
case _ItemSort.dateTime:
|
|
|
|
default:
|
|
|
|
sorter = compareFileDescriptorDateTimeDescending;
|
|
|
|
break;
|
|
|
|
}
|
2024-01-12 19:53:14 +01:00
|
|
|
|
2024-01-13 19:09:10 +01:00
|
|
|
final sortedFiles =
|
|
|
|
arg.files.where((f) => f.fdIsArchived != true).sorted(sorter);
|
|
|
|
final dateHelper = arg.sort == _ItemSort.dateTime
|
2024-01-13 19:48:49 +01:00
|
|
|
? photo_list_util.DateGroupHelper(isMonthOnly: !arg.isGroupByDay)
|
2024-01-13 19:09:10 +01:00
|
|
|
: null;
|
2024-01-12 19:53:14 +01:00
|
|
|
final today = clock.now();
|
2024-01-13 19:09:10 +01:00
|
|
|
final memoryCollectionHelper = arg.sort == _ItemSort.dateTime
|
|
|
|
? photo_list_util.MemoryCollectionHelper(
|
|
|
|
arg.account,
|
|
|
|
today: today,
|
|
|
|
dayRange: arg.memoriesDayRange,
|
|
|
|
)
|
|
|
|
: null;
|
|
|
|
|
2024-02-25 05:49:06 +01:00
|
|
|
final tzOffset = clock.now().timeZoneOffset;
|
2024-01-12 19:53:14 +01:00
|
|
|
final transformed = <_Item>[];
|
|
|
|
for (int i = 0; i < sortedFiles.length; ++i) {
|
|
|
|
final file = sortedFiles[i];
|
|
|
|
final item = _buildSingleItem(arg.account, file);
|
|
|
|
if (item == null) {
|
|
|
|
continue;
|
|
|
|
}
|
2024-02-25 05:49:06 +01:00
|
|
|
final localDate = file.fdDateTime.add(tzOffset);
|
|
|
|
final date = dateHelper?.onFile(file, localDate: localDate);
|
2024-01-12 19:53:14 +01:00
|
|
|
if (date != null) {
|
2024-01-13 19:48:49 +01:00
|
|
|
transformed.add(_DateItem(date: date, isMonthOnly: !arg.isGroupByDay));
|
2024-01-12 19:53:14 +01:00
|
|
|
}
|
|
|
|
transformed.add(item);
|
2024-02-25 05:49:06 +01:00
|
|
|
memoryCollectionHelper?.addFile(file, localDate: localDate);
|
2024-01-12 19:53:14 +01:00
|
|
|
}
|
|
|
|
final memoryCollections = memoryCollectionHelper
|
2024-01-13 19:09:10 +01:00
|
|
|
?.build((year) => L10n.of(arg.locale).memoryAlbumName(today.year - year));
|
2024-01-12 19:53:14 +01:00
|
|
|
return _ItemTransformerResult(
|
|
|
|
items: transformed,
|
2024-01-13 19:09:10 +01:00
|
|
|
memoryCollections: memoryCollections ?? [],
|
2024-01-12 19:53:14 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
_Item? _buildSingleItem(Account account, FileDescriptor file) {
|
|
|
|
if (file_util.isSupportedImageFormat(file)) {
|
|
|
|
return _PhotoItem(
|
|
|
|
file: file,
|
|
|
|
account: account,
|
|
|
|
);
|
|
|
|
} else if (file_util.isSupportedVideoFormat(file)) {
|
|
|
|
return _VideoItem(
|
|
|
|
file: file,
|
|
|
|
account: account,
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
_$__NpLog.log
|
|
|
|
.shout("[_buildSingleItem] Unsupported file format: ${file.fdMime}");
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|